How do you sort a collection by distant relationships? - ruby-on-rails

I have a tree-like relationship model with a fixed depth, and each level has a code attribute - similar to this;
class Category < ActiveRecord::Base
has_many :sub_categories
default_scope order(:code)
end
class SubCategory < ActiveRecord::Base
belongs_to :category
has_many :items
def self.sorted
self.joins(:category).order('"categories".code ASC, "sub_categories".code')
end
end
class Item < ActiveRecord::Base
belongs_to :sub_category
def self.sorted
# what goes here?
end
end
Category.all gets all the the categories ordered by categories.code.
SubCategory.sorted gets all the sub_categories ordered by categories.code, sub_categories.code. I used this approach because default_scope : joins(:categories).order('categories.code, sub_categories.code') makes .find return read-only records.
I would like to call Items.sorted and get the all items ordered by categories.code, sub_categories.code, items.code but I can't figure out how. I imagine I need a second .joins, but I don't have a relationship name to supply.

Try this:
class Item < ActiveRecord::Base
belongs_to :sub_category
def self.sorted
# do not need self here as that is implied
joins(sub_category: :category).
order('"categories".code ASC, "sub_categories".code, "items".code')
end
end
See the docs for joining nested assoications here

This works, but it seems like there should be a better way;
def self.sorted
joins(:sub_category).
joins('INNER JOIN "categories" on "categories".id = "sub_categories".category_id').
order('"categories".code ASC, "sub_categories".code ASC, "items".number ASC')
end

Related

Rails - Ordering by the association date

I have the following two models:
class Shelf < ActiveRecord::Model
has_many :wines
def self.order_by_oldest_bottle_of_wine
#TODO: order by the oldest wine bottle...
end
end
class Wine < ActiveRecord::Model
belongs_to :shelf
attr_accessible :produce_date
end
In the shelf model, I want to order shelfs by the oldest wine bottle on the shelf (i.e. shelf with the oldest wine bottle first), but not 100% sure of the implementation.
Many Thanks,
You could do this through a named scope
In your Shelf model you could defined it like so:
named_scope :order_by_oldest_bottle_of_wine, joins: :wines, order: "wines.produce_date DESC"
If you are using Rails 3.x you can use any one of the following
Solution 1:
def self.order_by_oldest_bottle_of_wine
self.wines.order("produce_date DESC")
end
Solution 2: If you want to use scope
class Wine < ActiveRecord::Model
belongs_to :shelf
scope :ordered_by_produce_date, order("produce_date DESC")
attr_accessible :produce_date
end
class Shelf < ActiveRecord::Model
has_many :wines
def self.order_by_oldest_bottle_of_wine
self.wines.ordered_by_produce_date
end
end
def self.order_by_oldest_bottle_of_wine
self.wines.find(:all, :order=>"produce_date DESC")
end

Finding multiple records through has_one association

My Customer and Person models looks like this:
class Customer < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
class Person < ActiveRecord::Base
has_one :customer
end
How can I get all Person records that have an association with a Customer?
with sql it might be something like
Customer.where("customers.person_id IS NOT NULL")
to get Person record you can use join
Person.joins( :customers ).where("customers.person_id IS NOT NULL")
I'm not sue either where is necessary here (I believe no) so try Person.joins( :customers ) first
person_array = []
Person.all.each do |p|
unless p.customer.nil?
person_array << p
end
end
I don't think it's the fastest query but:
Customer.where('person_id IS NOT NULL').map(&:person)
rails 2.3.x
Customer.all(:include => :person).map(&:person).compact

Rails: How can I eager load associations with sorting through instance an method?

class Newsroom < ActiveRecord::Base
has_many :blog_posts
has_many :quote_posts
end
class BlogPost < ActiveRecord::Base
belongs_to :newsroom
end
class QuotePost < ActiveRecord::Base
belongs_to :newsroom
end
I would like to have an instance method, such that I could do #newsroom.posts to get a collection of blog_posts and quote_posts sorted by created_at.
def posts
#posts ||= #load and sort blog_posts, quote_posts, etc
end
What is the best and most efficient way to accomplish this? I have looked into using default_scope, something like:
default_scope :include => [:blog_posts, :quote_posts]
def posts
#posts ||= [blog_posts + quote_posts].flatten.sort{|x,y| x.created_at <=> y.created_at}
end
But I would rather keep the sorting at the database level, if possible. Any suggestions on how to accomplish this? Thanks.
Try something like this:
#app/models/newsroom.rb
scope :ordered_posts, lambda {
includes(:blog_posts,:quote_posts) & BlogPost.order("created_at asc") & QuotePost.order("created_at asc")
}
ARel should be able to handle the ordering of included Quote and Blog Posts. You could clean that up slightly by having scopes in both the BlogPost and QuotePost model that order by created_at and then use those scopes in the Newsroom#ordered_posts method.
I ended up using a polymorphic post model. This seems to give me what I want with the insignificant downside of having an extra model/table. I used delegate to hand off specific attribute getter methods to the correct model.
class Newsroom < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belong_to :blog_post, :polymorphic => true
delegate :title, :author, :etc, :to => :postable
end
class BlogPost < ActiveRecord::Base
has_one :post, :as => :postable
end

ActiveRecord query ordering

Suppose I have the following models:
class Car < ActiveRecord::Base
belongs_to :seat
...
end
class Seat < ActiveRecord::Base
belongs_to :color
...
end
class Color < ActiveRecord::Base
attr_reader :name
...
end
If I have get a list of Cars, and I want to order the Cars by color.name, how to write the order query?
class Car < ActiveRecord::Base
belongs_to :seat
...
def cars_order_by_color(car_ids)
where(:id=>car_ids).order(?????) #HOW TO ORDER BY COLOR.name
end
end
If you use a joins on your query, you can then sort by the joined tables (either seats or colors):
Car.joins(:seat => :color).order("colors.name")
To retrieve records from the database in a specific order, you can specify the :order option to the find call.
Car.order("color")
You could specify ASC or DESC as well:
Car.order("color DESC")
For more help in query look here: active_record_querying
Hope this helps.
Edit
You can use find_by_sql:
Car.find_by_sql("SELECT * FROM clients
INNER JOIN orders ON clients.id = orders.client_id
ORDER clients.created_at desc")
Write appropriate query.

Rails: order using a has_many/belongs_to relationship

I was wondering if it was possible to use the find method to order the results based on a class's has_many relationship with another class. e.g.
# has the columns id, name
class Dog < ActiveRecord::Base
has_many :dog_tags
end
# has the columns id, color, dog_id
class DogTags < ActiveRecord::Base
belongs_to :dog
end
and I would like to do something like this:
#result = DogTag.find(:all, :order => dog.name)
thank you.
In Rails 4 it should be done this way:
#result = DogTag.joins(:dog).order('dogs.name')
or with scope:
class DogTags < ActiveRecord::Base
belongs_to :dog
scope :ordered_by_dog_name, -> { joins(:dog).order('dogs.name') }
end
#result = DogTags.ordered_by_dog_name
The second is easier to mock in tests as controller doesn't have to know about model details.
You need to join the related table to the request.
#result = DogTag.find(:all, :joins => :dog, :order => 'dogs.name')
Note that dogs is plural in the :order statement.

Resources