Can I define a query in Rails model? - ruby-on-rails

Here's what I have:
module EventDependencyProperties
def start_date
shows.order('show_date ASC').first.show_date
end
def end_date
shows.order('show_date DESC').first.show_date
end
def is_future_show?
end_date >= Date.today
end
end
class Event < ActiveRecord::Base
include EventDependencyProperties
has_many :shows
has_and_belongs_to_many :users
end
class Show < ActiveRecord::Base
belongs_to :event
end
I have bits of code elsewhere using the is_future_show? method. What I would like to do is have a method in the module mixin to return "future shows" using a query that has the same criteria as the is_future_show? method. How would I go about achieving this? I'm very much a newbie to Rails but tainted by knowledge of other languages and frameworks.
Cheers,
Dany.

You can put the query into a scope:
class Show < ActiveRecord::Base
scope :future, lambda { where("show_date > ?", Date.today) }
end
Call it like this:
my_event.shows.future
Edit: Ah I see. To return all events with a show in the future:
Event.joins(:shows).where("shows.show_date > ?", Date.today)
agains this can be scoped:
class Event
scope :future, lambda { joins(:shows).where("shows.show_date > ?", Date.today) }
end
On a side note, I'm not sure about the setup of your models, especially the use of the mixin. Here's what I do:
class Show < ActiveRecord::Base
belongs_to :event
# use default_scope so shows are ordered by date by default
default_scope order("show_date ASC")
end
class Event < ActiveRecord::Base
has_many :shows
has_and_belongs_to_many :users
scope :future, lambda { joins(:shows).where("shows.show_date > ?", Date.today) }
def start_date
shows.first.show_date
end
def end_date
shows.last.show_date
end
def ends_in_future?
end_date > Date.today
end
end
also it would be cleaner if the show_date column for the Show model was just called date (so you could just write show.date rather that show.show_date).

Related

Caching association that was in where clause

Let me show an example:
I have 2 models:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
scope :created_in, ->(start_date, end_date) { where(created_at: start_date..end_date) }
end
What I want is to get users that created post during a specific period:
users = User.includes(:posts).joins(:posts).merge(Post.created_in(start_date, end_date))
Is it somehow possible to cache posts that are in the where clause? So after I do
users.first.posts
it will show me exactly those posts that match the condition without producing any additional queries.
No, I don't think this is possible. Depending on the context, what you can do is to do a lookup table which you memoize / cache. Something like
User.all.each do |user|
posts = posts_by_user_id[user.id]
end
def posts_by_user_id
#_posts_by_user_id ||= posts.group_by(&:user_id)
end
def posts
Post.created_in(start_date, end_date)
end

Rails and Postgres: Finding items that are a "good deal"

I have two models in my Rails application which keeps track of the prices of products at different shops. Here they are, but simplified:
class Product < ActiveRecord::Base
attr_accessor :name
def latest_prices
prices.where('created_at >= ?', 30.days.ago)
end
def average_price
latest_prices.prices.map(&:value).sum / latest_prices.count
end
end
class Price < ActiveRecord::Base
attr_accessor :value, :shop_name, :created_at
belongs_to :product
end
I now want to find all Price objects which fall below the current average for that product. That basically means all Prices created in the last 30 days, that have a price below the recent average price for that Product.
Is this possible? I'm using Postgres.
Edit: I should have mentioned - I want to implement this method from the Price model - that is, just be able to display all prices that are a good deal, rather than all prices for a Product that are good deals.
Thanks in advance for any help!
Using named scopes in ActiveRecord, you can use composition to get what you want:
class Product < ActiveRecord::Base
attr_accessor :name
has_many :prices
end
class Price < ActiveRecord::Base
attr_accessor :value, :shop_name, :created_at
belongs_to :product
scope :latest, where('created_at >= ?', 30.days.ago)
scope :less_than, lambda { |value| where("value < ?", value) }
def good_deals
latest.less_than(average('value'))
end
end
try this:
class Product < ActiveRecord::Base
attr_accessor :name
def your_query
prices.where('created_at >= ?', 30.days.ago).where('value < ?', average_price)
end
def latest_prices
prices.where('created_at >= ?', 30.days.ago)
end
def average_price
latest_prices.prices.map(&:value).sum / latest_prices.count
end
end

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

Rails 3 joining a related model with it's default scope

I'm trying to query across models with the following setup
Class Scorecard < AR::Base
default_scope where(:archived => false)
belongs_to :user
has_many :scorecard_metrics
end
Class ScorecardMetric < AR::Base
belongs_to :scorecard
end
Class User < AR::Base
has_many :scorecards
end
I am trying to query from scorecard metrics with a named scope that joins scorecard and I want it to include the default scope for scorecard, my current implementation (which works) looks like this
# on ScorecardMetric
scope :for_user, lambda {
|user| joins(:scorecard).
where("scorecards.user_id = ? and scorecards.archived = ?", user.id, false)
}
This just seems messy to me, is there any way to join and include the default scope of the joined association?
Looks like I found the answer I was looking for, I just did this
scope :for_user, lambda { |user| joins(:scorecard).where('scorecards.user_id = ?', user.id) & Scorecard.scoped }
which is much nicer without the duplicated logic

Best Practice to abstract ActiveRecord model queries in Rails?

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)

Resources