I'm trying to figure out what is happening when I create a has_many through association.
models:
class Foo
has_many :bars
has_many :bazes, through: :bars
acceptes_nested_attributes_for :bars
acceptes_nested_attributes_for :bazes
end
class Bar
belongs_to :foo
belongs_to :baz
before_create :update_some_attr
def update_some_attr
if self.baz.new_record?
raise "baz is new record"
else
raise "baz is not new record"
end
end
end
class Baz
has_many :bars
end
form:
= form_for :foo do |f|
= f.fields_for :bazes do |baz_form|
# form fields for baz
= f.submit
If I raise an error within update_some_attr, and inspect the class, self.baz.new_record? returns true; and yet the ELSE condition fires, which means the Baz model is already persisted before the Bar record is created. I'm just trying to figure out why I'm getting this inconsistency while debugging.
Imgur link to _better_errors console output
Well first of all I have no direct answer to why the console output and the conditional statement both deliver inconsistent behaviour. However I do see opportunity to rewrite the method so the .new_record? condition can be circumvented:
You are applying a before_create callback which means that only on Model.create() the callback is fired so in that case why would one want to condition whether the instance is new? if the method should be used on existing records to update a certain attribute I would just stick to the update_attributes() method. Another method is to always require an exisiting record and change the callback to after_create this will make more sense as your method is called 'update'.
after_create :update_some_attr
def update_some_attr(*args={attribute: :name, value: "John Doe"})
args.assert_valid_keys(:attribute, :value)
attribute, value = [args[:attribute], args[:value]]
self.update_attributes(attribute => value)
end
I think tha you might expect something like this to happen:
Bar belongs_to Foo and to Baz, so before create Bar, ActiveRecord has to create Foo and Baz, get the ids and set it on the associations ids (foo_id and baz_id).
But i believe that the associations are giving you trouble:
Since Foo has many bars and bazs, ActiveRecord will try to save this associations before create Foo and maybe thats why Baz is already persisted on your method.
One solution (if thats the case) is to change the association between Bar and Baz to something like:
class Bar
has_many :bazs
end
class Baz
belongs_to :bar
end
But as i just realized now, you need Baz to have many Bars, so you can try to remove the association on Foo and the accepts_nested_attributes
has_many :basz, through: :bars
acceptes_nested_attributes_for :bazes
And accept the nested attributes on the Bar model:
acceptes_nested_attributes_for :baz
And you will be with something like:
class Foo
has_many :bars
acceptes_nested_attributes_for :bars
end
class Bar
belongs_to :foo
belongs_to :baz
acceptes_nested_attributes_for :baz
before_create :update_some_attr
def update_some_attr
if self.baz.new_record?
raise "baz is new record"
else
raise "baz is not new record"
end
end
end
class Baz
has_many :bars
end
Related
I have models like below:
class Foo < ActiveRecord::Base
has_many: :bars
end
class Bar < ActiveRecord::Base
belongs_to: :foo
has_many: :bazs
end
class Baz < ActiveRecord::Base
belongs_to: :bar
end
How can I can includes foo in my baz query? (Something like Baz.includes(:foo).where(condition: 'condition').map(&:foo))
You'll have to get a join to foo through the bar association. Something similar to this should work for you in ActiveRecord.
Baz.joins(bar: :foo).where(foos: { SOME_FOO_COLUMN: 'condition' })
This will return a collection of Baz's where your Foo condition is true.
I have these three Active Record models:
class Event < ActiveRecord::Base
has_many :event_categories, inverse_of: :event
has_many :categories, through: :event_categories
end
class EventCategory < ActiveRecord::Base
belongs_to :event
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :event_categories
has_many :events, through: :event_categories
end
I think the relations are good.
If I want to know what Events have a Category, for example id=5.. I do:
Category.find(5).events
But, if I want to know all Events for more than one category, for example:
Category.where(:id => [3,5]).events
It isn't working. Any ideas?
Please note, when you do has_many :events in a model, Active Record defines a method of name events for that class.
When you do Category.find(5).events, you get events associated with one object (i.e. Category.find(5)) , however Category.where(:id => [3,5]) returns an array of Category objects, so you can't use events function on an array, Only way to get events for all search results is iterate over them and access them individually, something like following:
all_events = Category.where(:id => [3,5]).inject([]) {|res,cat| res << cat.events}
Above code will do one query per iteration, to avoid this, we can include events, in the first query itself, like following, which will provide result in only one query:
all_events = Category.includes(:events).where(:id => [3,5]).inject([]) {|res,cat| res << cat.events}
I'm working in Rails 3 and have a table with multiple child tables, e.g.
class Foo < ActiveRecord::Base
has_many :things
has_many :items
has_many :widgets
end
class Thing < ActiveRecord::Base
belongs_to :foo
end
class Item < ActiveRecord::Base
belongs_to :foo
end
class Widget < ActiveRecord::Base
belongs_to :foo
end
Is there a simple way for me to check to if a given Foo has a child record in one or more of the tables? Basically, is there a better way to do this:
if !foo.things.empty? or !foo.items.empty? or !foo.widgets.empty?
puts "This foo is in use!"
emd
Well, I think you're on the right track, but maybe just put that as a method in your Foo model
class Foo < ActiveRecord::Base
def children?
things.any? || items.any? || widgets.any?
end
end
Then just say, Foo.first.children? and get true if the Foo instance has any children.
This is what any? is for.
class Foo < ActiveRecord::Base
def children?
things.any? || items.any? || widgets.any?
end
end
Since this has become a topic of contention, I present to you:
> foo = Foo.last
Foo Load (0.6ms) SELECT "foos"......
> foo.items
Item Load (0.9ms) SELECT "items".*.......
> foo.items.any?
=> true #OH, WHAT's that? NO SQL CALLS? GEE WILLICKERS
> foo.items.exists?
Item Exists (0.5ms) #Hmmmmmm....
=> true
The point here is that under any circumstances, exists makes a DB call, where as any? will not, if spaces is always loaded into memory. Now as I said, many times, the importance is not the efficiency of the DB call (AND YES, the SQL call exists? makes is more efficient), but the fact that any? won't necessarily make a call to the DB, which is a HUGE advantage. Look for yourself:
[20] pry(main)> Benchmark.measure { foo.item.exists? }
Item Exists (0.5ms) SELECT 1 AS one FROM "items" ...
=> #<Benchmark::Tms:0x007fc1f28a8638
#cstime=0.0,
#cutime=0.0,
#label="",
#real=0.002927,
#stime=0.0,
#total=0.00999999999999801,
#utime=0.00999999999999801>
[21] pry(main)> Benchmark.measure { foo.items.any? }
=> #<Benchmark::Tms:0x007fc1f29d1aa0
#cstime=0.0,
#cutime=0.0,
#label="",
#real=7.6e-05,
#stime=0.0,
#total=0.0,
#utime=0.0>
For a more concise timing, look at this:
> Benchmark.measure { 1000.times {foo.items.exists?} }.total
=> 2.5299999999999994
> Benchmark.measure { 1000.times {foo.items.any?} }.total
=> 0.0
Now as I said, many times, it depends on circumstance -- you could have many circumstances where these items aren't loaded into memory, but many times, they are. Choose which one works best for you depending on how you're calling it.
This should work for any given model.
class Foo < ActiveRecord::Base
def children?
has_associated_records = self.class.reflect_on_all_associations.map { |a| self.send(a.name).any? }
has_associated_records.include?(true)
end
end
You could subclass Thing Item and Widget. Or add a polymorphic join table to keep track of it. Not ideal, I know.
You could at least do this, so it would read a little better.
if foo.things.exists? || foo.items.exists? || foo.widgets.exists?
puts "This foo is in use!"
end
'empty?' uses 'exists?' behind the scenes, I believe.
Suppose all the associations are loaded into memory:
class Foo < ActiveRecord::Base
has_many :things
has_many :items
has_many :widgets
def in_use?
[things, items, widgets].flatten.any?
end
end
Edit
I just realized that this is wrong: each association (even if still loaded into memory) will be loaded which isn't good.
things.any? || items.any? || widgets.any?
is more correct and has been proposed before me.
The answer by #Marcelo De Polli is the most generalized one posted so far.
This answer is an updated version of it for Rails 5.
The parent class for a model is ApplicationRecord in Rails 5 and later, which used to be ActiveRecord::Base up to Rails 4 (n.b., the original question is tagged as Rails 3).
For simplicity of the code, use:
class Foo < ApplicationRecord
def children?
self.class.reflect_on_all_associations.map{ |a| self.send(a.name).any? }.any?
end
end
To pursue more run-time efficiency when a model may have many classes of children, use:
class Foo < ApplicationRecord
def children?
self.class.reflect_on_all_associations.each{ |a| return true if self.send(a.name).any? }
false
end
end
I'm trying to find an elegant (standard) way to pass the parent of a polymorphic model on to the view. For example:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
The following way (find_imageable) works, but it seems "hackish".
#PictureController (updated to include full listing)
class PictureController < ApplicationController
#/employees/:id/picture/new
#/products/:id/picture/new
def new
#picture = imageable.pictures.new
respond_with [imageable, #picture]
end
private
def imageable
#imageable ||= find_imageable
end
def find_imageable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
Is there a better way?
EDIT
I'm doing a new action. The path takes the form of parent_model/:id/picture/new and params include the parent id (employee_id or product_id).
I'm not sure exactly what you're trying to do but if you're trying to find the object that 'owns' the picture you should be able to use the imageable_type field to get the class name. You don't even need a helper method for this, just
def show
#picture = Picture.find(params[:id])
#parent = #picture.imagable
#=> so on and so forth
end
Update
For an index action you could do
def index
#pictures = Picture.includes(:imagable).all
end
That will instantiate all 'imagables' for you.
Update II: The Wrath of Poly
For your new method you could just pass the id to your constructor, but if you want to instantiate the parent you could get it from the url like
def parent
#parent ||= %w(employee product).find {|p| request.path.split('/').include? p }
end
def parent_class
parent.classify.constantize
end
def imageable
#imageable ||= parent_class.find(params["#{parent}_id"])
end
You could of course define a constant in your controller that contained the possible parents and use that instead of listing them in the method explicitly. Using the request path object feels a little more 'Rails-y' to me.
I just ran into this same problem.
The way I 'sort of' solved it is defining a find_parent method in each model with polymorphic associations.
class Polymorphic1 < ActiveRecord::Base
belongs_to :parent1, :polymorphic => true
def find_parent
self.parent1
end
end
class Polymorphic2 < ActiveRecord::Base
belongs_to :parent2, :polymorphic => true
def find_parent
self.parent2
end
end
Unfortunately, I can not think of a better way. Hope this helps a bit for you.
This is the way I did it for multiple nested resources, where the last param is the polymorphic model we are dealing with: (only slightly different from your own)
def find_noteable
#possibilities = []
params.each do |name, value|
if name =~ /(.+)_id$/
#possibilities.push $1.classify.constantize.find(value)
end
end
return #possibilities.last
end
Then in the view, something like this:
<% # Don't think this was needed: #possibilities << picture %>
<%= link_to polymorphic_path(#possibilities.map {|p| p}) do %>
The reason for returning the last of that array is to allow finding the child/poly records in question i.e. #employee.pictures or #product.pictures
I've got a super-class (model) Measurement and two sub-classes: WeightMeasurement and LengthMeasurement.
I've then got a Person class which as many WeightMeasurements and LengthMeasurements.
The issue is when creating a new measurement for a Person, I want to use a shared controller that will handle both weight and length measurements.
However, the way that I would typically build up a Person's measurements would be access them bia the parent (Person). Like person.weight_measurement.build. The problem is that I don't know what to put here... person..build ?
# Base-model, includes "type" column in database.
class Measurement < ActiveRecord::Base
belongs_to :person
end
# model subclass
class WeightMeasurement < Measurement
end
# model subclass
class LengthMeasurement < Measurement
end
class Parent < ActiveRecord::Base
has_many :weight_measurements, :dependent => :destroy
has_many :length_measurements, :dependent => :destroy
end
# Single controller for "Measurements"
class MeasurementsController < ApplicationController
...
def new
person = Person.find(params[:person_id])
#
normally would do this, but because I am using STI,
# I don't know that it is a Person's "weight" measurement we are creating
#
# #measurement = #person.weight_measurements.build
#
...
end
...
end
What I normally do, is to create a hidden field in my form, which contains the type I am trying to create.
<%= hidden_field_tag :type, "weight_measurement" %>
You could also have it as a visible form option (say a radio button or select - instead of the hidden field above)
In your controller, you can do the following then:
if ["length_measurement", "weight_measurement"].include?(params[:type])
#measurement = "Measurement::#{params[:type].classify}".constantize.new(:person => #person)
end