I've been using the association_collection method "other_ids" throughout my Rails app with no issues. However whenever I try to access it from within the model defining the association, Rails has no idea what I'm taking about. For example:
class Membership < ActiveRecord::Base
belongs_to :course, :touch => true
belongs_to :person, :touch => true
end
class Day < ActiveRecord::Base
belongs_to :course, :touch => true, :counter_cache => true
has_many :presents, :dependent => :delete_all
has_many :people, :through => :presents
before_destroy :clear_attendance
def clear_attendance
mems = Membership.where(:course_id => course.id, :person_id => person_ids)
mems.update_all(["attendance = attendance - ?", (1 / course.days.size.to_f)])
end
end
In this case, person_ids is always null. I've tried self.person_ids, people.ids, etc. All nothing. I have used day.person_ids elsewhere with no issues, so why can't I use it here?
I am using Ruby 1.9.1 and Rails 3.0.3. Here is the SQL call from my log:
[1m[36mAREL (0.0ms)[0m [1mUPDATE "memberships" SET attendance = attendance - 0.3333333333333333 WHERE ("memberships"."course_id" = 4) AND ("memberships"."person_id" IN (NULL))[0m
edit: added more code to clarify question
What you really want there is:
def a_method
self.people.all
end
But to answer your question, person_ids is the correct method, and it should return an empty array, not nil. I just tried an association like that out in 2.3.10. Maybe you can post some more of your code, rails version, etc.
Thanks for your help - I figured it out myself. The problem was the order of my callbacks. I was trying to call person_ids after the association had been deleted. Changing the order to this solved my issues.
class Day < ActiveRecord::Base
before_destroy :clear_attendance
belongs_to :course, :touch => true, :counter_cache => true
has_many :presents, :dependent => :delete_all
has_many :people, :through => :presents
Related
Hi I'm having trouble with a simple counter cache in rails. I have the following models:
class CarImage < ActiveRecord::Base
belongs_to :car, :counter_cache => :images_count
end
class Car < ActiveRecord::Base
has_many :images, :class_name => "CarImage", :dependent => :destroy, :limit => 4
end
The problem occurs when doing this:
car1.images << car2.images
None of the images_count on the two cars are being updated accordingly.
I found this on the topic: How to update counter_cache when updating a model?
And I my case the solution would look something like this:
class Car < ActiveRecord::Base
has_many :images, :class_name => "CarImage", :dependent => :destroy, :limit => 4
after_save :update_counter, :if => :car_id_changed?
private
def update_counter
new_car = Car.find(car_id)
Car.increment_counter(:images_count, new_car.id)
if car_id_was.present?
old_car = Car.find(car_id_was)
Car.decrement_counter(:images_count, old_car.id)
end
end
end
But - I'm asking myself - Why is this not build into rails at first hand? I can't find anything in the documentation about the issue, and to me using the build-in counter_cache in rails is almost unusable, if its really true, that it only supports create and destroy - not update! Can anyone give a good explanation as to why this is? Is it really necessary for me to build a callback and keep an eye on the relations my self?
BTW. I'm using 3.1
I have 3 models:
class DeliveryMethod
has_many :subscription_delivery_methods
has_many :subscriptions, :through => :subscription_delivery_methods
end
class SubscriptionDeliveryMethod < ActiveRecord::Base
belongs_to :subscription
belongs_to :delivery_method
end
class Subscription < ActiveRecord::Base
has_one :subscription_delivery_method
has_one :delivery_method, :through => :subscription_delivery_method
end
I assign delivery method to Subscription like this:
s.delivery_method = DeliveryMethod.find 1
When I do the assignment like above Rails updates subscription_delivery_methods, which is expected:
UPDATE subscription_delivery_methods SET delivery_method_id = 1, updated_at = '2011-10-27 09:11:23' WHERE subscription_delivery_methods.id = 2
But when I do s.save! it touches DeliveryMethod, which is unexpected and unwanted:
UPDATE delivery_methods SET updated_at = '2011-10-27 08:40:53' WHERE delivery_methods.id = 1
I tried all kinds of :readonly and :touch flags on the associations to prevent this update from happening. I haven't succeeded. Do you guys know how to stop it?
Thanks a lot.
Maybe autosave?
has_one :delivery_method, :through => :subscription_delivery_method, :autosave => false
my problem is following. How can I joins belongs_to association from polymorphic model
There is situation
opinion.rb
class Opinion < ActiveRecord::Base
belongs_to :opinionable, :polymorphic => true
belongs_to :category
end
answer.rb
class Answer < ActiveRecord::Base
has_many :opinions, :as => :opinionable
end
How can i do following
Opinion.joins(:opinionabe).all
it will throw
ArgumentError: You can't create a polymorphic belongs_to join without specifying the polymorphic class!
How can i specific which class i want to join?
Second question. How to preload it?
Opinion.preload(:opinionable).all
works fine. It will do query for each class in belongs_to.
But. if i want to do something like
Opinion.preload(:opinionable => :answer_form).all
there is problem because one model has this association and second hasn't. So it will throw exception.
So how i can do something like
Opinion.preload(:answer => :answer_form, :another_belongs_to_model).all
?
Thanks, David!
Actually if you just do
belongs_to :opinionable_answer, :foreign_key => :opinionable_id, :class_name => "Answer", conditions: { opinions: { opinionable_type: "Answer"}}
then you can do
Opinion.joins(:opinionable_answer).where(answers: { awesome: true})
It looks like you have not specified opinionable_type:string column for your Opinion model.
Try to update your migration in this manner:
class CreateOpinions < ActiveRecord::Migration
def self.up
create_table :opinions do |t|
t.integer :opinionable_id
t.string :opinionable_type
# ... other fields
t.timestamps
end
end
def self.down
drop_table :opinions
end
end
This will solve your second question and Opinion.preload(:opinionable).all should work well.
You cann't do joins on polymorphic association because they can be located in different tables, which are detected after Opinion model is loaded. That why model needs column opinionable_type.
If you try to do this you'll get next exception
ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :opinionable
UPD: Added magic join ^_^
class Opinion < ActiveRecord::Base
belongs_to :opinionable, :polymorphic => true
belongs_to :opinionable_answer, :foreign_key => :opinionable_id, :class_name => "Answer"
scope :by_type, lambda { |type| joins("JOIN #{type.table_name} ON #{type.table_name}.id = #{Opinion.table_name}.opinionable_id AND #{Opinion.table_name}.opinionable_type = '#{type.to_s}'") }
end
Example:
Opinion.by_type(Answer).to_sql
=> "SELECT \"opinions\".* FROM \"opinions\" JOIN answers ON answers.id = opinions.opinionable_id AND opinions.opinionable_type = 'Answer'"
I know this question is old but I just spent an hour looking for the solution to a similar problem (Rails 3) and the only way I got it to work was the solution stated here: https://stackoverflow.com/a/25966630/6878997
Which, in your case would be:
class Opinion < ActiveRecord::Base
# The true polymorphic association
belongs_to :opinionable, polymorphic: true
# The trick to solve this problem
has_one :self_ref, :class_name => self, :foreign_key => :id
has_one :answer, :through => :self_ref, :source => :opinionable, :source_type => Answer
end
Seems tricky but this way you will be able to do multiple chained joins such as:
joins(answer: :other_model).
And whenever opinion.opinionable is not an Answer, opinion.answer will return nil.
Hope it helps somebody!
I have a table with entries, and each entries can have different account-types. I'm trying to define and return the account based on the value of cindof
Each account type has one table, account_site and account_page. So a regular belongs_to won't do.
So is there any way to return something like:
belongs_to :account, :class_name => "AccountSite", :foreign_key => "account_id" if cindof = 1
belongs_to :account, :class_name => "AccountPage", :foreign_key => "account_id" if cindof = 2
Have tried to do that in a method allso, but no luck. Really want to have just one accountand not different belongs_to names.
Anyone that can figure out what I want? Hard to explain in English.
Terw
You should be able to do what you want with a polymorphic association. This won't switch on cindof by default, but that may not be a problem.
class ObjectWithAccount < ActiveRecord::Base
belongs_to :account, :polymorphic => true
end
class AccountSite < ActiveRecord::Base
has_many :objects_with_accounts,
:as => :account,
:class_name => 'ObjectWithAccount'
end
class AccountPage < ActiveRecord::Base
has_many :objects_with_accounts,
:as => :account,
:class_name => 'ObjectWithAccount'
end
You will need both an account_id column and a account_type column. The type of the account object is then stored in the extra type column.
This will let you do:
obj.account = AccountPage.new
or
obj.account = AccountSite.new
I would look into Single Table Inheritance. Not 100% sure, but I think it would solve your problem http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html
If that isn't good, this isn't too hard to implement yourself.
def account
case self.cindof
when 1 then AccountSite.find self.account_id
when 2 then AccountPage.find self.account_id
end
end
I have the following models.
# app/models/domain/domain_object.rb
class Domain::DomainObject < ActiveRecord::Base
has_many :links_from, :class_name => "Link", :as => :from, :dependent => :destroy
end
# app/models/link.rb
class Link < ActiveRecord::Base
belongs_to :from, :polymorphic => true
belongs_to :object_value, :polymorphic => true
end
Problem is, when I do the following, the from_type doesn't prefix the Domain namespace to the model e.g.
Domain::DomainObject.all(:include=> :links_from )
That causes the following SELECT:
SELECT `links`.* FROM `links` WHERE (`links`.`from_id` IN (5,6,12,13,18,24,25,27,29,30,31,32,34,35,39) and `links`.`from_type` = 'DomainObject')
The query should be:
SELECT `links`.* FROM `links` WHERE (`links`.`from_id` IN (5,6,12,13,18,24,25,27,29,30,31,32,34,35,39) and `links`.`from_type` = 'Domain::DomainObject')
because Rails automatically saves the model with the namespace.
I've seen a few recommendations on Rails sites about doing something like this:
belongs_to :from, :polymorphic => true, :class_name => "Domain::DomainObject"
However, that doesn't appear to work either.
So, is there a better way to do this? Or is this not supported?
To fix this, I did a include Domain in the DomainObject model and set ActiveRecord::Base.store_full_sti_class = true in config/environment.rb.
hoyhoy's response is the solution. This solved my problem , too (I actually wanted the namespace to be stripped off).
However, I'd recommend that x.store_full_sti_class = true be added to config/environment.rb, only if it is desired globally. I imagine it might not always be required, in which case, we can easily translate the solution to the class level.
class User < ActiveRecord::Base
self.store_full_sti_class = true
...
end