3
votes

I'm trying to figure out where to place common functions that I would normally (in Rails/ActiveRecord) put in a model class. Specifically, I have User and Company with a many-to-many relationship between them, but a user has a default_company, which just has a boolean flag on the user_companies join table.

ActiveRecord

class User < ActiveRecord::Base
  belongs_to :user_companies
  has_many :companies, through: :user_companies

  def default_company
    # Filter through companies to find the one that I want
  end
end

(Note, there's probably an even easier way to do it, but this is the basic idea.)

Ecto

I could do something similar in Ecto, like so:

defmodule MyApp.User do
  use MyApp.Web, :model

  alias MyApp.{Company, CompaniesUser}

  schema "users" do
    has_many :companies_users, CompaniesUser, on_delete: :delete_all
    many_to_many :companies, Company, join_through: "companies_users"
  end

  def default_company(%User{} = user) do
    from(company in Company,
       join: cu in CompaniesUser,
       where: cu.company_id == company.id
       and cu.user_id == ^user.id
       and cu.default_company == true
    ) |> first() |> Repo.one()
  end
end

However, based on my limited experience, this seems incorrect. All the examples I have seen keep the Ecto model very limited, just a bunch of changeset methods and some validation code, but strictly nothing business related. There is talk of keeping your business logic separate from your database logic. I get that and respect it, but most of the examples show putting raw Ecto queries inside a controller or otherwise sprinkling Ecto queries all over your app, and that seems wrong too.

1

1 Answers

4
votes

Phoenix 1.3

From what I've read about the upcoming 1.3, it looks like the expectation is that this will be handled with Contexts, or specifically, modules that will allow you to logically group your Ecto schema models along with associated modules that define (manually: you define it) an API to access your persistence layer. So, using my above example, it would be something like:

defmodule MyApp.Account do

  alias MyApp.Account.User
  alias MyApp.Corporate.{Company, CompaniesUser}

  def default_company(%User{} = user) do
    from(company in Company,
       join: cu in CompaniesUser,
       where: cu.company_id == company.id
       and cu.user_id == ^user.id
       and cu.default_company == true
    ) |> first() |> Repo.one()
  end
end

defmodule MyApp.Account.User do
  use MyApp.Web, :model

  alias MyApp.Corporate.{Company, CompaniesUser}

  schema "users" do
    has_many :companies_users, CompaniesUser, on_delete: :delete_all
    many_to_many :companies, Company, join_through: "companies_users"
  end
end

It has 2 modules, one (MyApp.Account.User) is my raw Ecto schema. The other (MyApp.Account) is the API/entry point for all the other logic in my app, like the controllers.

I guess I like the theory, but I'm worried about trying to figure out what models should go where, like in this example: Does Company belong in the Account context, or do I make a new Corporate context?

(Sorry for asking/answering my own question, but in researching the question I found the info for Phoenix 1.3 and thought I might as well just post for anyone who is interested.)