Querying through four models in Rails - ruby-on-rails

My relationships are set up like this:
A Project has_many documents
A Document has_one content_body
ContentBody has_many audits
I need to retrieve the Audits having only a project id in a descending order.

Assuming
class Audit
belongs_to :content_body
end
class ContentBody
belongs_to :document
end
class Document
belongs_to :project
end
Then
#audits = Audit.joins(content_body: { document: :project })
.where(projects: {id: some_id })
.order(created_at: :desc)
Or
#audits = Audit.joins(content_body: :document)
.where(documents: {project_id: some_id })
.order(created_at: :desc)
Should do the trick. As for the order you did not specify what column exactly descending should be based on so I assumed created_at

Related

Referencing different names in joins with scopes

I have a polymorphic association and sometimes I want to preload it's associations.
When I left join the model, my WHERE filters get lost because they don't referenced the named association.
SELECT COUNT(*) FROM `companies` LEFT OUTER JOIN `key_values` `latest_information` ON `latest_information`.`attachment_id` = `companies`.`id` AND `latest_information`.`attachment_type` = 'Company' AND `key_values`.`name` = 'latest_information' WHERE `latest_information`.`id` IS NOT NULL
# => ActiveRecord::StatementInvalid: Mysql2::Error: symbol key_values.`name` not found
This is the query that is generated but it's invalid due to the key_values.name not being referenced.
Here's what my model looks like:
class Company < LeadRecord
has_many :key_values, as: :attachment, dependent: :delete_all
has_one :latest_information,
-> { KeyValue.latest('latest_information') },
class_name: KeyValue.to_s,
as: :attachment
end
class KeyValue < LeadRecord
belongs_to :attachment, polymorphic: true
def self.latest(name)
order(created_at: :desc).where(name: name) # This is the source of the error
end
end
I can probably fix this by passing addition parameters to self.latest such as the association name but I want to know if there's a better Rails way to do this.
In the interim I have solved this by making this change on KeyValue.
# key_value.rb
def self.latest(name, association_name = 'key_values')
order(created_at: :desc).where("#{association_name}.name = ?", name)
end
# company.rb
has_one association_name,
-> {
KeyValue.latest(
method_name.to_s,
association_name.to_s,
)
},
class_name: KeyValue.to_s,
as: :attachment

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

How to use object attributes in belongs_to :select association?

I have CartItem model that has a belongs_to relation with Product
def CartItem
belongs_to :product, :select => "*, get_product_price_for_shop(#{self.shop_id}) as shop_price"
end
As you can see above I have postgresql function that calculates price for specific shop and I want to use it in select clause of products so I can refer to it like this:
#cart_item.product.shop_price
I'm looking for a way to pass shop_id attribute from CartItem to belongs_to :select key.
A quick thought : did you try a proc ?
def CartItem
belongs_to :product, :select => proc { "*, get_product_price_for_shop(#{self.shop_id}) as shop_price" }
end

Creating a model that has a tree structure

I have categories that are in a tree structure. I am trying to link them together by defining a parent for each one. (I couldn't figure out how to call the property parent so it's just category for now, but it means the parent).
class Category < ActiveRecord::Base
has_one :category # the parent category
end
But the relationship ends up the wrong way around.
The getter function is on the child category (correctly) but the category_id is stored on the parent:
parent = Category.create(:name => "parent")
child = Category.create(:name => "child", :category => parent)
parent.id # 1
child.id # 2
child.category_id # nil
parent.category_id # 2
child.category.name # "parent" (!!)
The parent needs to be able to have multiple children so this isn't going to work.
What you're looking for is self joins. Check this section of the Rails guide out: http://guides.rubyonrails.org/association_basics.html#self-joins
class Category < ActiveRecord::Base
  has_many :children, class_name: "Category", foreign_key: "parent_id"
  belongs_to :parent, class_name: "Category"
end
Every Category will belong_to a parent, even your parent categories. You can create a single category parent that your highest level categories all belong to, then you can disregard that information in your application.
You can use acts_as_tree gem to achieve this, find below example and link.
https://github.com/amerine/acts_as_tree/tree/master
class Category < ActiveRecord::Base
include ActsAsTree
acts_as_tree order: "name"
end
root = Category.create("name" => "root")
child1 = root.children.create("name" => "child1")
subchild1 = child1.children.create("name" => "subchild1")
root.parent # => nil
child1.parent # => root
root.children # => [child1]
root.children.first.children.first # => subchild1
You should take a look at the ancestry gem: https://github.com/stefankroes/ancestry
It provides all the functionality you need and is able to get all descendants, siblings, parents, etc with a single SQL query by using a variant of materialized paths so it'll have better performance than the self-joins and acts_as_tree answers above.
Category should have many categories, and the foreign key of each category should be the parent_id. So, when you do parent.children it lists all the categories which have parent_id=parent.id.
Have you read on Single Table Inheritance?
Full Article - https://blog.francium.tech/best-practices-for-handling-hierarchical-data-structure-in-ruby-on-rails-b5830c5ea64d
A Simple table
Table Emp
id: Integer
name: String
parent_id: Integer
Associations
app/models/emp.rb
class Emp < ApplicationRecord
has_many :subs, class_name: 'Emp', foreign_key: :parent_id
belongs_to :superior, class_name: 'Emp', foreign_key: :parent_id
end
Scope Definition
class Emp < ApplicationRecord
----
----
scope :roots, -> { where(parent_id: nil) }
end
Fetching data
def tree_data
output = []
Emp.roots.each do |emp|
output << data(emp)
end
output.to_json
end
def data(employee)
subordinates = []
unless employee.subs.blank?
employee.subs.each do |emp|
subordinates << data(emp)
end
end
{name: employee.name, subordinates: subordinates}
end
Eager Loading
def tree_data
output = []
Emp.roots.includes(subs: {subs: {subs: subs}}}.each do |emp|
output << data(emp)
end
output.to_json
end

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