using scope in Rails custom validations - ruby-on-rails

I want to apply scope limiter in my custom validation
I have this Product Model
which has make,model,serial_number, vin as a attributes
Now I have a custom validation to check against vin if vin is not present to check for combination of make+model+serial_number uniqueness in database something like this
validate :combination_vin,:if => "vin.nil?"
def combination_vin
if Product.exists?(:make => make,:model => model,:serial_number => serial_number)
errors.add(:base,"The Combination of 'make+model+serial_number' already present")
end
end
I want to introduce a scope in this validator against user_id
Now I know I could easily write this to achieve same using
def combination_vin
if Product.exists?(:make => make,:model => model,:serial_number => serial_number,:user_id => user_id)
errors.add(:base,"The Combination of 'make+model+serial_number' already present")
end
end
But out of curiosity I was thinking is there a scope validator (something like {:scope => :user_id}) on custom validation
so that I dont have to pass that extra user_id in the exists? hash
Thanks

Try :
validate :combination_vin , :uniqueness => { :scope => :user_id } , :if => "vin.nil?"

Related

When does validate method get called in Rails?

I have a model StudentProductRelationship. I am adding a custom validator
validate :validate_primary_product , :if => "!primary_product"
The method is
def validate_primary_tag
unless StudentProductRelationship.exists?(:primary_product => true, :student_id => student_id)
errors.add(:base,"There is no primary product associated to product")
else
end
end
primary_product is a boolean field. I want to validate presence of at least one true primary_product for student_id. The problem is if I have an StudentProductRelationship object say spr with primary_product = true. If I do spr.update_attributes(primary_product: false). The validation does not raise an error because StudentProductRelationship.exists?(:primary_product => true, :student_id => student_id) exists beacuse spr still exists in db with primary_product = true. How do i surpass this?
Doesn't validates_presence_of :primary_product, scope: :student_id work for your?

How do I validate_uniqueness_of when scoped to two attributes only when one attribute on matching existing records does not equal a specific value?

I have an Invitation model that represents an invitation to join a subscription. There should only be one Invitation at any given time with a specific email / subscription_id combination unless the other records with the matching email / subscription_id also have a state of 'declined'.
I can currently validate for uniqueness given that the email and subscription_id combination is unique:
My Invitation model:
validates :email, :uniqueness => { :scope => :subscription_id }
Rspec (passes):
it { should validate_uniqueness_of(:email).scoped_to(:subscription_id) }
However, I want to skip the uniqueness check if the matching model(s) in the database have a state that is equal to 'declined'.
If the existing model's state is 'declined', the validation should pass.
The first thing that comes to mind is:
validates :email, :uniqueness => { :scope => :subscription_id },
:unless => lambda { |asset| asset.state == 'declined' }
But this is wrong because it checks if the newly created model has a state of 'declined', I want to check if the previously existing records have a state of 'declined'.
I also tried this:
validates :email, :uniqueness => { :scope => :subscription_id, :message => 'subscriptionery do' },
:if => lambda { |asset| asset.state == 'declined' }
But that fails for what I assume is the same reason.
How would I write a validation that checks an additional scope?
I feel like writing something like the following, but this is just made up syntax to help explain my idea:
it { should validate_uniqueness_of(:email).scoped_to(:subscription_id) }
unless MyModel.where(:email == new_object.email,
:subscription_id == new_object.subscription_id,
:state == 'declined')
Update:
I did this and it worked:
validates :email, uniqueness: { scope: :subscription_id, message: 'The email address %{value} is already associated with this subscription.' }, if: :state_of_others_are_not_declined?, on: :create
def state_of_others_are_not_declined?
Invitation.where(email: email).where(subscription_id: subscription_id).where.not(state: 'declined').any?
end
How does this work for you;
validate :unique_email_with_subscription_and_state
def unique_email_with_subscription_and_state
errors.add(:email,"YOUR MESSAGE") if Invitation.where(email: self.email, subscription_id: self.subscription_id).where.not(state: 'declined').any?
end
This will select all Invitiations where the the email matches, subscription_id matches and the state is not declined. If it finds any it will add an error to :email. Something like this
"SELECT invitations.* FROM invitatations WHERE email = 'me#example.com' AND subscription_id = 2 AND state <> 'declined'"
Is that the desired result?

Rails - Custom Validation For Having A Single Value Once

so I have these two models:
class Tag < ActiveRecord::Base
has_many :event_tags
attr_accessible :tag_id, :tag_type, :value
end
class EventTag < ActiveRecord::Base
belongs_to :tag
attr_accessible :tag_id, :event_id, :region
end
and this table for Tags:
**tag_id** **tag_type** **value**
1 "funLevel" "Boring..."
2 "funLevel" "A Little"
3 "funLevel" "Hellz ya"
4 "generic" "Needs less clowns"
5 "generic" "Lazer Tag"
...
What I would like to do is write a custom validation where it checks to see:
Each event_id has only one tag_type of "funLevel" attached to it, but can have more than one "generic" tags
For example:
t1 = EventTag.new(:tag_id => 1, :event_id =>777, :region => 'US')
t1.save # success
t2 = EventTag.new(:tag_id => 2, :event_id =>777, :region => 'US')
t2.save # failure
# because (event_id: 777) already has a tag_type of
# "funLevel" associated with it
t3 = EventTag.new(:tag_id => 4, :event_id =>777, :region => 'US')
t3.save # success, because as (tag_id:4) is not "funLevel" type
I have come up with one ugly solution:
def cannot_have_multiple_funLevel_tag
list_of_tag_ids = EventTag.where("event_id = ?", event_id).pluck(:tag_id)
if(Tag.where("tag_id in ?", list_of_tag_ids).pluck(:tag_type).include? "funLevel")
errors.add(:tag_id, "Already has a Fun Level Tag!")
end
Being new to rails, is there a more better/more elegant/more inexpensive way?
The way you have your data structured means that the inbuilt Rails validations are probably not going to be a heap of help to you. If the funLevel attribute was directly accessible by the EventTag class, you could just use something like:
# event_tag.rb
validate :tag_type, uniqueness: { scope: :event_id },
if: Proc.new { |tag| tag.tag_type == "funLevel" }
(unfortunately, from a quick test you don't seem to be able to validate the uniqueness of a virtual attribute.)
Without that, you're probably stuck using a custom validation. The obvious improvement to the custom validation you have (given it looks like you want to have the validation on EventTag) would be to not run the validation unless that EventTag is a funLevel tag:
def cannot_have_multiple_funLevel_tag
return unless self.tag.tag_type == "funLevel"
...
end

Rails3: Defining enum and using it as a custom type for db column

New to rails so not sure what the best approach is here. I want to define a simple c++ style enum which can then be used as a custom type in my db. The enum can be simulated with an array or a custom module but how do I go about turning that into a custom type for my table?
Here's a pattern I follow in rails:
In my model class, I add a module to hold the possible values of the column. Then I put them into an array and define validation against the array of possible values.
Imagine I have a column/attribute called status and it can be three possible values. I'd do this:
class MyModel < ActiveRecord::Base
# This validates that status can't be null
validates :status, :presence => true
# Define a module with all possible values
module Status
IN_DEVELOPMENT = 'in development'
DISABLED = 'disabled'
ACTIVE = 'active'
end
# Now create an array of possible status values, and add a validation
STATUSES = [ Status::DISABLED, Status::ACTIVE, Status::IN_DEVELOPMENT]
validates :status, :inclusion => { :in => STATUSES, :message => "%{value} is not a valid status value" }
end
Have you considered using the built-in enumeration support in your database? Lots of common RDMBSes have enum support, such as Postgres (see http://www.postgresql.org/docs/9.1/static/datatype-enum.html) and MySQL (see http://dev.mysql.com/doc/refman/5.5/en/enum.html). With that, you can directly create the type in your data store and then use it via one of the ActiveRecord plugins (such as enum_type for Postgres: https://github.com/riscfuture/enum_type).
Alternatively, you could use something like active_enum to structure the enumeration as you described and store fields as integers in the database.
Depending on how you plan to utilize this enum type in your code I've found that using scopes accomplishes close to the same thing along with an enum type in the database to ensure only specific values are set.
Example:
scope :trial, :conditions => { :utype => 'TRIAL' }
scope :registered, :conditions => { :utype => 'REGISTERED' }
scope :active, :conditions => { :status => 'ACTIVE' }
scope :abuse, :conditions => { :status => 'ABUSE' }

acts_as_taggable_on with permissions required to create new tags

How can I prevent users from adding new tags which don't already exist in the tags db?
I want them to be able to add any tags that already exist to another model which they can fully edit, but not be able to create new tags if they don't yet exist?
I'm using declarative_auth so some users with permissions should be create to add whatever tags they want.
user.rb
acts_as_tagger
post.rb
acts_as_taggable_on :features
https://github.com/mbleigh/acts-as-taggable-on
UPDATE:
This seems to do it except I can't get the error message variable to work:
validates :feature_list, :inclusion => {
:in => SomeModel.tag_counts_on(:features).map(&:name),
:message => "does not include {s}" }
I havn't used acts_as_taggable, but can you pass normal rails validations?
# LIKE is used for cross-database case-insensitivity
validates_inclusion_of :name => lambda { find(:all, :conditions => ["name LIKE ?", name]) }
Could probably be more robust and rails validation like but this works:
validate :valid_feature_tag
def valid_feature_tag
invalid_tags = false
feature_list.each do |tag|
list = SomeModel.tag_counts_on(:features).map(&:name)
unless list.include?(tag)
invalid_tags = true
end
end
unless invalid_tags == false
errors.add(:feature_list, 'cannot contain new tags, please suggest new tags to us')
return false
else
return true
end
end
Here's an efficient and clean way to enforce allowed tags:
validate :must_have_valid_tags
def must_have_valid_tags
valid_tags = ActsAsTaggableOn::Tag.select('LOWER(name) name').where(name: tag_list).map(&:name)
invalid_tags = tag_list - valid_tags
if invalid_tags.any?
errors.add(:tag_list, "contains unknown tags: [#{invalid_tags.join(', ')}]")
end
end

Resources