passing object for polymorphic lookup parameter in Rails find/where - ruby-on-rails-3

Let's say I have:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Article < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Photo < ActiveRecord::Base
has_many :comments, :as => :commentable
#...
end
now I want to find all comments on Jim's photo:
#jims_photo = Photo.where(:of => "Jim")
#photo_comments = Comment.where(:commentable => #jims_photo)
this seems to not work in rails (Rails 3). The generated query seems to not expand the polymorphic object into commentable_id and commentable_type fields:
SQLite3::SQLException: no such column: comments.commentable:
I'm new to ruby and rails so I might be using the paradigm incorrectly but my expectation was that rails automatically expands
:commentable => #jims_photo
to:
:commentable_id => #jims_photo.id, :commentable_type => #jims_photo.class.name

If you want to be really safe with:
:commentable_id => #jims_photo.id, :commentable_type => #jims_photo.class.name
Then I'd recommend replacing .class.name with .base_class (you don't actually need the name: to_s returns name and will be called automatically).
The reason for this is that when ActiveRecord saves the _type for a polymorphic association it'll use base_class to ensure it's not saving a class which itself is a polymorphic one.
If you play with store_full_sti_class you'll probably have to take even more precautions.
I highly recommend looking at Rails' STI code, here.

The guides for Rails are one of the best so I'd suggest you start reading about Polymorphic Associations
You class declarations looks OK and I'm assuming that you're migrations is as well. But just for the sake of it. Let's say it looks like this:
class CreateComment < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :name
t.references :commentable, :polymorphic => true
# this is the equivalent of
# t.integer :commentable_id
# t.string :commentable_type
t.timestamps
end
end
end
Not if you have a Article or Photo object and you want to get the comments for that object then Thilo's suggestion is right on. All you need to do is this: #jims_photo.comments
If, on the other hand, you have a an instance of the Comment model, you can get the parent like this: #comment.commentable. But if you want to get Jim's photo comments best to do it like that. Otherwise, you'd have to supply as arguments both the :commentable_id and commentable_type. I'm not aware of any finder that expands the polymorphic object into commentable_id and commentable_type fields for you.
But you can always create a class method for that:
def self.find_by_parent(parent)
where(:commentable_id => parent.id, :commentable_type => parent.class.name)
end

Related

rails spree has_many through association

'm trying to tweek Spree to my needs and I've hit a bump. I'll try to explain as best as I can without pasting too much code, 'cause Spree has quite a lot of it...
For starters. I've extended OptionType like this
Spree::OptionType.class_eval do
PRODUCT_TYPES = SuperMap.new( [:default, 0],
[:vehicle_bonnet, 1],
[:vehicle_images, 2])
super_mapped_attr :product_type, PRODUCT_TYPES
attr_accessible :product_type
end
I want to check in the show view of the ProductsController what sort of product_type I'm dealing with, like this:
<% if #product.option_types.product_type_key == :vehicle_bonnet %>
...
<% end %>
and I get undefined method ``product_type_key'. This method should be available thanks to SuperMap.
Now, the Product and OptionType has a bit strange association (for me at least).
module Spree
class Product < ActiveRecord::Base
has_many :product_option_types, :dependent => :destroy
has_many :option_types, :through => :product_option_types
end
end
so far seems to be ok.
module Spree
class ProductOptionType < ActiveRecord::Base
belongs_to :product
belongs_to :option_type
acts_as_list :scope => :product
end
end
and now comes the weird part:
module Spree
class OptionType < ActiveRecord::Base
has_many :option_values, :order => :position, :dependent => :destroy
has_many :product_option_types, :dependent => :destroy
has_and_belongs_to_many :prototypes, :join_table => 'spree_option_types_prototypes'
attr_accessible :name, :presentation, :option_values_attributes
validates :name, :presentation, :presence => true
default_scope :order => "#{self.table_name}.position"
accepts_nested_attributes_for :option_values, :reject_if => lambda { |ov| ov[:name].blank? || ov[:presentation].blank? }, :allow_destroy => true
end
end
this is actually the whole OptionType model. What suprises me it doesn't have has_many :products, through: :product_option_types. I've tried to add this line to my extension, but didn't help. I've tried few other thing of course, but it just made such a mess I went back to this version, since I think it shows best what I'm trying to achieve. What am I missing?
EDIT (solved, I should hope so...):
Turned out that in the Product model there was an alias alias :options :product_option_types, which was (imho) in the wrong place, because somewhere in the middle of a quite a long model (I should think it should have been somewhere closer to the has_many, but that's just me).
Further more, the options turned out to be an array, no that shocking come to think of it. So, what I did is extended the product model, added a method, where I'm checking the product_type_key, like so:
def type_of_product?(type)
options.each do |opts|
if opts.option_type.product_type_key == type
return true
end
end
false
end
Not the prettiest method, but it works as I expect it to...

has_many association with conditions on ActiveRecord

I have this models: Store and Address.
The second model Address I'm using this with other models and has some custom fields inside for different models.
Yes like polymorphic but without the varchar field for Class, I'm using an integer. (optimization stuff)
now on my Store model the association in set like this:
class Store < ActiveRecord::Base
has_many :addresses, :foreign_key => "parent_id", :conditions => ['parent_kind = ?', 2]
accepts_nested_attributes_for :addresses
end
Now in my controller I do:
#store.addresses.build
And I can use f.fields_for :addresses... inside the form.
The problem comes out when I submit the form and the data is saved to the database.
The record in the stores table is saved, the record in addresses is saved with the parent_id of the store in place, but the parent_kind is in 0 which is the default value for that attribute on MySQL.
My Quick fix was this:
#store = Store.new(params[:store])
#store.addresses[0].parent_kind = 2
if #store.save
....
But I know there must be another way.
Anny suggestions?
Thanks.
Did you try using a hidden field in your nested form?
<%= f.fields_for :addresses, Address.new do |ff| %>
<%= ff.hidden_field :parent_kind, :value => 2 %>
...
EDIT
If you don't want to use it due to security concerns, here's another way that might help. You can try using an association callback like so:
class Store < ActiveRecord::Base
has_many :addresses, :foreign_key => "parent_id",
:conditions => ['parent_kind = ?', 2],
:before_add => Proc.new { |store,address| address.parent_kind = 2}
accepts_nested_attributes_for :addresses
end

Problems with validations

I have 2 models
class Variant < ActiveRecord::Base
belongs_to :product
with_options :if => :is_active? do |p_active|
p_active.validates :avatar, :presence => true
end
with_options :if => :isnt_diavoleria? do |p_active|
p_active.validates :color, :presence => true
end
def is_active?
self.product.active
end
def isnt_diavoleria?
a = (self.is_active? and self.product.section_id != 5)
a
end
end
class Product < ActiveRecord::Base
has_many :variants, :autosave => true
accepts_nested_attributes_for :variants
end
If i change the attribute section_id or active of a product and save, the validations of the model variant are executed with the old values of section_id and active.
Why?
How can i do the validations with the new values?
The problem is that by default a pair of has_many and belongs_to associations don't know that they are the inverse of each other. So when you
product.section_id = 23
product.save
then inside your validation, the variant goes
self.product
and actually fetches that from the database again, which obviously doesn't have your unsaved change.
You should be able to fix this by adding the :inverse_of flag to your associations, i.e.
class Variant < AR::Base
belongs_to :product, :inverse_of => :variants
end
class Product < AR::Base
has_many :variants, :inverse_of => :products
end
One day rails will have an identity map which should make this sort of stuff less error prone (it is in rails 3.1 but disabled because of subtle associated bugs if i remember correctly)
You probably need to do what #thoferon is suggesting (assuming you aren't doing taking nested attributes for products or something) or make sure all changes to the product are happening through the association object so it is up-to-date.
Maybe you are modifying a product through another Ruby object. The product referenced by the variant is still holding the old values. I don't know if this is what you're doing but it could be the case.
A solution could be to reload the product before validation.
class Variant
before_validation do
self.product.reload
end
end

Polymorphic :has_many, :through as module in Rails 3.1 plugin

I've search everywhere for a pointer to this, but can't find one. Basically, I want to do what everyone else wants to do when they create a polymorphic relationship in a :has_many, :through way… but I want to do it in a module. I keep getting stuck and think I must be overlooking something simple.
To wit:
module ActsPermissive
module PermissiveUser
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def acts_permissive
has_many :ownables
has_many :owned_circles, :through => :ownables
end
end
end
class PermissiveCircle < ActiveRecord::Base
belongs_to :ownable, :polymorphic => true
end
end
With a migration that looks like this:
create_table :permissive_circles do |t|
t.string :ownable_type
t.integer :ownable_id
t.timestamps
end
The idea, of course, is that whatever loads acts_permissive will be able to have a list of circles that it owns.
For simple tests, I have
it "should have a list of circles" do
user = Factory :user
user.owned_circles.should be_an_instance_of Array
end
which fails with:
Failure/Error: #user.circles.should be_an_instance_of Array
NameError: uninitialized constant User::Ownable
I've tried: using :class_name => 'ActsPermissive::PermissiveCircle' on the has_many :ownables line, which fails with:
Failure/Error: #user.circles.should be_an_instance_of Array
ActiveRecord::HasManyThroughSourceAssociationNotFoundError:
Could not find the source association(s) :owned_circle or
:owned_circles in model ActsPermissive::PermissiveCircle.
Try 'has_many :owned_circles, :through => :ownables,
:source => <name>'. Is it one of :ownable?
while following the suggestion and setting :source => :ownable fails with
Failure/Error: #user.circles.should be_an_instance_of Array
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError:
Cannot have a has_many :through association 'User#owned_circles'
on the polymorphic object 'Ownable#ownable'
Which seems to suggest that doing things with a non-polymorphic-through is necessary. So I added a circle_owner class similar to the setup here:
module ActsPermissive
class CircleOwner < ActiveRecord::Base
belongs_to :permissive_circle
belongs_to :ownable, :polymorphic => true
end
module PermissiveUser
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def acts_permissive
has_many :circle_owners, :as => :ownable
has_many :circles, :through => :circle_owners,
:source => :ownable,
:class_name => 'ActsPermissive::PermissiveCircle'
end
end
class PermissiveCircle < ActiveRecord::Base
has_many :circle_owners
end
end
With a migration:
create_table :permissive_circles do |t|
t.string :name
t.string :guid
t.timestamps
end
create_table :circle_owner do |t|
t.string :ownable_type
t.string :ownable_id
t.integer :permissive_circle_id
end
which still fails with:
Failure/Error: #user.circles.should be_an_instance_of Array
NameError:
uninitialized constant User::CircleOwner
Which brings us back to the beginning.
How can I do what seems to be a rather common polymorphic :has_many, :through on a module?
Alternatively, is there a good way to allow an object to be collected by arbitrary objects in a similar way that will work with a module?
It turns out that adding :class_name to both :has_many definitions will actually work (someone commented on that, but they deleted their comment). It didn't work in my non-simplified program because of something else in my program that was causing a cascading error that SEEMED to be local to the :has_many definition.
Short story: It was a lot of trouble for something that wasn't actually a problem. Blech

Mixing has_one and has_and_belongs_to_many associations

I'm trying to build a database of urls(links). I have a Category model that has and belongs to many Links.
Here's the migration I ran:
class CreateLinksCategories < ActiveRecord::Migration
def self.up
create_table :links_categories, :id => false do |t|
t.references :link
t.references :category
end
end
def self.down
drop_table :links_categories
end
end
Here's the Link model:
class Link < ActiveRecord::Base
validates :path, :presence => true, :format => { :with => /^(#{URI::regexp(%w(http
https))})$|^$/ }
validates :name, :presence => true
has_one :category
end
Here's the category model:
class Category < ActiveRecord::Base
has_and_belongs_to_many :links
end
And here's the error the console kicked back when I tried to associate the first link with the first category:
>>link = Link.first
=> #<Link id: 1, path: "http://www.yahoo.com", created_at: "2011-01-10...
>>category = Category.first
=> #<category id : 1, name: "News Site", created_at: "2011-01-11...
>>link.category << category
=> ActiveRecord::StatementInvalid: SQLite3::Exception: no such column :
categories.link_id:
Are my associations wrong or am I missing something in the database? I expected it to find the links_categories table. Any help is appreciated.
Why are you using HABTM here at all? Just put a belongs_to :category on Link, and a has_many :links on Category. Then in the db, you don't need a join table at all, just a :category_id on the links table.
But, if you do want a HABTM here, from a quick glance, the first thing I noticed is that your join table is named incorrectly -- it should be alphabetical, categories_links.
The second thing is that you can't mix has_one and has_and_belongs_to_many. HABTM means just that -- A has many of B and A belongs to many of B; this relationship implies that the opposite must also be true -- B has many of A and B belongs to many of A. If links HABTM cateogries, then categories must HABTM links.
See http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many