Rails sorting child objects and displaying - ruby-on-rails

Relating to my last question here: Rails: Finding all associated objects to a parent object
Is it possible to sort multiple separate child objects in Rails by creation date, and then list them? Using the previous example I have a resume with two different has_many child objects, I would like to fetch them and then sort them based on creation date and then use that to display them.

I assume that you have two (or more) seperate models for children objects, so your Parent model looks like this:
class Parent < ActiveRecord::Base
has_many :dogs
has_many :cats
end
To sort them and get them generally as children you can write method (similar to #gertas answer):
def children
#children ||= (self.dogs.all + self.cats.all).sort(&:created_at)
end
and put it in Parent model. Then you can use it in controller:
#parent = Parent.find(params[:id])
#children = #parent.children
Now we'll try to display them in a view. I assume that you have created two partials for each model _cat.html.erb and _dog.html.erb. In view:
<h1>Children list:</h1>
<% #parent.children.each do |child| %>
<%= render child %>
<% end %>
It should automaticaly find which partial should be used, but it can be used only if you follow Rails way. If you want to name partials in different way, or store it in different directory, then you would have to write your own methods that will choose correct partial based on type od object.

You can add an accessor method on your parent model:
class Parent < ActiveRecord::Base
def sorted_children
children.scoped( :order => 'created_at DESC' )
# or, in rails3:
# children.order('created_at DESC')
end
end
If the natural order for your child model is the date field and you would like to do that everywhere, then just set a default scope on it:
class Child < ActiveRecord::Base
default_scope :order => 'created_at DESC'
end

As child objects are in different types and they are fetched separately (separate has_many) you have to do sorting in Ruby:
sorted_childs=(#resume.child1_sections.all + #resume.child2_sections.all).sort(&:created_at)
Otherwise you would need to introduce table inheritance with common columns in parent. Then it would be possible to have another has_many for all children with :order.

Related

Ruby on Rails API: computed attribute pattern best practice

I would like to know the best practice when I need a computed attribute that require a call to the database.
If I have a Parent that has many Child, how would I render a children_count attribute in ParentController#index as I don't want to render the children, just the count? what's the best way to do it?
Thank you!
Model:
class Parent < ApplicationRecord
has_many :children
def children_count
children.count # Wouldn't it ask the database when I call this method?
end
end
Controller:
class ParentsController < ApplicationController
def index
parents = Parent.all
render json: parents, only: %i[attr1, attr2] # How do I pass children_count?
end
end
The Rails way to avoid additional database queries in a case like this would be to implement a counter cache.
To do so change
belongs_to :parent
in child.rb to
belongs_to :parent, counter_cache: true
And add an integer column named children_count to your parents database table. When there are already records in your database then you should run something like
Parent.ids.each { |id| Parent.reset_counters(id) }
to fill the children_count with the correct number of existing records (for example in the migration in which you add the new column).
Once these preparations are done, Rails will take care of incrementing and decrementing the count automatically when you add or remove children.
Because the children_count database column is handled like all other attributes you must remove your custom children_count method from your Parent class and can still simple call
<%= parent.children_count %>
in your views. Or you can add it to the list of attributes you want to return as JSON:
render json: parents, only: %i[attr1 attr2 children_count]
children.count will call the database, yes; however, it will do it as a SQL count:
SELECT COUNT(*) FROM "children" WHERE "children"."parent_id" = $1
It doesn't actually load all of the child records. A more efficient method is to use a Rails counter_cache for this specific case: https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache

Put model associations calls from view to controller

How can I put model associations calls from views to controllers in Rails 4 or later?
For example I have:
Model:
class Parent
has_many :children
end
class Child
end
Controller:
class ParentController < ApplicationController
def index
#parents = Parent.all
end
end
class ChildrenController < ApplicationController
def index
#children = Parent.find(params[:parent_id]).children
end
end
View:
parents/index.html.erb
<% #parents.each do |parent| %>
<% render parent.children %>
<% end %>
some partial children/_child.html.erb
<%= child.name %>
route.rb
resources :parents do
resources :children
end
How can I substitute call of method parent.children by using somehow logic in controller - for example, ChildrenController::index method that requires url parameter?
I believe this will allow me to abstract view from model.
By abstraction I mean - if i change the model - for example, it won't have has_many association between parent and child - but still want to preserve the view then i will have only change the code in controller not in the view.
Further, i can extract the model logic in controller to some business class that will act like a model interface to use.
I am trying to build two-layered architecture with separated Presentation Layer (views, controllers) and Business layer (business classes, activerecords).
Probably this architecture could avoid the cases like with futurelearn portal that decided to split their STI model (single table) to several different ones. But having no real separation it became a creative task of tricking the Rails (see https://about.futurelearn.com/blog/refactoring-rails-sti). Code had a lot of association calls and it was necessary to develop smth to preserve these calls (only because otherwise it would be necessary to change too much code) and change model at the same time.
Not sure what you mean by
I believe this will allow me to abstract view from model.
You are not using model in the view per se. You are using the ActiveRecord associations in the view which I think is the right way to do this sort of thing. Not sure if rendering all the children for each parent is the right thing to do as far as reading all those records in memory is concerned but that's a whole different topic.
FWIW, both parent.children and Parent.find(params[:parent_id]).children will generate the exact same SQL on the backend.
That is ok, if you have such a call in your view, i believe that there is no good way to remove it. The only thing you can do - you can try to hide this call, but this will not bring you real abstraction.
But there are some things, that you need to do:
1. Add includes to load your associations in a single query
def index
#parents = Parent.includes(:children).all
end
2. If you don't need your parents in your view (it's not clear - do you only render children partial, or there are other lines in that view?), only children - you can just load your childrens :)
#children = Children.all
# or, if your children can be without parents
#children = Children.where.not(parent_id: nil).all
I think you missed put belongs_to:
class Parent
has_many :children
end
class Child
belongs_to :parent
end

Rails 4 - find nested attributes, select certain child

I have and object with a nested model. I am currently getting all the nested objects like so:
#no = Parent.find(params[:parent_id]).children
Now, one of these children has an attribute that identifies them as the favorite. How can I get the favorite child from among the children?
In addition, how can I edit the attributes using fields_for for just that single object in the view/update?
I don't know the name of your attribute that identifies the record as the favorite, but let's say it is a boolean named is_favorite. Considering this abose, the following should work:
children = Parent.find(params[:parent_id]).children
#favorited_children = children.where(is_favorite: true) # return 0..N records! not only 0..1 !
To edit its attributes, you can do as following (you will have to translate it in ERB or HAML, depending on what your app uses):
form_for #favorited_children do |form_builder|
form_builder.text_field :name
form_builder.check_box :is_favorite
end
Hope this helps!
You could also look at using an ActiveRecord Association Extension
This basically works by creating instance methods you can chain onto the child association, like so:
#app/models/parent.rb
Class Parent < ActiveRecord::Base
has_many :children do
def favorites
where(is_favorite: true) #-> to use MrYoshi's example
end
end
end
This will allow you to use the following:
#parent = Parent.find params[:id]
#favorites = #parent.children.favorites

How to eager loading when there are further conditions on association objects?

I am using Ruby on Rails 3.2.2 and I have the following has_many :through association in order to "order articles in categories":
class Article < ActiveRecord::Base
has_many :category_associations # Association objects
has_many :associated_categories, :through => :category_associations # Associated objects
end
class CategoryAssociation < ActiveRecord::Base
acts_as_list :scope => 'category_id = #{category_id} AND creator_user_id = #{creator_user_id}'
belongs_to :associated_article
belongs_to :creator_user, :foreign_key => 'creator_user_id'
end
On retrieving associated_categories I would like to load category_associations objects created by a user (note: the creator user is identified by the creator_user_id column present in the category_associations database table) because I need to display position values (note: the position attribute, an Integer, is required by the act_as_list gem and it is a column present in the category_associations database table) "near" each article title.
Practically speaking, in my view I would like to make something like the following in a proper and performant way (note: It is assumed that each article in #articles is "category-associated" by a user - the user refers to the mentioned creator user of category_associations):
<% #articles.each do |article| %>
<%= link_to(article.title, article_path(article)) %> (<%= # Display the article position in the given category %>)
<% end %>
Probably, I should "create" and "handle" a custom data structure (or, maybe, I should make some else...), but I do not how to proceed to accomplish what I am looking for.
At this time I am thinking that the eager loading is a good approach for my case because I could avoid the N + 1 queries problem since I have to state further conditions on association objects in order to:
retrieve specific attribute values (in my case those refer to position values) of association objects created by a given user;
"relate" (in some way, so that position values are suitable for displaing) each of those specific attribute values to the corresponding associated object.
I think, you are looking for this
#articles = Article.includes(:associated_categories)
This will eager load all your articles including both of its associations (associated_categories, associated_categories). Thus, it will avoid N+1 problem and wont fire queries when you iterate over #articles and its associations in your view.

eager loading association on a subclass

I have the following (simplified) class hierarchy:
def Parent < ActiveRecord::Base end
def Child < Parent
belongs_to :other
end
def Other < ActiveRecord::Base end
I want to get all Parent objects and -if they are Child objects- have them eager load the :other association. So I had hoped I could do:
Parent.find(:all, :include => [:other])
But as I feared, I get the message: "Association named 'other' was not found; perhaps you misspelled it?"
What is the best way to establish eager loading in this scenario?
[Edit]
As requested, here's the more concrete example:
Parent = Event
Child = PostEvent
Other = Post
I want to log different types of events, all with their own properties (some of which are references to other objects), like the above example. At the same time I want to be able to list all Events that occurred, hence the parent class.
Can you define the belongs_to :other association in the Parent model? It won't be relevant to every Parent object, but that's the nature of STI: you will almost always have some column that's not used by every child.
If you really can't move the association to the parent, you may have to load in two steps, for example:
Parent.find(:all, :conditions => "type != 'Child'") +
Child.find(:all, :include => [:other])
Since Child inherits from Parent (and not the other way around), Parent has no knowledge of the belongs_to :other association.
I think you need to reconsider how you're modeling your app. Perhaps some specifics on your actual models would raise some answers on alternative methods of what you're trying to accomplish.

Resources