Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActiveRecord: How to include another model based on Enum?

Have a Post Model with an Enum

// Post.rb

enum category: {
   job: 'job',
   conference: 'conference'
}

and an Org has and belongs to many Posts (via join table)

Org.includes(:posts).where(category: Post.categories[:job])

appears that its trying to call category on Org. Is there a way to write this to only return the Org with included post, where the post has an enum of string 'job'?

like image 269
GN. Avatar asked Jun 19 '26 22:06

GN.


1 Answers

tl;dr use

Org.includes(:posts).where(posts: {category: :job})

The longer answer...

I guess it's worth noting that your question doesn't really have anything to do with enums. Nor does it have to do with "including another model". What you're really trying to do is Specify Conditions on the Joined Tables which you can read more about in the Active Record Query Interface guide.

The issues is that you've malformed your ActiveRecord Query:

Org.includes(:posts).where(category: Post.categories[:job])

The essential form of what you currently have is:

Model.where(attribute: 'value')

The:

.includes(:joined_models)

...bit doesn't change the essential form. And so, ActiveRecord will return all Model records where attribute has value. Or, in your case, all Org models where category is job.

But, that's not what you want. You want all Orgs that have Posts where the Post category is job. (Or, I suppose, "all Orgs with job posts.")

Which is where the .includes(:joined_models) bit comes in: it lets you specify conditions on joined_models, which in it's essential form looks like:

Model.includes(:joined_models).where(joined_models: {attribute: 'value'})
                                     ^^^^^^^^^^^^^

Or, in your case:

Org.includes(:posts).where(posts: {category: Post.categories[:job]})

Or, as mu says in the comments:

Org.includes(:posts).where(posts: {category: :job})

Now, I don't know what context you're in, but wherever you are, that above code requires that your context knows a lot about Org and how it relates to Post and the attributes of Post which in general is not great. So, I suggest you add a method to Org which allows you to decouple the knowledge of Org in your context:

class Org < ApplicationRecord 

  class << self

    def with_job_posts
      includes(:posts).where(posts: {category: :job}})
    end

  end

end

And now you can simply do:

Org.with_job_posts

...and get back "all Orgs with job posts". And your context needs to know a lot less about Post and its attributes.

Post also has a category of conference. So, you could do:

class Org < ApplicationRecord 

  class << self

    def with_job_posts
      includes(:posts).where(posts: {category: :job}})
    end

    def with_conference_posts
      includes(:posts).where(posts: {category: :conference}})
    end

  end

end

But, if your Post categories start to grow, that will become tedious. So, instead do:

class Org < ApplicationRecord 

  Post.categories.each do |post_category|
    define_singleton_method("#{post_category}"_posts) do 
      includes(:posts).where(posts: {category: post_category.to_sym})
    end
  end

end

And now you'll have any number of methods like:

Org.with_job_posts
Org.with_conference_posts
Org.with_some_other_type_of_posts

Super! Check out this Q&A for more info from Jörg W Mittag.

BTW, that looks like a potentially unusual way to use enum. In the docs, it says:

Finally, it's also possible to explicitly map the relation between attribute and database integer with a hash:

class Conversation < ActiveRecord::Base
  enum status: { active: 0, archived: 1 }
end

I always figured a mapped enum was meant to use integers as the values, not strings. Interesting.

like image 121
jvillian Avatar answered Jun 22 '26 12:06

jvillian



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!