rails 4 eager loading - order nested associations - ruby-on-rails-4

I have 4 models
class A < ActiveRecord::Base
has_many :Bs
end
class B < ActiveRecord::Base
belongs_to :A
has_many :Cs
has_many :Ds
end
class C < ActiveRecord::Base
belongs_to :B
end
class D < ActiveRecord::Base
belongs_to :B
end
I need to eager load A, and get all nested associations. So from A:
eager_load(Bs: [:Cs, :Ds]).where('id=?',id).to_a
Now I want to order B records through the eager loading. I tried to add the order through the association like this:
class A < ActiveRecord::Base
has_many :Bs, -> { order("id desc") }
end
But it doesn't work. When I do something like
A.first.bs
B records are not ordered by id desc.
Could you please advice on this one?
Thanks.

I haven't tested this, but you could do this to order the Bs by id or any other field you like:
eager_load(Bs: [:Cs, :Ds]).where('id=?',id).order('Bs.id DESC')
The other option would be to setup a default scope on the model, like so:
def self.default_scope
order(id: :desc)
end

Related

How to restrict a has_many relationship to only 1 for a certain sub-type

I have a few models that have a has_many through relationship.
class Participant < ActiveRecord::Base
has_many :participant_scores
has_many :groups, through: :participant_scores
end
class Group < ActiveRecord::Base
has_many :participant_scores
has_many :participants, through: :participant_scores
end
class ParticipantScore < ActiveRecord::Base
belongs_to :group
belongs_to :participant
end
I have the need to have different types of groups, of which a participant can only be a member of 1 of that type.
For example, in a company there multiple departments and locations, but a person can only be associated with one department, and one location. On the other hand, there are generic groupings, that do not have a restriction, for example, there can be many social clubs, and a person could be a member of any number of them.
The groupings are all the same behaviorally, with the only difference being that some have the restriction around being only a member of 1.
I've contemplated using single table inheritance, but I've not figured out how to restrict it to only 1 association.
Parhaps add a type column, and then restrict with validations?
Use a scope?
None of those seem optimal, how can I best accomplish this?
You should use a validation with scope
http://guides.rubyonrails.org/active_record_validations.html#uniqueness

Count on third level associated model

I have the following association:
Reservation
- has_many reservation_occupations
ReservationOccupations
- has_many reservation_occupants
ReservationOccupants
I want to do the following queries:
1 - Get the number of occupants for one reservation
2 - Get the number of occupants for a group of reservations (Reservations.all for example)
Thanks in advance!
1 - Get the number of occupants for one reservation
First, add a has_many :through association from Reservation to ReservationOccupant:
class Reservation < ActiveRecord::Base
has_many :reservation_occupations
has_many :reservation_occupants, through: :reservation_occupations
end
Now you can simply do
reservation = Reservation.first
reservation.reservation_occupants.count
2 - Get the number of occupants for a group of reservations
First, add some more associations:
class ReservationOccupant < ActiveRecord::Base
belongs_to :reservation_occupation
has_one :reservation, through: :reservation_occupation
end
and
class ReservationOccupation < ActiveRecord::Base
belongs_to :reservation
# ...
end
Then, to count the number of occupants for a group of reservations, you can add to your Reservation class the following method:
class Reservation < ActiveRecord::Base
# ...
def self.num_occupants(reservations)
ReservationOccupant
.joins(:reservation_occupation)
.joins(:reservation)
.where("reservations.id": reservations)
.count
end
end
It's worth noting that this num_occupants method works regardless of whether reservations is a collection of reservations or a single reservation. In other words, this method could be used for both of your questions, #1 and #2. However, the first method generates a more efficient SQL query, and is arguably a little clearer, so I'd personally use that when finding the number of occupants for a single reservation.

Rails 4: sort by association attribute

Person has_many :dogs
Dog belongs_to :person
Dog has_many :bones
Bone belongs_to :dog
I want to find the bone associated with each dog a given person owns and sort the results by bone_buried_date
Something like...
bones = []
some_person.dogs.each do |dog|
bones << dog.bones.first
end
bones.sort_by{ |e| e[:bone_buried_date] }
Seems clumsy. Wondering if there is a better way.
try this one
Bone.where(dog: Dog.where(person: some_person)).order(:bone_buried_date)

error on querying nested model attribute

My models:
OrderStatus
belongs_to Order
Order
has_one OrderStatus
belongs_to Logo
Logo
has_many Orders
I would like to perform a query on Logo model attribute named artwork:
OrderStatus.includes({:order => :logo}).where(:order => {:logo => {:artwork => search_artwork}})
but it basically throws an error:
SQLite3::SQLException: no such column: order.logo: SELECT COUNT(DISTINCT "order_statuses"."id") FROM "order_statuses" LEFT OUTER JOIN "orders" ON "orders"."id" = "order_statuses"."order_id" LEFT OUTER JOIN "logos" ON "logos"."id" = "orders"."logo_id" WHERE "order"."logo" = '---
:artwork: xxxxxxx'
I can't see the reason of this error.
EDIT
After extensive searching I realised that where part should be using table names (that is, plurals), so my code should be
#order_statuses = OrderStatus.includes(:order => [:logo]).where(:orders => {:logos => {:artwork => search_artwork}})
but I still see the SQLite3 exception error
SQLite3::SQLException: no such column: orders.logos: SELECT COUNT(DISTINCT "order_statuses"."id") FROM "order_statuses" LEFT OUTER JOIN "orders" ON "orders"."id" = "order_statuses"."order_id" LEFT OUTER JOIN "logos" ON "logos"."id" = "orders"."logo_id" WHERE "orders"."logos" = '---
:artwork: xxxxxxxx
'
So the quick answer to
I still see the SQLite3 exception error ... no such column: orders.logos"
Is that your orders table doesn't have a column named logos, it has a column named logo_id, which was created in your migration you mentioned in your comment.
The reason this error is happening is that Rails doesn't understand (as far as I know) nested values in a .where clause.
Perhaps more importantly, I think what you're (probably) more interested in is:
how do I write logic that will give me all the OrderStatus records related to the Logo with an artwork value equal to search_artwork?
If so, adding a has_one :through relationship to your Logo model will make this really easy!
OrderStatus
belongs_to Order
Order
has_one OrderStatus
belongs_to Logo
Logo
has_many Orders
has_many OrderStatuses, :through => :Order # This is what you're adding!
Then, your query should be as simple as:
Logo.where(artwork: search_artwork).includes(:orderStatus)
EDIT: oops the above is wrong
Logo.where(artwork: search_artwork).order_statuses
For more information on Rails relationships and using has_many :through, see:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

Get attributes of children in a recursive tree structure unless it's a leaf node

I have a recursive tree structure for handling my categories. Each leaf category can have zero or more deals. The categories is defined by
class Category < ActiveRecord::Base
has_many :sub_categories, class_name: "Category", foreign_key: "parent_category_id"
belongs_to :parent_category, class_name: "Category"
has_many :deal_categories
has_many :deals, through: :deal_categories
def leaf?
!has_sub_categories?
end
def has_sub_categories?
!sub_categories.empty?
end
end
Deals and DealCategories looks like follows:
class Deal < ActiveRecord::Base
has_many :deal_categories
has_many :categories, through: :deal_categories
end
class DealCategory < ActiveRecord::Base
belongs_to :deal
belongs_to :category
end
There are also some validations making sure that Deals only can exist as leaf categories. Thus, if I call category.deals on a leaf node I get some deals, and if I call it on a root node I get an empty result. All good.
But now I want category.deals to return the deals of it's children if it's not a root node. My approach has been to override the following method in my Category class as follows:
alias_method :original_deals, :deals
def deals
if leaf?
self.original_deals
else
self.sub_categories.deals
end
end
This however does not work as I can't call deals directly on sub_categories, the error being
undefined method `deals' for #<Category::ActiveRecord_Associations_CollectionProxy:0x00000009243d40>
How do I solve this?
You can't call deals on sub_categories because it is not a category...it is a collection of categories. Instead you could do something like
sub_categories.reduce([]) { |union, sub_category| union + sub_category.deals }
Using reduce creates a memo object (represented by the union variable) and the evaluation of the block becomes the new memo object. I am starting with an empty array and add in the result of calling deals on each sub_category in your sub_categories collection.