Is there a better way to find associations on a class level? - ruby-on-rails

Here are the objects I am working with:
class Client < ActiveRecord::Base
has_many :servers
def self.active
where("updated_at > ?", 1.month.ago)
end
end
class Server
belongs_to :client
end
I would like to be able to get all the servers that belong to active clients like so:
Client.active.servers
The only way I can think to do this is:
def Client.servers
Server.where(id: all.collect(&:id))
end
There must be a more Rails-y to do this!

I believe I've found what you're looking for, and it's the Active Record method merge.
Server.joins(:client).merge(Client.active)
In testing, if you find a conflict with your updated_at column, be sure to disambiguate it in your active scope:
def self.active
where("clients.updated_at > ?", 1.month.ago)
end

You want to join to the client from server. Something like this should work:
Server.joins(:client).where('clients.updated_at > ?', 1.month.ago)

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 searching a belongs_to relationship

In my application I have a Property and Customers model. Property has_one :customer and Customer belongs_to :property. Property has an address column of type string.
I am trying to allow users to search Customers by the address of the property it belongs to.
# customer.rb
class Customer < ActiveRecord::Base
belongs_to: property
def self.search(search, user)
if search
where('full_name LIKE ?', "%#{search}%").where(user: user)
else
where(user: user)
end
end
end
Doing this doesn't work:
def self.search(search, user)
if search
where('full_name LIKE ? OR property.address LIKE ?', "%#{search}%", "%#{search}%").where(user: user)
else
where(user: user)
end
end
What is the best way to accomplish this?
You need to use a "join."
def self.search(search, user)
if search
joins(:property).where('properties.address LIKE ?', "%#{search}%").where(user: user)
else
where(user: user)
end
end
In SQL terminology this is called an "inner join."
Here is the Rails Guide on joining tables.

Creating a named scope that includes an associated model

The model Organizer has_many events.
Event has attributes begin_day:date and published:boolean.
I have the following query for events that haven't ocurred yet:
#organizer.events.order('begin_day asc').where('begin_day >= ?', Date.today).where(published: true).limit(8)
which I would like to extraxt to a scope such that it's implemented something like this:
#organizer.upcoming_events.limit(8)
How do I do create this scope though that includes an associated model?
Try smth like this:
scope :upcoming_events, -> { joins(:events).where("events.begin_day >= ?", Date.today).where(events: {published: true}).order('events.begin_day asc') }
The has_many and belongs_to helpers automatically create fields and scopes that easily allow you to join two models, in a user defined scope you have to join those models manually. ;)
class Event
belongs_to :organizer
end
class Organizer
has_many :events
scope :upcoming_events, joins(:events).order('begin_day asc').where('begin_day >= ?', Date.today).where(published: true)
# lol007's query
scope :upcoming_events, -> { joins(:events).where("events.begin_day >= ?", Date.today).where(events: {published: true}).order('events.begin_day asc') }
end
end
Reading #TheChamp's reasoning in his answer, it seems like just doing a method pasting in the query part works equally well. Chainable as well.
class Event
belongs_to :organizer
end
class Organizer
has_many :events
def upcoming_events
self.events.order('begin_day asc').where('begin_day >= ?', Date.today).where(published: true)
end
end
end
This works now:
#organizer.upcoming_events.limit(8)

Rails 3: Retrieve all child records where parent model attribute equals search key

I want to do a query that returns only the assets that do not have a serial number where the workorder branch equals a number.
class Workorder < ActiveRecord::Base
belongs_to :user
has_many :assets
scope :current_branch, where("branch=350").order("wo_date ASC")
end
class Asset < ActiveRecord::Base
belongs_to :workorder
scope :needs_serial, :conditions => {:serial => ""}
end
class AssetsController < ApplicationController
def index
#assets_needing_serial=???
end
end
So I want a hash of :assets where the assets.workorder.branch="350". I think I could do a loop and create the hash that way but should I be able to do this in a query? Should I be trying to use scopes for this?
**Update
This is what I ended up using. Worked great.
#assets = Asset.joins(:workorder).where('workorders.branch=350').order('workorders.wo_date ASC')
The query you would want to do is
Asset.joins(:workorder).where('workorders.branch = 325')
So you can make a scope like this:
scope :with_workorder_branch, lambda { |branch| joins(:workorder).where('workorders.branch = ?', branch) }
If you're going to be looping through the workorders, you should change the joins to includes as this eager loads them.
The rails guide to queries is very helpful for this sort of thing http://guides.rubyonrails.org/active_record_querying.html

Can I define a query in Rails model?

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).

Resources