how to give query in has_many through association Rails - ruby-on-rails

I have following association
Mobile.rb
has_many :mobile_networks, :dependent => :destroy
has_many :networks, :through => :mobile_networks
Network.rb
has_many :mobiles, :through => :mobile_networks
MobileNetwork.rb
belongs_to :mobile
belongs_to :network
I want to query all mobile_networks of mobile and after that I want to check all network whether it is active or not something like I have written this code in my helper where I am getting mobile
def mobile_state(mobile)
mobile.mobile_networks.each do |e|
e.network.is_checked #true
end
end
So i need to do this in a query. Please guide me how to do this.

You could do the following:
Mobile.joins(:networks).where(id: params[:id]).each do |m|
m.networks.map(&:is_checked?)
end

MobileNetwork.mobiles(params[:id]).joins(:network).merge(Network.checked)
class Network < ActiveRecord::Base
scope :checked -> {
where(is_checked: true)
}
end
class MobileNetwork < ActiveRecord::Base
scope :mobiles, ->(*m) {
where(mobile_id: m.flatten.compact.uniq)
}
end

Related

How to use scopes to join across multiple tables

I am new to ROR and I am trying to understand scopes. In my current implementation I am getting all the Processors and displaying it in the view.
class ProcessorsController
def index
#processors = Processor.all
end
end
I want to modify this so I can get only the processors where the user is admin. This is how my relations are set up.
class Processor
belongs_to :feed
#SCOPES (what I have done so far)
scope :feed, joins(:feed)
scope :groups, joins(:feed => :groups).join(:user).where(:admin => true)
end
class Feed < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :groups
scope :admin, where(:admin => true)
end
I was able to do this in my pry
pry(main)> Processor.find(63).feed.groups.first.user.admin?
PS: could someone provide some good resources where I could learn how to use scopes if the relationships are complex.
scope :with_admin, -> { joins(:feed => { :groups => :user }).where('users.admin' => true) }
As for the resources, have you gone through the official documentation on ActiveRecord joins?
you do not need scopes... you can get only the processors where the user is admin using relations and conditions:
class Feed < ActiveRecord::Base
...
has_one :user, through: :groups
end
class Processor
...
has_one :admin, through: :feed, source: :user, conditions: ['users.admin = 1']
end

Reusing activerecord scopes on has many through associations

Say I have a few activerecord models in my rails 3.1 project that look like this:
class Component < ActiveRecord::Base
has_many :bugs
end
class Bug < ActiveRecord::Base
belongs_to :component
belongs_to :project
scope :open, where(:open => true)
scope :closed, where(:open => false)
end
class Project < ActiveRecord::Base
has_many :bugs
has_many :components_with_bugs, :through => :bugs, :conditions => ["bugs.open = ?", true]
end
In Short: I have a has_many through association (components_with_bugs) where I want to scope the "through" model. At present I'm doing this by duplicating the code for the scope.
Is there any way to define this has many through association (components_with_bugs) such that I can reuse the Bug.open scope on the through model, while still loading the components in a single database query? (I'm imagining something like :conditions => Bug.open)
Rails 4 answer
Given you have:
class Component < ActiveRecord::Base
has_many :bugs
end
class Bug < ActiveRecord::Base
belongs_to :component
belongs_to :project
scope :open, ->{ where( open: true) }
scope :closed, ->{ where( open: false) }
end
You have two possibilities:
class Project < ActiveRecord::Base
has_many :bugs
# you can use an explicitly named scope
has_many :components_with_bugs, -> { merge( Bug.open ) }, through: :bugs, source: 'component'
# or you can define an association extension method
has_many :components, through: :bugs do
def with_open_bugs
merge( Bug.open )
end
end
end
Calling projet.components_with_bugs or project.components.with_open_bugs will fire the same sql query:
SELECT "components".* FROM "components"
INNER JOIN "bugs" ON "components"."id" = "bugs"."component_id"
WHERE "bugs"."project_id" = ? AND "bugs"."open" = 't' [["project_id", 1]]
Which one is better to use depends on your application. But if you need to use many scopes on the same association, I guess association extensions could be clearer.
The real magic is done with merge which allows you to, as the name says, merge conditions of another ActiveRecord::Relation. In this case, it is responsible for adding AND "bugs"."open" = 't' in the sql query.
Apart from your scopes , write the default scope as:
default_scope where(:open => true) in your "through" model Bug.
class Bug < ActiveRecord::Base
belongs_to :component
belongs_to :project
default_scope where(:open => true)
scope :open, where(:open => true)
scope :closed, where(:open => false)
end
And in the Project model remove :conditions => ["bugs.open = ?", true]
class Project < ActiveRecord::Base
has_many :bugs
has_many :components_with_bugs, :through => :bugs
end
I think the above will work for you.
Try using the following.
has_many :components_with_bugs, :through => :bugs do
Bug.open
end
Can't you use something like this ?
has_many :components_with_bugs, :through => :bugs, :conditions => Bug.open.where_values
I haven't tested it, just proposing an path for investigation
The http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html specifies
:conditions Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1.
Hence you can do it as:
class Project < ActiveRecord::Base
has_many :bugs
has_many :components_with_bugs, :through => :bugs do
def open
where("bugs.open = ?", true)
end
end
end
EDIT:
You can't specify another model's scope as a condition. In your case, they way you have it implemented is right. You can implement it another way as
has_many :components_with_bugs, :through => :bugs # in this case, no need to use the relation.
def open_bugs
self.bugs.openn # openn is the scope in bug. Don't use name 'open'. It's a private method of Array.
end

Can I use an extended active record association in a has_many :through declaration?

I've extended many of my has_many declarations to filter / join / preload associations. I'd like to re-use some of these extensions when I declare has_many :through relationships. Is this possible? Should I take a different approach?
Example:
I have this in my Library Model:
class Library < ActiveRecord::Base
has_many :meals, :dependent => :destroy do
def enabled
where(:enabled => true)
end
end
end
My Meal Model has this:
class Meal < ActiveRecord::Base
has_many :servings, :inverse_of => :meal, :dependent => :destroy
end
I'd like my library to have many servings, but only from the enabled meals. There are a couple ways I can do this:
# repeat the condition in the has_many :servings declaration
class Library < ActiveRecord::Base
has_many :servings, :through => :meals, :conditions => ["meals.enabled = ?", true]
end
# declare a different meals association for only the enabled meals
class Library < ActiveRecord::Base
has_many :enabled_meals, :class_name => "Meals", :conditions => [:enabled => true]
has_many :servings, :through => :enabled_meals
end
Is there any way to re-use the extension to my existing :meals declaration? (def enabled in the first code block)
Looks a lot like you want to use activerecord-association-extensions, as described here http://blog.zerosum.org/2007/2/8/activerecord-association-extensions.html.
I haven't tried it, but I think you could do:
module LibraryMealExtensions
def enabled?
where(:enabled=>true)
end
def standard_includes
includes(:servings)
end
end
class Library < ActiveRecord::Base
has_many :meals, :dependent => :destroy, :extend=>LibraryMealExtensions
has_many :servings, :through => :meals, :extend=>LibraryMealExtensions
end
Not sure about the "enabled=>true" there - you might have to say
where("meals.enabled=true")
b/c of confusion with aliases.

Scopes with has many through association

I have 2 models Widget and Feature which have a has many through association using WidgetFeature model.
class Widget < ActiveRecord::Base
has_many :widget_features
has_many :features, :through => :widget_features
end
class WidgetFeature < ActiveRecord::Base
belongs_to :feature
belongs_to :widget
attr_accessible :children_features, :widget_id, :feature_id
end
class WidgetFeature < ActiveRecord::Base
belongs_to :feature
belongs_to :widget
attr_accessible :children_features, :widget_id, :feature_id
end
I have a widget_id.
So i do Widget.find_by_id(widget_id)
Now i want to find all the features for this widget where widget_features.children_features IS NULL.
I dont know how to do this, help me out.
Try
#widget = Widget.find_by_id(widget_id)
#features = #widget.features.conditions("widget_features.children_features IS nil")
EDITED
Ref this
has_many :features, :through => :widget_features, :conditions=>["widget_features.children_features is nil"]
AND then
#widget = Widget.find_by_id(widget_id)
#features = #widget.features
Feature.all(:joins => :widget_features, :conditions => ["widget_id = ? and children_features is null", some_id])
I worked my way around named_scope and found an elegant solution. So, I am posting it here so that others stuck with the same problem can also get help.
My solution gives you a way to access any column of the join model in the has many through association.
Here's the solution for my problem above:
class Widget < ActiveRecord::Base
has_many :widget_features
has_many :features, :through => :widget_features
def leaf_features
widget_features.leaf_features.map{|widget_feature|widget_feature.feature}
end
end
class WidgetFeature < ActiveRecord::Base
named_scope :leaf_features, :conditions => 'children_features IS NULL'
belongs_to :feature
belongs_to :widget
attr_accessible :children_features, :widget_id, :feature_id
end
Now, Widget.find_by_id(widget_id).leaf_features
will give you only those features where
children_features column is NULL.

Rails order by in associated model

I have two models in a has_many relationship such that Log has_many Items. Rails then nicely sets up things like: some_log.items which returns all of the associated items to some_log. If I wanted to order these items based on a different field in the Items model is there a way to do this through a similar construct, or does one have to break down into something like:
Item.find_by_log_id(:all,some_log.id => "some_col DESC")
There are multiple ways to do this:
If you want all calls to that association to be ordered that way, you can specify the ordering when you create the association, as follows:
class Log < ActiveRecord::Base
has_many :items, :order => "some_col DESC"
end
You could also do this with a named_scope, which would allow that ordering to be easily specified any time Item is accessed:
class Item < ActiveRecord::Base
named_scope :ordered, :order => "some_col DESC"
end
class Log < ActiveRecord::Base
has_many :items
end
log.items # uses the default ordering
log.items.ordered # uses the "some_col DESC" ordering
If you always want the items to be ordered in the same way by default, you can use the (new in Rails 2.3) default_scope method, as follows:
class Item < ActiveRecord::Base
default_scope :order => "some_col DESC"
end
rails 4.2.20 syntax requires calling with a block:
class Item < ActiveRecord::Base
default_scope { order('some_col DESC') }
end
This can also be written with an alternate syntax:
default_scope { order(some_col: :desc) }
Either of these should work:
Item.all(:conditions => {:log_id => some_log.id}, :order => "some_col DESC")
some_log.items.all(:order => "some_col DESC")
set default_scope in your model class
class Item < ActiveRecord::Base
default_scope :order => "some_col DESC"
end
This will work
order by direct relationship has_many :model
is answered here by Aaron
order by joined relationship has_many :modelable, through: :model
class Tournament
has_many :games # this is a join table
has_many :teams, through: :games
# order by :name, assuming team has this column
def teams
super.order(:name)
end
end
Tournament.first.teams # are returned ordered by name
For anyone coming across this question using more recent versions of Rails, the second argument to has_many has been an optional scope since Rails 4.0.2. Examples from the docs (see scopes and options examples) include:
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy
As previously answered, you can also pass a block to has_many. "This is useful for adding new finders, creators and other factory-type methods to be used as part of the association." (same reference - see Extensions).
The example given there is:
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
In more modern Rails versions the OP's example could be written:
class Log < ApplicationRecord
has_many :items, -> { order(some_col: :desc) }
end
Keep in mind this has all the downsides of default scopes so you may prefer to add this as a separate method:
class Log < ApplicationRecord
has_many :items
def reverse_chronological_items
self.items.order(date: :desc)
end
end

Resources