Where to put basic Ecto Queries

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. :slight_smile:
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 :stuck_out_tongue: thanks!

import just brings another modules public functions into the ā€˜localā€™ scope. :slight_smile:
To re-export to the outer scope you need to defdelegate each function that you want to re-export. :slight_smile:

3 Likes

thanks! havent heard of defdelegate before will check it out. elixir/phoenix community rocks! :stuck_out_tongue:

Is it better to do the defdelegate in this case or just put the shared basic queries in Repo?

Explicit is better than implicit. :slight_smile:

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

thanks! =)

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ā€™ scopes. 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