Finding multiple records through has_one association - ruby-on-rails

My Customer and Person models looks like this:
class Customer < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
class Person < ActiveRecord::Base
has_one :customer
end
How can I get all Person records that have an association with a Customer?

with sql it might be something like
Customer.where("customers.person_id IS NOT NULL")
to get Person record you can use join
Person.joins( :customers ).where("customers.person_id IS NOT NULL")
I'm not sue either where is necessary here (I believe no) so try Person.joins( :customers ) first

person_array = []
Person.all.each do |p|
unless p.customer.nil?
person_array << p
end
end

I don't think it's the fastest query but:
Customer.where('person_id IS NOT NULL').map(&:person)

rails 2.3.x
Customer.all(:include => :person).map(&:person).compact

Related

Rails 4 - scope with has_one instead of attribute

I have the following two models:
class Person < ActiveRecord::Base
has_one :archive, :dependent => :destroy
class Archive < ActiveRecord::Base
belongs_to :person
Archive holds person_id.
I need to have a scope Person.with_archive. Since Person has no db column of archive_id, the only solution I found is:
def self.has_archive
res = Array.new
self.all.each do |i|
res << i if !i.archive.nil?
end
res
end
The problem is that I get an Array instead of a relationship, which limits me to chain other scopes.
Any nice and clean solution for that?? Thanks!
Try this:
scope :with_archive, -> { joins(:archive) }
This should return only the Persons with an archive, because joins uses an INNER JOIN statement.

How do you sort a collection by distant relationships?

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

Rails 3 Three Models Join

I don't seem to get this right for some reason, though it sounds simple :/
I have three models :
User(id,name,email)
Skill(id,name,description)
UserSkill(user_id,skill_id,level)
How can i get all skills of a certain user, whether he or she has discovered them or not ?
For example, 3 skills (walk, talk, write). 3 users (John, Mary, Jack).
If Mary walks and writes, how can i get it back as a result like :
Mary => {Skill: walk(includes UserSkill), Skill : talk, Skill : write(includes UserSkill) }
You get the idea :)
Try this:
class User
def skill_list
Skill.all(
:select =>"skills.*, A.user_id AS user_id",
:joins => "LEFT OUTER JOIN user_skills A
ON A.skill_id = skills.id
AND A.user_id = #{id}").map do |skill|
skill.name + (skill.user_id.nil? ? "" : "(*)")
end
end
end
Now
user = User.find_by_name("Mary")
user.skill_list
Will print:
[
walk(*),
talk,
write(*)
]
I'm assuming you want to set something up like this:
class User < ActiveRecord::Base
has_many :user_skills
has_many :skills, :through => :user_skills
end
class Skill < ActiveRecord::Base
has_many :user_skills
has_many :users, :through => :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
then you can do:
my_user.skills # returns all Skill records assigned to the user
my_user.user_skills.includes(:skill) # this allows you to access :level in addition to Skill attributes
So the way to get both skills and user_skills is to use the :user_skills association. Basic has_many :through. Am I missing something?
user = User.first
user.user_skills.all.map(&:skills)

Help with a Join in Rails 3

I have the following models:
class Event < ActiveRecord::Base
has_many :action_items
end
class ActionItem < ActiveRecord::Base
belongs_to :event
belongs_to :action_item_type
end
class ActionItemType < ActiveRecord::Base
has_many :action_items
end
And what I want to do is, for a given event, find all the action items that have an action item type with a name of "foo" (for example). So I think the SQL would go something like this:
SELECT * FROM action_items a
INNER JOIN action_item_types t
ON a.action_item_type_id = t.id
WHERE a.event_id = 1
AND t.name = "foo"
Can anybody help me translate this into a nice active record query? (Rails 3 - Arel)
Thanks!
Well I think I solved it myself. Here's what I did
e = Event.find(1)
e.action_items.joins(:action_item_type).where("action_item_types.name = ?", "foo")
Ehm, why not define
has_many :action_item_types, :through => :action_items
and refer to
e.action_item_types.where(:name => "foo")
?
or (as long as "name" is a unique column name)
e.action_items.joins(:action_item_type).where(:name => "foo")

Rails: order using a has_many/belongs_to relationship

I was wondering if it was possible to use the find method to order the results based on a class's has_many relationship with another class. e.g.
# has the columns id, name
class Dog < ActiveRecord::Base
has_many :dog_tags
end
# has the columns id, color, dog_id
class DogTags < ActiveRecord::Base
belongs_to :dog
end
and I would like to do something like this:
#result = DogTag.find(:all, :order => dog.name)
thank you.
In Rails 4 it should be done this way:
#result = DogTag.joins(:dog).order('dogs.name')
or with scope:
class DogTags < ActiveRecord::Base
belongs_to :dog
scope :ordered_by_dog_name, -> { joins(:dog).order('dogs.name') }
end
#result = DogTags.ordered_by_dog_name
The second is easier to mock in tests as controller doesn't have to know about model details.
You need to join the related table to the request.
#result = DogTag.find(:all, :joins => :dog, :order => 'dogs.name')
Note that dogs is plural in the :order statement.

Resources