My problem:
I have Three models: Company, Parent, and Child.
Child belongs_to Parent which belongs_to Company
I need to get all children where a certain attribute is set to false, within one company. (The Parent model has a company_id while the Child model does not.)
What I'm trying:
I have the following join:
#objects = Parent.joins(:childs).where('parents.company_id' => current_user.company_id, 'childs.foo' => false)
In my view:
<!-- This should be a list of child objects -->
<% #objects.each do |obj| %>
<%= obj.foo %>
<% end %>
(foo being the attribute of the child object)
Models:
class Company < ActiveRecord::Base
has_many :parents, :dependent => :destroy
...
end
class Parent < ActiveRecord::Base
has_many :childs, :dependent => :destroy
belongs_to :company
...
end
class Child < ActiveRecord::Base
belongs_to :parent
...
end
However, writing the Parent.joins(:childs)... returns an ActiveRecord relation of Parent objects. (Throwing an error when I try to access the child attributes) I need the end list to be of child objects. But I am finding it difficult to do so.
A good answer to this question would be one that:
Solved this problem in another way that made more sense while not being too computationally intensive.
Or something that shows how to get a list/relation of child objects instead of the parent objects.
Simple, start with the Child class:
Child.joins(:parent).where(parents: {company_id: current_user.company_id}, foo: false)
I would probably advise using scopes/class methods to accomplish this in a cleaner fashion:
class Parent < ActiveRecord::Base
def self.for_user(user)
where(company_id: user.company_id)
end
end
class Child < ActiveRecord::Base
scope :fooless, ->{where foo: false}
end
Now you can do this:
Child.joins(:parent).merge(Parent.for_user(current_user)).fooless
Related
I have three serializers, nested within each other. Like this:
class PersonSerializer < ActiveModel::Serializer
attributes :id :name
has_many: companies
class Company < ActiveModel::Serializer
has_many :products
class ProductSerializer < ActiveModel::Serializer
has_many :product_items do
unless person.id != object.company.user_id
object.product_items
end
end
end
end
end
My issue is the line: unless person.id != object.company.user_id.
person is undefined here. How do I get access to the current person instance within the ProductSerializer?
I am unsure why would you need that structure, but general class definition declares a new scope. Whether you need to capture variables from the parent scope, use closure with Class#new instead:
class PersonSerializer < ActiveModel::Serializer
attributes :id :name
has_many: companies
Company = Class.new(ActiveModel::Serializer) do
has_many :products
ProductSerializer = Class.new(ActiveModel::Serializer) do
has_many :product_items do
unless person.id != object.company.user_id
object.product_items
end
end
end
end
end
The models I have:
Category:
class Category < ApplicationRecord
has_many :categorizations
has_many :providers, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Provider:
class Provider < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Categorization:
class Categorization < ApplicationRecord
belongs_to :category
belongs_to :provider
has_many :games, dependent: :destroy
accepts_nested_attributes_for :games
end
Game:
class Game < ApplicationRecord
belongs_to :categorization
end
I need to display the games, that belongs to a specific provider. I tried to do it like:
<% #provider.categorizations.joins(:games).each do |game| %>
<%= game.title %>
<% end %>
It gives me an error: NoMethodError: undefined method 'title' for #<Categorization:0x007f2cf6ee49e8>. So, it loops through the Categorization. What is the best way to loop through the joined games table? Thanks.
First, you should do the request in your controller, or even better call a scope (defined in a model) from the controller.
Do not forget that Active Record is just an ORM, a tool allowing you to manipulate SQL.
With #provider.categorizations.joins(:games) you are not asking for games. You are asking for the categorizations and you do a JOIN with the games table. This joins is usually to allow to filter by games attributes.
To do what you want you should do the following :
#games = Game.joins(:categorization).where('categorization.provider_id = ?',#provider.id)
As you can see, the join do not return categorization, it allow me to use categorization as a filter.
You should always be aware of the SQL generated by Active Record. Look at the SQL query generated in your server's traces.
I'm guessing 'title' is an attribute of games and not categorization, so you either need to return an array of games, or add a select on the end to pull the title attribute into the categorization object, like so:
<% #provider.categorizations.joins(:games).select('dba.games.title').each do |game| %>
<%= game.title %>
<% end %>
Just to add- you shouldn't really be doing this in the view file. I'd go as far as not even doing this in the controller. I tend to encapsulate this sort of logic into a service class, which is instantiated in the controller to return a set of results. The controller should only be passing the result set on, which is then presented by the view.
class Provider < ActiveRecrord::Base
# this could be a scope instead, or in a seperate class which
# the provider model delegates to- whatever floats you boat
def get_games
# you could use pluck instead, which would return an array of titles
categorizations.joins(:games).select('dba.games.title')
end
end
class ProviderController < ApplicationController
def show
provider = Provide.find(params[:id])
#games = provider.get_games
end
end
<% #games.each do |game| %>
<%= game.title %>
<% end %>
I have a form for creating a new invoice with many items.
class Invoice < ActiveRecord::Base
attr_accessible :project_id, :number, :date, :recipient, :items_attributes
accepts_nested_attributes_for :items
end
Now when I instantiate a new invoice and a set of containing items, I want these items to know something about the invoice they belong to even before they are saved, so I can do something like this in my Item model:
class Item < ActiveRecord::Base
belongs_to :invoice
after_initialize :set_hourly_rate
private
def set_hourly_rate
if new_record?
self.price ||= invoice.project.hourly_rate
end
end
end
Right now, my code fails because the child (item) doesn't know anything about its parent (invoice) during instantiation. Only after saving the invoice (and thus its nested items), it all works out. But I want to set a default value on each new item even before it gets saved.
How can this be done?
Thanks for any help.
You can add a callback on the invoice association, as follows:
class Invoice < ActiveRecord::Base
# Code
belongs_to :project
has_many :items, :after_add => :set_item_price
private
def set_item_price(item)
item.price = project.hourly_rate
end
end
Once you have your invoice object, you can create children records with the .items.build method (docs here)
items created through this method should have a reference to the invoice
Though, I think they will have the reference only if the Invoice has been persisted (not really sure about that.)
I have two models, one is the parent of the other, and the parent accepts_nested_attributes_for and validates_associated the children.
However, some of my validations have an :if that needs to check one of the properties of the parent.
I was thinking that I could do something like this:
validates_presence_of :blah, :if => Proc.new{|thing| thing.parent.some_value.present?}
However, the 'parent' relationship doesn't appear to be setup at the time of validation (I would assume the children get instantiated and validated first.
Therefore is there any way of doing what I'm thinking of? Is it possible?
You can use before_update or before_create callbacks as per your need like this..
def before_update
self.errors.add("Error Message") if self.parent.some_value.present?
return false if self.errors.count > 0
end
def before_create
self.errors.add("Error Message") if self.parent.some_value.present?
return false if self.errors.count > 0
end
This kind of validation should work:
validates_associated :children
But it won't
The reason is, as far as I understand, beause using acceptes_nested_attributes_for is creating nested objects straight to database via one transaction without passing any children validations.
What you can do here: write your own validation in parent model and validate creating children objects.
Use the :inverse_of option for the association on the parent, so the children will have a reference to the parent when they are built.
class Parent < ActiveRecord::Base
has_many :children, :inverse_of => :parent
accepts_nested_attributes_for :children
end
class Child < ActiveRecord::Base
belongs_to :parent
end
p = Parent.new :children_attributes => { 0 => { :child_attribute => 'value' } }
p.children.first.parent #=> shouldn't be nil anymore
I'm trying to access my parent model in my child model when validating. I found something about an inverse property on the has_one, but my Rails 2.3.5 doesn't recognize it, so it must have never made it into the release. I'm not sure if it's exactly what I need though.
I want to validate the child conditionally based on parent attributes. My Parent model has already been created. If the child hasn't been created when I update_attributes on the parent, then it doesn't have access to the parent. I'm wondering how I can access this parent. It should be easy, something like parent.build_child sets the parent_id of the child model, why is it not doing it when building the child for accepts_nested_attributes_for?
For Example:
class Parent < AR
has_one :child
accepts_nested_attributes_for :child
end
class Child < AR
belongs_to :parent
validates_presence_of :name, :if => :some_method
def some_method
return self.parent.some_condition # => undefined method `some_condition' for nil:NilClass
end
end
My form is standard:
<% form_for #parent do |f| %>
<% f.fields_for :child do |c| %>
<%= c.name %>
<% end %>
<% end %>
With an update method
def update
#parent = Parent.find(params[:id])
#parent.update_attributes(params[:parent]) # => this is where my child validations take place
end
I had basically the same problem with Rails 3.2. As suggested in the question, adding the inverse_of option to the parent's association fixed it for me.
Applied to your example:
class Parent < AR
has_one :child, inverse_of: :parent
accepts_nested_attributes_for :child
end
class Child < AR
belongs_to :parent, inverse_of: :child
validates_presence_of :name, :if => :some_method
def some_method
return self.parent.some_condition # => undefined method `some_condition' for nil:NilClass
end
end
I had a similar problem: Ruby on Rails - nested attributes: How do I access the parent model from child model
This is how I solved it eventually; by setting parent on callback
class Parent < AR
has_one :child, :before_add => :set_nest
accepts_nested_attributes_for :child
private
def set_nest(child)
child.parent ||= self
end
end
You cannot do this because in-memory child doesn't know the parent its assigned to. It only knows after save. For example.
child = parent.build_child
parent.child # => child
child.parent # => nil
# BUT
child.parent = parent
child.parent # => parent
parent.child # => child
So you can kind of force this behavior by doing reverse association manually. For example
def child_with_inverse_assignment=(child)
child.parent = self
self.child_without_inverse_assignment = child
end
def build_child_with_inverse_assignment(*args)
build_child_without_inverse_assignment(*args)
child.parent = self
child
end
def create_child_with_inverse_assignment(*args)
create_child_without_inverse_assignment(*args)
child.parent = self
child
end
alias_method_chain :"child=", :inverse_assignment
alias_method_chain :build_child, :inverse_assignment
alias_method_chain :create_child, :inverse_assignment
If you really find it necessary.
P.S. The reason it's not doing it now is because it's not too easy. It needs to be explicitly told how to access parent/child in each particular case. A comprehensive approach with identity map would've solved it, but for newer version there's :inverse_of workaround. Some discussions like this one took place on newsgroups.
check these sites, maybe they'll help you...
Rails Nested Attributes Association Validation Failing
accepts_nested_attributes_for child association validation failing
http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
it seems, rails will assign parent_id after child validation succeeds.
(as parent has an id after it's saved)
maybe worth trying this:
child.parent.some_condition
instead of self.parent.some_condition ... who knows...