The code you posted above does not have a MyApp.Model.get_all_shared/0
though, so if you are supposed to have it then you did not show code that defines it.
This is your code from above:
defmodule MyApp.Model do
use MyApp.Web, :model
import MyApp.Models.Helpers
end
defmodule MyApp.Models.Helpers do
import Ecto.Query
def get_all_shared(query) do
from q in query,
select: q
end
end
You defined a get_all_shared/2
inside of MyApp.Models.Helpers
but you have defined no functions at all whatsoever inside of MyApp.Model
. The only thing MyApp.Model
has defined in it is use MyApp.Web, :model
, which defines some helpers for functions inside of the MyApp.Model
module, and it has import MyApp.Models.Helpers
, which brings the functions of import MyApp.Models.Helpers
into the scope of this module for functions inside of this module to more easily call, but since there are no functions in this module the import MyApp.Models.Helpers
line and use MyApp.Web, :model
line are both basically no-ops.
1 Like
thanks! sorry im still very new to this! I thought I can call get_all_shared from MyApp.Model since i already imported it without defining a function inside of it. =p my bad thanks!
import
just brings another modules public functions into the ālocalā scope.
To re-export to the outer scope you need to defdelegate each function that you want to re-export.
3 Likes
thanks! havent heard of defdelegate before will check it out. elixir/phoenix community rocks!
Is it better to do the defdelegate in this case or just put the shared basic queries in Repo?
Explicit is better than implicit.
I like to keep things separate so I like defdelegate. (Macro stuff, donāt do unless you are getting comfortable with them: You can also make a using macro that does the importing or on-site definition as well.)
1 Like
Hi, do you have example code of this in practice? Iām specifically interested in the tests used.
It is still not clear to me where to put DB functions.
I get that we can put them in any module, but it is a little confusing as to which makes most senseā¦
The only takeway I have is that it sounds like it depends on the project size.
According to the Ecto docs:
Ecto.Repo - repositories are wrappers around the data store. Via the repository, we can create, update, destroy and query existing entries. A repository needs an adapter and credentials to communicate to the database
Which to me reads that the Repo is a suitable location for function callsā¦ Where as Schema, Changeset and Query are all building blocks of the call. The other way I also read this is that Repo is purely for the database connectionā¦ and the querying is to be done elsewhereā¦
I admit, I am not as regular Phoenix / Elixir programmer as I would like to be, but this topic seems to be ambiguous every time I come back to work on my appā¦ and I get the feeling I am not the only one.
1 Like
The current Phoenix way of doing it is to put those functions into context modules.
If itās a small project you might just use the main app file: MyApp.get_post(id)
.
If itās a bigger app, you might chose a context based on the use case (Blog.get_post(id)
or Accounts.get_user(id)
) or maybe you decide resource based contexts would be better (Posts.get(id)
).
Whereas Rails is all about āconvention over configurationā (which is great for a lot of apps, but can be a real pain in the butt for others), Phoenix is ānot your applicationā, so itās worth thinking about what architecture makes the most sense for what your building.
I personally donāt follow the Phoenix way, and itās not an issue, everything still works, Phoenix still serves my app, and I donāt have to jump through hoops to make it function (which I often did with Rails), it just sometimes takes a little more forethought about how Iām going to build it.
1 Like
Yes, you are rightā¦ I forgot about that.
I was getting too caught up with configuration.
Thanks.
If you are familiar with Rails, there is a bit of an analogue you can do (depending on your style). Itās sort of covered in this thread but with the older āmodelā pattern.
So query fragments can go in your schema, which is roughly equivalent Railsā scope
s. You then compose these fragments in contexts. If you are familiar with DDD, this can be really nice.
defmodule Shop.Orders.Order do
use Ecot.Schema
import Ecto.{Changeset, Query} # Make sure you import Query, the generator just imports Changeset
schema "orders" do
field :placed_at, :integer
end
# def changeset( ...
def sorted do
from __MODULE__,
order_by: [desc: :placed_at]
end
end
defmodule Shop.Orders do
alias Shop.Orders.Order
def list_orders do
Order.sorted()
|> Repo.all()
end
end
You can then call your context function (Shop.Orders.list_orders
) from your controller or LiveView or wherever.
Contexts are really nice because, unlike Rails, they give you a clear root entry point into a particular concept (ie, context). In Rails, we are usually forced to decide on which model is going to act as the root. In my example of a webshop, this is fairly obviousāthe Order
is the root. First we find an order, then we use that order to list all of its items, discounts, etc. In other cases, this isnāt so obvious. Contexts allow us a dedicated place to act as a root for a group of related domain concepts without having to decide upfront which model will play this role.
Note, this is totally possible in Rails and OO in general, itās not really promoted by any OO frameworks that I know of.
1 Like