2

I would like to use the includes method with the related element of my Post

My Post can be associated with different type of element. And I use a value :cat to knows witch kind of element is associated.

The value work as this (cat: (1 => Message, 2=>Question, 3=>Task, 4=>Event) with the association has_one

Example : If post.cat == 3, I can call the task related with a method post.task

Now, I would like to optimize the SQL requests of my Post/Index with the method includes. But is not working for the moment. Can you help me to find the error of my code ?

Post_controller :

def index
  @posts = current_user.posts
  @posts.each do |post|
    if post.cat == 3
      @task = post.task.includes(:users)
    elsif post.cat ==  4
      @event = post.event.includes(:reminds)
    end
  end
end

Error: undefined method `includes'

Edit :

Post_model:

class Post < ApplicationRecord
  has_one :post_message, dependent: :destroy
  has_one :question, dependent: :destroy
  has_one :task, dependent: :destroy
  has_one :event, dependent: :destroy
end

Task_model :

class Task < ApplicationRecord
  belongs_to :post
  has_many :users_task, dependent: :destroy
  has_many :users, through: :users_task
end
stig Garet
  • 564
  • 2
  • 13
  • Provide your `Post` model code, please – AntonTkachov Nov 08 '17 at 14:54
  • yep, i'm on it ! – stig Garet Nov 08 '17 at 14:55
  • in your context, looping through the `@posts`'s content and assigning the same variables (`@task` or `@event`) will overwrite it. `@task` will be equal to the last `post.task` that was executed. Can you describe what you are trying to achieve? Are you trying to display the list of each post's task+users or task+reminds (depending on the post's cat) ? – MrYoshiji Nov 08 '17 at 15:04
  • Also, `post.task` and `post.event` return only 1 record, so you can't includes a relation on a single element because accessing this relation's records will always trigger 1 query (`select * from tasks where post_id = ?`) – MrYoshiji Nov 08 '17 at 15:05
  • Thanks for your answer @MrYoshiji, I'm trying to load the element of the post in one request. But I don't want to search task, event, etc... for each post. So I would like to say if the post.cat == 3 {include the related task} As this I can reduce the number of SQL requests on my page – stig Garet Nov 08 '17 at 15:09
  • @stigGaret 'I already know the polymorphic association is not the best way to my situation' Why do you think so? I am sure it is – AntonTkachov Nov 08 '17 at 15:11
  • Polymorphic associations are quite difficult to handle for optimization (can't preload the relation, for example). – MrYoshiji Nov 08 '17 at 15:14
  • Yep, I was responding the same thing, thanks @MrYoshiji to answer him. By the way, do you know a good way to solve this kind of preloading ? – stig Garet Nov 08 '17 at 15:19
  • You can't preload a relation for a single record. On the other hand, you can preload the relation(s) from an `ActiveRecord::Relation`, something like `current_user.posts.includes(task: :users, event: :reminds)`. – MrYoshiji Nov 08 '17 at 15:22
  • I was running this before, but it's searching task, event, etc.. for each post. And that's a lot of loading for one post. Because I simplified my example, but in my case, I have a lot more association to load. Thanks anyway ! – stig Garet Nov 08 '17 at 15:38
  • I think that's also a good point to have a look: https://stackoverflow.com/questions/2390017/ruby-on-rails-include-on-a-polymorphic-association-with-submodels. About includes with polymorphic – AntonTkachov Nov 08 '17 at 16:00
  • Please share your final solution with us ;) – AntonTkachov Nov 08 '17 at 16:00

3 Answers3

2

Why are you using @posts.each ?

For me, the best solution for that is to find all the posts whith the defined cat to run the includes method. In your case, it would be like that :

@posts.where(cat: 1).includes(:message)
@posts.where(cat: 2).includes(:question)
@posts.where(cat: 3).includes(task: :users)
@posts.where(cat: 4).includes(event: :reminds)
jacques Mesnet
  • 607
  • 3
  • 10
2

Well, after many tries, I opted for a scope method to run the includes method. It's not a really elegant solution, but I think it's the best in my case.

So I'm preparing the scopes in my Post_Model:

scope :with_tasks, -> { where(cat: 3).includes(:user).includes(task: :users) }
scope :with_events, -> { where(cat: 4).includes(:user).includes(event: :reminds) }

And after, I render them in my index action like this :

@posts = current_user.posts.with_tasks + current_user.posts.with_events

So the code is generating 2 SQL Requests to find the posts (one for each category).

I think there is a way to join all that directly into a new global scope, but I don't know how. So if there is anyone knows that, he can edit the answer

Enjoy !

stig Garet
  • 564
  • 2
  • 13
-1

If you're getting an undefined method: 'includes' error, it means that either post.task or post.event are not returning ActiveRecord objects like your code is expecting. Are you sure there will always be values set for .task or .event at that point in execution? Are there any cases where that value might be nil or blank?

By the way, have you heard about 'polymorphic associations'? Defining an association as polymorphic allows you to associate records of arbitrary types with a specific column (by storing both object ID and class name on each record behind the scenes). It seems like this exactly matches your use case. It would be much easier to use the built-in mechanism than trying to do all the if-then switching based on category in your code.

jk_
  • 755
  • 5
  • 4
  • Thanks for your answer @jk_, no there is no cases where the value can be nil or blank. And thanks for your suggestion I already know the polymorphic association is not the best way to my situation. Thanks anyway – stig Garet Nov 08 '17 at 14:55