Is it posible to validate the uniqueness of a child model's attribute scoped against a polymorphic relationship?
For example I have a model called field that belongs to fieldable:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => :fieldable_id
end
I have several other models (Pages, Items) which have many Fields. So what I want is to validate the uniqueness of the field name against the parent model, but the problem is that occasionally a Page and an Item share the same ID number, causing the validations to fail when they shouldn't.
Am I just doing this wrong or is there a better way to do this?
Just widen the scope to include the fieldable type:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => [:fieldable_id, :fieldable_type]
end
You can also add a message to override the default message, or use scope to add the validation:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :fieldable_id, :scope => [:fieldable_id, :fieldable_type], :message => 'cannot be duplicated'
end
As a bonus if you go to your en.yml, and enter:
activerecord:
attributes:
field:
fieldable_id: 'Field'
You are going to replace the default 'subject' that rails add to the errors with the one you specify here. So instead of saying: Fieldable Id has been already taken or so, it would say:
Field cannot be duplicated
Related
I'm creating a polling system. I would like all options to be made unique, but only within their respective Poll. I'm using a proc to validate that they are not blank:
class Poll < ActiveRecord::Base
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['option'].blank? }
end
But I'm not sure how to validate their uniqueness. I tried doing it within the Option model but it's not rejecting duplicate options created through the Poll form's f.fields_for:
class Option < ActiveRecord::Base
belongs_to :poll
validates_uniqueness_of :option, scope: :poll_id
end
Is it possible to do it with proc?
Apply uniqueness validation upon option attributes like validates_uniqueness_of :title, :other, scope: :poll_id
You can pass any condition to that proc. So you could do query for that column and see whether any results are returned and reject if it they are.
In my rails projects I have a lot of association tables. And I have some validations. Nothing really difficult, and it works almost every times.
But from time to time (like tonight), I have to switch from
validates_presence_of :project_id
validates_presence_of :tag_id
validates_uniqueness_of :project_id, :scope => [:tag_id]
to
validates_presence_of :project
validates_presence_of :tag
validates_uniqueness_of :project, :scope => [:tag]
Do you know the difference ? Do you if one is better than the other ?
From the Rails Guides: http://guides.rubyonrails.org/active_record_validations.html#presence
2.9 presence This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either
nil or a blank string, that is, a string that is either empty or
consists of whitespace.
class Person < ActiveRecord::Base
validates :name, :login, :email, presence: true
end
If you want to be sure that an association is present, you'll need to
test whether the associated object itself is present, and not the
foreign key used to map the association.
class LineItem < ActiveRecord::Base
belongs_to :order
validates :order, presence: true
end
So, you should use the second example you gave, which tests if the associated object itself is present, and not the first example, which only tests if the foreign key used to map the association is present.
Is this even possible?
I have a mongoid class named Magazine, with some associations as well, that I would like to re-name to Publication. Problem is that I already have a bunch of users who have already made magazines, issues and articles.
Original Magazine model:
class Magazine
# 1. Include mongoid stuff
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Slug
# 2. Define fields
field :title, type: String
field :description, type: String
field :live, type: Boolean, default: false
field :show_walkthrough, type: Boolean, default: true
# 3. Set attributes accesible
attr_accessible :title, :description, :live, :show_walkthrough, :cover_image_attributes, :logo_image_attributes
# 4. Set slug
slug :title
# 5. Set associations
belongs_to :user
has_many :issues, dependent: :delete, autosave: true
has_one :foreword, :as => :articleable, :class_name => 'Article', dependent: :delete, autosave: true
embeds_one :cover_image, :as => :imageable, :class_name => 'Image', cascade_callbacks: true, autobuild: true
embeds_one :logo_image, :as => :imageable, :class_name => 'Image', cascade_callbacks: true, autobuild: true
# 6. Accepting nested attributes
accepts_nested_attributes_for :cover_image, :allow_destroy => true
accepts_nested_attributes_for :logo_image, :allow_destroy => true
# 7. Set validations
validates_presence_of :title, :description, :cover_image, :logo_image
end
I know I can change the class-name to Publication and then do db.magazines.renameCollection( "publications" ) on the mongodb, but the associations doesn't follow along.
Any suggestions?
I looks like you have association fields in your Issue and Foreword models that probably refer to Magazine. So if you are happy enough to change the name of the class and underlying collection then renaming these association fields is your main problem. You may have something like:
class Issue
belongs_to :magazine
end
You could redefine this association as belongs_to :publication. Assuming that are happy to fix all the references to Issue#magazine in your code then your remaining problem is that your issues collection will be full of documents that have a magazine_id field instead of publication_field. You have two options to fix the database mapping.
First option is to rename the field in the database. See mongoDB : renaming column name in collection
The second option is to declare the association so that it maps to the old database field by overriding the 'foreign key' name:
belongs_to :publication, foreign_key: :magazine_id
You will have to repeat this for the Foreword model and any others that reference Magazine.
Just a heads up for polymorphism and class inheritance.
Mongoid handles inheritance and polymorphic associations by storing the class name as a document attribute.
On the class itself, this is stored as the "_type" attribute
For polymorphic associations like belongs_to :polymorphic_class mongoid adds an attribute "polymorphic_class_type", so that the class can be resolved (with Rails' .constantize) when browsing polymorphic associations.
So if you decide to change the class name, and you have inheritance or polymorphic associations, well you'll have to also rewrite all those attributes !
I have the following Models:
Language
Itemtype
Item
belongs_to :itemtype
LocalisedItem
belongs_to :item
belongs_to :language
The LocalisedItem model has an attribute called "title".
I want to validate the uniqueness of said "title" attribute. My problem is the scope: It´s supposed to be unique per language (easy) and itemtype, which I could not figure out how to do until now.
My best try...
validates :title, :uniqueness => { :scope => [:language_id, 'item.itemtype_id'] }
...fails with "NoMethodError: undefined method `item.itemtype_id'".
Is there any way to check for uniqueness in the way described?
You can use this format for validate uniqueness with a scope:
validates_uniqueness_of :title, :scope => :language_id
Let's say you have a SiteUpdate and a Comment model, and you want to make Comment polymorphic. You make comment hold a "commentable_id" and "commentable_type"...
Here's our comment model:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
validates_presence_of :content
validates_presence_of :commentable
end
Here is our SiteUpdate:
class SiteUpdate < ActiveRecord::Base
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
has_many :comments, :as => :commentable
validates_presence_of :subject
validates_length_of :subject, :maximum => 80
validates_presence_of :intro
validates_length_of :intro, :maximum => 200
validates_presence_of :text
validates_presence_of :author
scope :sorted, order("site_updates.created_at desc")
end
Does Rails link the commentable to the site_update instance, or do I have to do that manually?
#site_update.comments << Factory.build(:comment, :commentable_id => nil)
#site_update.save
This fails -> it complains that the comment.commentable_id should not be blank (I set this validation in the Comment model).
So do I do this manually, or do I set this up incorrectly?
Or do I just not validate it at all?
I'm making an assumption that your #site_update object is a new object. If so...
There's a somewhat annoying thing about rails associations. You can't really add them before the record is saved.
What's happening is, you have a new site update object without an ID. You build a new comment object for that site update, so it sets the commentable_type to "SiteUpdate", however, there's no ID yet, so it sets the commentable_id to nil. You save, and it bubbles out to save associated objects, but it doesn't set the comment commentable_id to the SiteUpdate ID, because it doesn't exist.
So if you change it around to be :
#site_update.save
#site_update.comments << Factory.build(:comment, :commentable_id => nil)
#site_update.comments.map { |c| c.save }
it should work.
If it's not a new record...it should work as is.