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
Related
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
This is my scenario:
class User < ActiveRecord::Base
has_many :things
# attr_accessible :average_rating
end
class Thing < ActiveRecord::Base
belongs_to :user
has_one :thing_rating
end
class ThingRating < ActiveRecord::Base
belongs_to :thing
attr_accessible :rating
end
I want to have an attribute in my User model which has the average calculation of his related ThingsRating.
What would be the best practice to manage this?
Thanks
May be you can use relation not sure but you can try this
class User < ActiveRecord::Base
has_many :things
has_many :thing_ratings, through: :things
# attr_accessible :average_rating
def avg_rating
#avg_rating ||= thing_ratings.average("thing_ratings.rating")
end
end
The easy way :
class User < ActiveRecord::Base
has_many :things
def avg_rating
#avg_rating ||= average(things.map(&:thing_rating))
end
private
def average(values)
values.inject(0.0) { |sum, el| sum + el } / arr.size
end
end
This is fine as a starter. But if you have a bit of trafic, you might find yourself with scaling problems.
You'll then have to refactor this to avoid making an SQL query to the things every time you call the method for a different user.
You could then have several possibilites :
Add a field in your User database, avg_rating, which would be updated by the ThingRating when it's created or updated.
Use a memcached or redis database to cache the value and invalidate the cache every time a ThingRating is updated or created.
These solutions aren't exhaustive of course. And you could find other ones which would better fit your needs.
I want to grab all the categories that contain purchaseable products.
class Product < ActiveRecord::Base
belongs_to :category
scope :purchaseable, where(:available => true)
end
class Category < ActiveRecord::Base
has_many :products
scope :with_purchaseable_products, ?????
end
So, I'm trying to define :with_purchaseable_products. This works:
scope :with_purchaseable_products, joins(:products).where("products.available is true").group(:id).having('count(products.id) > 0')
But that's not very DRY. Is there any way to apply my :purchaseable scope to products in my :with_purchaseable_products scope?
Thanks.
You should use the merge method
class Category < ActiveRecord::Base
has_many :products
scope :with_purchaseable_products, joins(:products).merge(Product.purchaseable).group(:id).having('count(products.id) > 0')
end
Read more on http://asciicasts.com/episodes/215-advanced-queries-in-rails-3
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
I'd like to extract out logic from the controllers to somewhere that it can be more DRY. What's the best way of handling something like the following in Rails?
For instance, as opposed to having the query in the controllers, I could place it in the model:
class SmoothieBlender < ActiveRecord::Base
belongs_to :user
def self.get_blenders_for_user(user)
self.where(["user_id = ?", user.id])
end
end
Or would it be better to create a module as a service layer and include that in each model that uses it?
module BlenderUser
def get_blenders_for_user(user)
SmoothieBlender.where(["user_id = ?", user.id])
end
end
class SmoothieBlender < ActiveRecord::Base
include BlenderUser
belongs_to :user
end
class User < ActiveRecord::Base
include BlenderUser
has_many :smoothie_blenders
end
Or just make it a full blown service class that's accessible from the User and Blender controller? Where would you put this class?
class BlenderService
def self.get_blenders_for_user(user)
SmoothieBlender.where(["user_id = ?", user.id])
end
end
I'm new to Ruby and Rails, so if this is a silly question/syntax is incorrect, forgive me. Thanks in advance!
I'd create a named_scope (I think it's just scope in Rails 3)
class SmoothieBlender < ActiveRecord::Base
belongs_to :user
scope :for_user, lambda { |user_id|
where("user_id = ?", user_id)
}
end
This way you can call
SmoothieBlender.for_user(user.id)