Ar multiple joins - ruby-on-rails-3

I have the following models:
class Distributor < ActiveRecord::Base
has_many :products
end
class Producer < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
has_one :favorite
belongs_to :producer
belongs_to :distributor
end
class Favorite < ActiveRecord::Base
belongs_to :product
end
class User < ActiveRecord::Base
has_many :favorites
end
I would like to build a AR expression is analog of sql query:
select *
from `favorites`
inner join `products` on `products`.`id` = `favorites`.`product_id`
inner join `producers` on `producers`.`id` = `products`.`producer_id`
inner join `distributors` on `distributors`.`id` = `products`.`distributor_id`
where `favorites`.`user_id` = 1

You can use a nested set of joins methods like this:
Favorite.joins(:product => [:producer , :distributor]).where("favorites.user_id = 1")
Note that i am using the => notation, but you can use the ruby 1.9+ one too.

Related

How to write a Rails SQL query for finding an object where all children have an equal value

I've been reading this, but can't make sense of writing it into a Rails scope :
find all parent records where all child records have a given value (but not just some child records)
I have a Course, Section, and Quiz, object :
class Course < ActiveRecord::Base
has_many :course_members
has_many :members, through: :course_members
has_many :sections
has_many :quizzes, through: :sections
end
class Quiz < ActiveRecord::Base
belongs_to :member
belongs_to :section
end
class Section < ActiveRecord::Base
belongs_to :course
has_many :quizzes
end
I'd like to find all courses of a member, where all quizzes related to that course have the attribute completed = true.
So in my Member class, I'd ideally like to write something like :
has_many :completed_courses, -> {
joins(:courses, :quizzes, :sections)
# .select( 'CASE WHEN quizzes.completed = true then 1 end') ??? maybe ???
}, class_name: 'Course'
Haha! But barring that being too complicated. I've been trying to write this simply in the Course would also be fine.
class Member < ActiveRecord::Base
has_many :courses, through: :course_members
has_many :course_members
has_many :completed_courses,
-> { joins(:quizzes).where.not(quizzes: {completed: [false, nil]}) },
through: :course_members,
source: :course
end
If your completed boolean column is NOT NULL, then change [false, nil] above to just simply false
Usage Example
irb(main):002:0> Member.first.completed_courses
Member Load (0.2ms) SELECT "members".* FROM "members" ORDER BY "members"."id" ASC LIMIT 1
Course Load (0.1ms) SELECT "courses".* FROM "courses" INNER JOIN "sections" ON "sections"."course_id" = "courses"."id" INNER JOIN "quizzes" ON "quizzes"."section_id" = "sections"."id" INNER JOIN "course_members" ON "courses"."id" = "course_members"."course_id" WHERE (NOT (("quizzes"."completed" = 'f' OR "quizzes"."completed" IS NULL))) AND "course_members"."member_id" = ? [["member_id", 1]]

ActiveRecord map count of whole collection, then that query to SQL

How can I write this query in SQL?
a = Brand.find(1).publications.map(&:component_id)
Hash[a.group_by(&:itself).map {|k, v| [Component.find(k).name, v.size] }]
=> {"title one"=>1, "something"=>1, "continue"=>1}
Obviously I can't call .to_sql on Ennumerable. I've written this so far, but it seems to be counting something other than the number of occurrences:
SELECT c.name, c.id, COUNT(p.component_id)
FROM publications p
INNER JOIN components c
ON c.id = p.component_id
INNER JOIN brands_components bc
ON bc.brand_id IN (1)
GROUP BY 1, 2
This gets the wrong numbers (i.e., too many are identical):
name id count
------------------------------
something 2026 114
another name 3028 1,140
another new one 2409 2,850
world class 264 6,612
top up 3370 114
The model associations look like this:
class Brand < ActiveRecord::Base
has_many :documents
has_many :publications, through: :users
has_many :users
end
class User < ActiveRecord::Base
has_many :documents, dependent: :destroy
has_many :publications, through: :documents, dependent: :destroy
end
class Document < ActiveRecord::Base
belongs_to :user
belongs_to :brand
has_many :components, through: :publications
has_many :publications, dependent: :destroy
end
class Publication < ActiveRecord::Base
belongs_to :document
belongs_to :component
end
class Component < ActiveRecord::Base
has_many :publications
has_many :documents, through: :publications
end
I want to return the count of occurrences of component_id on Publication filtered by Brand in SQL.
I've solved my issue:
SELECT c.name, COUNT(p.component_id)
FROM publications p
INNER JOIN components c
ON c.id = p.component_id
INNER JOIN brands_components bc
ON c.id = bc.component_id
AND bc.brand_id IN (1)
GROUP BY 1
I had forgotten to make this match: c.id = bc.component_id. Might help someone.

How to perform such join tables in ActiveRecord?

I need help in doing the following join using ActiveRecord relation models:
select "access_urls"."id", "controller_urls"."controller", "action_urls"."action" from
"access_urls"
inner join "controller_urls"
on "controller_urls"."id" = "access_urls"."controller_url_id"
inner join "action_urls"
on "action_urls"."id" = "access_urls"."action_url_id"
Where I have AccessUrl, ActionUrl and ControllerUrl models
class AccessUrl < ActiveRecord::Base
belongs_to :controller_url
belongs_to :action_url
end
class ActionUrl < ActiveRecord::Base
has_many :access_urls
validates :action, presence: true, uniqueness: { message: "já encontra-se em uso." }
end
class ControllerUrl < ActiveRecord::Base
has_many :access_urls
validates :controller, presence: true, uniqueness: { message: "já encontra-se em uso." }
end
Can anyone help me?
Using joins:
result = AccessUrl.joins(
:controller_url, :action_url
).select(
'access_urls.id, controller_urls.control, action_urls.action'
)
This will give you a relation result containing objects of AccessUrl. You can loop through the result and access the selected columns as:
result.each do |r|
# r.id
# r.control
# r.action
end

Ruby ActiveRecord multiple joins through associations

I'd like to convert
SELECT `users`.* FROM `users`
INNER JOIN `memberships`
ON `memberships`.`user_id` = `users`.`id`
INNER JOIN `roles`
ON `roles`.`id` = `memberships`.`role_id`
WHERE `memberships`.`group_id` = 'NUCC' AND (expiration > '2012-07-02')
ORDER BY `roles`.`rank` DESC
Into an ActiveRecord association.
Groups have many members (class User) through memberships. Each membership has a role (role_id) which maps to another table (roles) and subsequently an AR model (Role). Each role has a rank (integer) associated with it.
I'd like to simply sort the members of a group by the memberships-roles-rank.
Untested, probably has typos, but...
class User < ActiveRecord::Base
has_many :memberships
has_many :roles, :through => :memberships, :uniq => true
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships, :uniq => true
end
And then, to sort the users by roles.rank:
q = User.joins(:memberships => :users)
q = q.where(:memberships.group_id => 'NUCC')
q = q.where("expressionn > '2012-07-02'")
q = q.order("roles.rank DESC")
puts q.to_sql
AREL lets you join things up like that pretty easily. For instance, you can keep that going with even further INNER JOINS with syntax similar to:
User.joins(:memberships => { :users => :someothermodel })
Just remember to replicate that structure whenever you need to reference something through the JOIN, or just write your own SQL fragment..

Ruby ActiveRecord has_one with conditions

Let's say I have some Items for sale, and I'm keeping track of their Cost historically:
class Cost < ActiveRecord::Base
belongs_to :item
# eg: costs.amount = 123.45; costs.item_id = 1; costs.created_at = 2011-08-11 16:28
end
class Item < ActiveRecord::Base
has_many :costs
# eg: items.id = 1; items.name = Cheese Sandwich
end
This code works, I can pull out all the previous costs for the item I'm selling.
I feel like it should be possible to have a second clause for Item so that I can pull out the current price directly:
class Item < ActiveRecord::Base
has_many :costs
has_one :current_cost, :class_name => :costs, :conditions => 'MAX(created_at)'
end
my_item.current_cost # => <£123.45, 45 minutes ago>
Any ideas how to achieve this?
class Item < ActiveRecord::Base
has_many :costs
def current_cost
self.costs.order("created_at DESC").first
end
end
my_item.current_cost
has_one :current_cost, :class_name => :costs, :order => 'create_at DESC'
You can use the scope:
class Item < ActiveRecord::Base
has_many :costs
scope :current_cost, limit(1).order("created_at DESC")
end
usage:
my_item.current_cost