How to use scopes to join across multiple tables - ruby-on-rails

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

Related

Rails: alternatives to using nested loops to find match in controller

There has to be a better way to do this. My Favorite model belongs to User while Applicant belongs to both Gig and User. I am trying to efficiently determine whether a user has applied for Gig that was favorited (<% if #application.present? %>).
I tried chaining the collection by using something like #favorites.each.gig to no avail. While the below index action for Favorites seems to work, it's really verbose and inefficient. What is a more succinct way of doing this?
def index
#favorites = Favorite.where(:candidate_id => current_candidate)
#applications = Applicant.where(:candidate_id => current_candidate)
#favorites.each do |favorite|
#applications.each do |application|
if favorite.gig.id == application.id
#application = application
end
end
end
end
class User
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :candidate
belongs_to :gig
end
class Applicant < ActiveRecord::Base
belongs_to :gig
belongs_to :candidate
end
class Candidate < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Gig < ActiveRecord::Base
belongs_to :employer
has_many :applicants
has_many :favorites
has_many :users, :through => :applicants
end
For lack of a better answer, here's my idea:
--
User
Your user model should be structured as such (I just highlighted foreign keys, which I imagine you'd have anyway):
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants, foreign_key: "candidate_id"
has_many :favorites, foreign_key: "candidate_id"
end
This means you'll be able to call:
current_candidate.favorites
current_candidate.applicants
This will remove the need for your #applications and #favorites queries
--
Favorite
You basically want to return a boolean of whether applicant is part of the favorite model or not. In essence, for each favorite the candidate has made, you'll be able to check if it's got an application
I would do this by setting an instance method on your favorites method using an ActiveRecord Association Extension, like so:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :favorites do
def applied?
self.applicant.exists? proxy_association.owner.gig.id
end
end
end
This will allow you to call:
<%= for favorite in current_candidate.favorites do %>
<%= if favorite.applied? %>
<% end %>
This is untested & highly speculative. I hope it gives you some ideas, though!

Associations, Joins and Scopes

I have the following setup:
class Program
has_many :participants
end
class Participant
belongs_to :user
end
class User
has_many :participants
end
I want class method or scope to return all the programs in which a certain user participates. Here's what I have so far:
def self.where_user_participates(user)
Program.joins(:participants).where('participants.user_id' => user.id)
end
I believe that works but I am not in love with it. I prefer not to talk about 'id's but use the associations, but I could not get it to work, e.g.:
def self.where_user_participates(user)
Program.joins(:participants).where('participants.user' => user)
end
How can I improve this? And is it true that official 'scope's are not needed and a class method is 'best practice' in Rails 3?
class Program
has_many :participants
end
class Participant
belongs_to :user
belongs_to :program
end
class User
has_many :participants
has_many :programs, :through => :participants
end
Then to get the programs call:
user.programs

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

How to combine two has_many associations into one?

This seems to be a fairly common problem over here, yet there is no definitive solution. To restate once again, say I have a model:
def Model < ActiveRecord::Base
has_many :somethings, ...
has_many :otherthings, ...
end
The question is then how to add a third association :combined that combines the two? I know this can be done with :finder_sql and similar result can be achieved with a scope, but neither of these gives me an actual association. The whole point of this is to be able to use it for another association with :through and things like Model.first.combined.some_scope.count
EDIT: the relevant portions of the actual code
class Donation < ActiveRecord::Base
# either Project or Nonprofit
belongs_to :donatable, :polymorphic => true
belongs_to :account
end
class Project < ActiveRecord::Base
belongs_to :nonprofit
end
class Nonprofit < ActiveRecord::Base
has_many :projects
# donations can be either direct or through a project
# the next two associations work fine on their own
# has_many :donations, :as => :donatable, :through => :projects
# has_many :donations, :as => :donatable
has_many :donations, .... # how do I get both here,
has_many :supporters, :through => :donations # for this to work?
end
Thanks.
If Something and Otherthing are sufficiently similar, use STI:
def Model < ActiveRecord::Base
has_many :somethings
has_many :otherthings
has_many :genericthings
end
def Genericthing < Activerecord::Base
# put a string column named "type" in the table
belongs_to :model
end
def Something < Genericthing
end
def Otherthing < Genericthing
end

Rails has_many :through and has_one :through associations

First I'm using Rails 3.1 from the 3-1-stable branch updated an hour ago.
I'm developing an application where I have 3 essential models User, Company and Job, Here's the relevant part of the models:
class User < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :companies, :through => :companies_users, :source => :company
end
class Company < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :employees, :through => :companies_users, :source => :user
has_many :jobs, :dependent => :destroy
end
class Job < ActiveRecord::Base
belongs_to :company, :counter_cache => true
end
class CompaniesUsers < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
The code works just fine, but I have been wondering if it's possible to:
I want to link a job with an employer, so think of this scenario: A user John who's an employee at Example, he posted the job Rails Developer, so I want to access #job.employer and it should get me back the user John, in other words:
#user = User.find_by_name('john')
#job = Job.find(1)
#job.employer == #user #=> true
So I thought of two possible solutions
First solution
class Job
has_one :employer, :through => :employers
end
class User
has_many :jobs, :through => :employers
end
class Employer
belongs_to :job
belongs_to :user
end
Second solution
class Job
has_one :employer, :class_name => "User"
end
class User
belongs_to :job
end
Which route should I go? Is my code right ?
I have another question, how to get rid of the class_name => "CompaniesUsers" option passed to has_many, should the class be Singular or Plural ? Should I rename it to something like Employees ?
P.S: I posted the same question to Ruby on Rails: Talk
Unless I'm missing something, I'd suggest simply doing
class Job
belongs_to :employer, :class_name => "User"
end
class User
has_many :jobs
end
This would give you methods like
user = User.first
user.jobs.create(params)
user.jobs # array
job = user.jobs.first
job.employer == user # true
You'll need an employer_id integer field in your Jobs table for this to work.
Typically you want to name your pass through model:
company_user
Then you don't need this:
class_name: "CompaniesUsers"
Just make sure the name of your database table is:
company_users
What you have works for you, so that's great. I just find when I don't follow convention I
run in to trouble down the road.

Resources