I've been banging my head on this one for a while. Someone please resque me.
Scenario
I have the following models
class House < ActiveRecord::Base
has_one :tenancy, :dependent => :destroy, :as => :tenant
end
class LeaseAgreement < ActiveRecord::Base
has_many :tenancies
end
class Tenancy < ActiveRecord::Base
belongs_to :tenant, :polymorphic => true
belongs_to :lease_agreement
def lease=(lease)
if lease.rent_amount > 10000
# do something here
else
# do something else here
end
self.lease_agreement = lease
end
end
My factories
Factory.define :lease_agreement do |l|
l.name "Foo"
l.rent_amount 5000
end
Factory.define :tenancy do |t|
t.name "Foo"
t.association :tenant, :factory => :house
t.after_build { |tenancy| tenancy.lease = Factory.create(:lease_agreement) }
end
also tried this
Factory.define :tenancy do |t|
t.name "Foo"
t.association :tenant, :factory => :house
t.after_build { |tenancy| tenancy.lease = Factory.create(:lease_agreement, :tenant => tenancy) }
end
Both ways in my spec tests when I try this; #house = Factory(:house) I get the following error
NoMethodError: undefined method `rent_amount' for nil:NilClass
from /home/kibet/.rvm/gems/ruby-1.8.7-p352/gems/activesupport-3.0.5/lib/active_support/whiny_nil.rb:48:in `method_missing'
from /home/kibet/code/ruby/stuff/app/models/tenancy.rb:44:in `lease='
How would I go about this?
It looks like an order of operations problem, I think lease is being set to nil before it evaluates your after_build hook where lease is a legit LeaseAgreement instance.
You code can't handle a nil lease being passed in, which is a legitimate value if you want to clear the association. Try handling the nil like so:
class Tenancy < ActiveRecord::Base
belongs_to :tenant, :polymorphic => true
belongs_to :lease_agreement
def lease=(lease)
if lease.present? && lease.rent_amount > 10000
# do something here
else
# do something else here
end
self.lease_agreement = lease
end
end
The code as written will always produce an error with a nil lease passed in.
I think if you write your factory like this:
Factory.define :tenancy do |t|
t.name "Foo"
t.association :tenant, :factory => :house
t.after_build { |tenancy| tenancy.lease = Factory.create(:lease_agreement) }
end
your :lease_agreement will then be created correctly, and it should work. There is no tenancy for a lease_agreement.
Related
I am creating an application in Ruby and I have two model classes
class Restaurant < ActiveRecord::Base
attr_accessible :title, :cuisine, :price_range, :environment
self.primary_key = 'title'
has_many :environments
end
end
class Environment < ActiveRecord::Base
attr_accessible :title, :type
belongs_to :restaurants,foreign_key: 'title'
end
I am trying to query two tables in the controller- to return restaurants with specific environments.
#restaurants = Restaurant.joins('INNER JOIN environments ON restaurants.title=environments.title').where('environments.env_type'=> #selected_environment)
NOTE: #selected_environment is a hash contained list of environments. Environments table has a list of environment and restaurant pairs(there can be more than one restaurant).
I know the query above is not correct to achieve my goal. Is there a way i can get this done?
controller method:
def index
# can later be moved to the Restaurant model
ordering = {:title => :asc}
#all_environments = Restaurant.all_environments
#selected_environments = params[:environments] || session[:environments] || {}
if #selected_environments == {}
#selected_environments = Hash[#all_environments.map {|environment| [environment, environment]}]
end
if params[:environments] != session[:environments]
session[:environments] = #selected_environments
redirect_to :environments => #selected_environments and return
end
#restaurants = Restaurant.joins(:environments).where(environments:{env_type: #selected_environments.keys })
end
For your models you want to do this:
class Restaurant < ActiveRecord::Base
attr_accessible :title, :cuisine, :price_range, :environment
self.primary_key = 'title'
has_many :environments, :foreign_key => 'title', :primary_key => 'title'
end
class Environment < ActiveRecord::Base
attr_accessible :title, :type
belongs_to :restaurants, foreign_key: 'title'
end
Try structuring your query like this:
Restaurant.joins(:environments).where(environments: { env_type: #selected_environments.values })
I am having an issue overriding a setter on a belongs_to attribute. I have the following:
class Task < ActiveRecord::Base
belongs_to :parent_task, :class_name => 'Task', :foreign_key => 'parent_task_id'
def parent_task=(value)
write_attribute(:parent_task, value)
unless value == nil
#remove all groups_belonging_to if this has been made into a child task -i.e. if it has a parent
self.groups_belonging_to = []
end
self.save
end
The user model has many tasks:
class User < ActiveRecord::Base
has_many :tasks_created_by, class_name: 'Task', foreign_key: 'created_by_id', dependent: :destroy
In my testing I am creating a task like so:
#child_task = #user.tasks_created_by.create!(name: "Task to Delete", parent_task: #top_parent)
Which gives the error:
ActiveModel::MissingAttributeError: can't write unknown attribute `parent_task`
When I remove the override there is no problem, so I am definitely doing the override wrong somehow. I have used very similar override logic elsewhere but not through a relation before.
This would be better written as a callback. You can use a before_save callback to check for a parent_task and if it is set, clear groups_belonging_to:
class Task < ActiveRecord::Base
belongs_to :parent_task, class_name: 'Task', foreign_key: 'parent_task_id'
before_save :clear_groups if: :parent_task
def clear_groups
self.groups_belonging_to = []
end
end
I solved this problem using alias_method before I override the setter:
class Task < ActiveRecord::Base
belongs_to :parent_task, :class_name => 'Task', :foreign_key => 'parent_task_id'
alias_method :set_parent_task, :parent_task=
def parent_task=(value)
set_parent_task(value)
unless value == nil
#remove all groups_belonging_to if this has been made into a child task -i.e. if it has a parent
self.groups_belonging_to = []
end
self.save
end
end
This is my code for moving data from my old database:
class Old < ActiveRecord::Base
establish_connection :old_version
self.abstract_class = true
class Recipe < self
set_table_name :recipes
has_many :uploaded_files, :as => :storage
end
class UploadedFile < self
set_table_name :uploaded_files
belongs_to :storage, :polymorphic => true
end
end
When I run the following code
Old::Recipe.all.each do |recipe|
puts recipe.uploaded_files.to_sql
end
It performs this SQL
SELECT `uploaded_files`.* FROM `uploaded_files` WHERE `uploaded_files`.`storage_id` = 38 AND `uploaded_files`.`storage_type` = 'Old::Recipe'
The problem is that I get:
`storage_type` = 'Old::Recipe'
But I need:
`storage_type` = 'Recipe'
How can I change the class for a polymorphic relationship?
The doc for has_many doesn't give me an answer.
Recently I had similar problem, this is a solution that worked for me in rails 4.2:
class Recipe < self
set_table_name :recipes
has_many :old_files, -> (object) { unscope(where: :storage_type).where(storage_type: 'Recipe') }, class_name: 'UploadedFile'
end
You have to add unscope(:where) to remove condition uploaded_files.storage_type = 'Old::Recipe' from query.
The answer by santuxus above is working properly for rails 4.2+
However, for lower versions, you could try overwriting the association like so:
class Recipe
has_many :uploaded_files, conditions: { storage_type: 'Recipe' }, foreign_key: :storage_id
end
Unfortunately, for now I found only one way to do that:
class Old < ActiveRecord::Base
establish_connection :old_version
self.abstract_class = true
class Recipe < self
set_table_name :recipes
has_many :old_files,
:class_name => 'UploadedFile',
:finder_sql => Proc.new {
%Q{
SELECT uploaded_files.*
FROM uploaded_files
WHERE uploaded_files.storage_id = #{id} AND
uploaded_files.storage_type = 'Recipe'
}
}
end
class UploadedFile < self
set_table_name :uploaded_files
belongs_to :storage, :polymorphic => true
end
end
namespace :old do
task :menu => :environment do
Old::Recipe.all.each do |recipe|
puts '~' * 50
puts recipe.id
recipe.old_files.to_a.each do |file|
puts file.storage_id, file.storage_type
end
end
end
end
very very sad
I have the following models:
class Section < ActiveRecord::Base
belongs_to :course
has_one :term, :through => :course
end
class Course < ActiveRecord::Base
belongs_to :term
has_many :sections
end
class Term < ActiveRecord::Base
has_many :courses
has_many :sections, :through => :courses
end
I would like to be able to do the following in my Section model (call_number is a field in Section):
validates_uniqueness_of :call_number, :scope => :term_id
This obviously doesn't work because Section doesn't have term_id, so how can I limit the scope to a relationship's model?
I tried creating a custom validator for Section to no avail (doesn't work when I create a new Section with the error "undefined method 'sections' for nil:NilClass"):
def validate_call_number
if self.term.sections.all(:conditions => ["call_number = ? AND sections.id <> ?", self.call_number, self.id]).count > 0
self.errors[:base] << "Call number exists for term"
false
end
true
end
Thanks a lot!
Assuming your validation code is correct, why don't you simply add a check for term existence?
def validate_call_number
return true if self.term.nil? # add this line
if self.term.sections.all(:conditions => ["call_number = ? AND sections.id <> ?", self.call_number, self.id]).count > 0
self.errors[:base] << "Call number exists for term"
false
end
true
end
I'm using factory_girl_rails instead of fixtures. Here are my models:
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :user
end
Here are my factories:
Factory.define :user do |u|
end
Factory.define :task do |t|
t.association :user, :factory => :user
end
In a test I do this:
user = Factory.create :user
(1..5).each { Factory.create(:task, :user => user)}
The problem that I'm experiencing is that afterward user.tasks contains only one task.
I have tried defining the user factory like this:
Factory.define :user do |u|
u.tasks {|tasks| [tasks.association(:user)] }
end
and like this:
Factory.define :user do |u|
u.tasks {|tasks| [tasks.association(:user), tasks.association(:user)] }
end
In both cases Factory.create(:user) causes an infinite loop.
I think what you're doing is backwards -- you can create Tasks from the User, but not visa versa like you're trying to do. Conceptually, the association goes from the user to the task.
What I did was create a special factory for that type of user.
Factory.define :user_with_tasks, :parent => :user do |user|
user.after_create {|u| 5.times { Factory.create(:task, :user => user) } }
end
Also, if you just want to use the default factory, you just do this
Factory.define :task do |f|
f.user :user
end
No need to specify the factory
UPDATE: Looks like others have done something similar in the past:
Populating an association with children in factory_girl
Ah, this works:
Factory.define :task do |t|
t.association :user
t.after_create {|t| t.user.tasks << t}
end
John, your approach would work but I actually couldn't use what you described because in my test the tasks need to also reference another (unmentioned) model and I don't see a way of referencing it in the user_with_tasks factory definition.