I am fairly new to Rails validations. I have an Activity model that has many attributes (listed in attributes array below). I need to validate that every activity has a name and a at least one of the other attributes. I was think of something like the following but it looks a little messy. Any advice?
class Activity < ActiveRecord::Base
belongs_to :user
validate :valid_activity
def valid_activity
attributes = [reps, distance, meal, post_meal, yoga, reminder, duration]
if name.present? && self.include? (activity)
end
end
end
You would create a new validator class like so
class ActivityValidator < ActiveModel::Validator
def valid_activity
attributes = [:reps, :distance, :meal, :post_meal, :yoga, :reminder, :duration]
unless name.present? && attributes.any?{ |a| self.activity == a }
errors[:user] << 'Need to add an activity'
end
end
end
Then in your user.rb file, include the validator module and use the validates_with method.
include ActiveModel::Validations
validates_with ActivityValidator
Related
How can I use custom validation between two different models in Ruby on Rails?
I have two models how can I validate columns between these two models.
The cleanest way is to create separate validator class instead of just a custom validation method. A validator is really just a subclass of ActiveModel::Validator or ActiveModel::EachValidator that implements #validate or #validate_each:
class TitleCaseValidator < ActiveModel::Validator
def validate(record)
unless record.name[0] == record.name[0].upcase
record.errors[:title] << 'must be title cased'
end
end
end
class Album
validates_with TitleCaseValidator
end
class Book
validates_with TitleCaseValidator
end
class TitleCaseValidator < ActiveModel::EachValidator
def validate(record, attribute, value)
unless value[0] == value[0].upcase
record.errors[attribute] << 'must be title cased'
end
end
end
class Album
validates :title, title_case: true
end
class Artist
validates :name, title_case: true
end
See Performing Custom Validations.
I have the following method called capitalizeEachWord. Inside this method there is an attribute called company
class BusCompany < ActiveRecord::Base
attr_accessible :company
before_save :capitalizeEachWord
validates :company,presence: true,
uniqueness: { case_sensitive: false },
format: /^([a-zA-z0-9]+\s?){1,}$/
def capitalizeEachWord
self.company=self.company.downcase.split.map(&:capitalize).join(' ')
end
end
I would like that this method not use the attribute company directly, but receives this attribute as a parameter for doesn't do it dependent of the model BusCompany. Something as the following. The problem is that this method I going to use in various models and don't want to write it in each model but use the inheritance
class BusCompany < ActiveRecord::Base
attr_accessible :company
before_save :capitalizeEachWord(self.company)
validates :company,presence: true,
uniqueness: { case_sensitive: false },
format: /^([a-zA-z0-9]+\s?){1,}$/
def capitalizeEachWord(attribute)
self.attribute=self.attribute.downcase.split.map(&:capitalize).join(' ')
end
end
Add the following code into config/initializers/capitalizer.rb
module Capitalizer
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def capitalize(*attributes)
#attributes_to_capitalize = attributes
before_save :capitalize_each_word
end
def attributes_to_capitalize
Array.new(#attributes_to_capitalize)
end
end
def capitalize_each_word
self.class.attributes_to_capitalize.each do |attr|
if value = send(attr)
self.send("#{attr}=", value.strip.titleize)
end
end
end
end
And then in your class:
class BusCompany < ActiveRecord::Base
include Capitalizer
capitalize :company
...
end
First, I'd recommend you override the setter for company instead of using error prone callbacks, like this:
class BusCompany < ActiveRecord::Base
# you can also use #titleize instead of capitalize each word
# also use try, in case `arg` is nil
def company=(arg)
super arg.try(:titleize)
end
end
Then you can use modules to wrap this functionality into a reusable unit. Throw this in a file in your concerns folder, or just in to the models folder:
module CapitalizedSetter
def capitalize_setter(*attr_names)
# for each attr name, redifine the setter so it supers the titleized argument instead
attr_names.each do |attr|
define_method(:"#{attr}=") { |arg| super arg.try(:titleize) }
end
end
end
Finally extend it into the desired models:
class BusCompany
extend CapitalizedSetter
capitalized_setter :company
end
My application has a form_for tag with element :foo that is not saved in the model for the object used in form_for.
I need to confirm that the user submitted a value for this element, using Rails Validation Helpers. However, the 'presence' validator makes a call to object.foo to confirm that it has a value. Since foo is not saved as part of my object, how can I do this validation?
Thanks!
You should probably check for the presence of it in the params in your controller action:
def create
#model = MyModel.find(params[:id])
unless params[:foo].present?
#model.errors.add(:foo, "You need more foo")
end
# ...
end
If :foo is an attribute of your object that isn't saved in the database and you really want to use ActiveRecord Validations, you can create an attr_accessor for it, and validate presence like this.
class MyModel < ActiveRecord::Base
attr_accessor :foo
validates :foo, presence: true
end
But that could result in invalid records being saved, so you probably don't want to do it this way.
Try this..
class SearchController < ApplicationController
include ActiveModel::ForbiddenAttributesProtection
def create
# Doesn't have to be an ActiveRecord model
#results = Search.create(search_params)
respond_with #results
end
private
def search_params
# This will ensure that you have :start_time and :end_time, but will allow :foo and :bar
params.require(:val1, :foo).permit(:foo, :bar , whatever else)
end
end
class Search < ActiveRecord::Base
validates :presence_of_foo
private
def presence_of_foo
errors.add(:foo, "should be foo") if (foo.empty?)
end
end
See more here
I'm trying to move my validations to a module. I want to extend an existing object an aribtrary set of validators, but I'm struggling to figure out how to get them to execute. Any ideas?
Active Record Object
class Test < ActiveRecord::Base
has_many :resources
end
Validator
module TestValidator
extend ActiveSupport::Concern
included do
validates_associated :resources
end
end
Console
t = Test.new
t.extend TestValidator
t.valid?
# true ... should be false
I hope this can help
6.1 Custom Validators
Custom validators are classes that extend ActiveModel::Validator. These classes must implement a validate method which takes a record as an argument and performs the validation on it. The custom validator is called using the validates_with method.
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.starts_with? 'X'
record.errors[:name] << 'Need a name starting with X please!'
end
end
end
class Person
include ActiveModel::Validations
validates_with MyValidator
end
The easiest way to add custom validators for validating individual attributes is with the convenient ActiveModel::EachValidator. In this case, the custom validator class must implement a validate_each method which takes three arguments: record, attribute and value which correspond to the instance, the attribute to be validated and the value of the attribute in the passed instance.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "is not an email")
end
end
end
class Person < ActiveRecord::Base
validates :email, :presence => true, :email => true
end
As shown in the example, you can also combine standard validations with your own custom validators.
https://guides.rubyonrails.org/active_record_validations.html#custom-validators
Hey folks, following problem with Rails and STI:
I have following classes:
class Account < AC::Base
has_many :users
end
class User < AC::Base
extend STI
belongs_to :account
class Standard < User
before_save :some_callback
end
class Other < User
end
end
module STI
def new(*args, &block)
type = args.dup.extract_options!.with_indifferent_access.delete(:type)
if type.blank? or (type = type.constantize) == self
super(*args, &block)
else
type.new(*args, &block)
end
end
end
And now the problem:
Without rewriting User.new (in module STI), the callback inside User::Standard gets never called, otherwise the account_id is always nil if I create users this way:
account.users.create([{ :type => 'User::Standard', :firstname => ... }, { :type => 'User::Other', :firstname => ... }])
If I'm using a different approach for the module like:
module STI
def new(*args, &block)
type = args.dup.extract_options!.with_indifferent_access.delete(:type)
if type.blank? or (type = type.constantize) == self
super(*args, &block)
else
super(*args, &block).becomes(type)
end
end
end
Then instance variables are not shared, because it's creating a new object.
Is there any solution for this problem without moving the callbacks to the parent class and checking the type of class?
Greetz
Mario
Maybe there's something I don't know, but I've never seen Rails STI classes defined in that manner. Normally it looks like...
app/models/user.rb:
class User < AC::Base
belongs_to :account
end
app/models/users/standard.rb:
module Users
class Standard < User
before_save :some_callback
end
end
app/models/users/other.rb:
module Users
class Other < User
end
end
It looks as though you are conflating class scope (where a class "lives" in relation to other classes, modules, methods, etc.) with class inheritance (denoted by "class Standard < User"). Rails STI relationships involve inheritance but do not care about scope. Perhaps you are trying to accomplish something very specific by nesting inherited classes and I am just missing it. But if not, it's possible it's causing some of your issues.
Now moving on to the callbacks specifically. The callback in Standard isn't getting called because the "account.users" relationship is using the User class, not the Standard class (but I think you already know that). There are several ways to deal with this (I will be using my class structure in the examples):
One:
class Account
has_many :users, :class_name => Users::Standard.name
end
This will force all account.users to use the Standard class. If you need the possibility of Other users, then...
Two:
class Account
has_many :users # Use this to look up any user
has_many :standard_users, :class_name => Users::Standard.name # Use this to look up/create only Standards
has_many :other_users, :class_name => Users::Other.name # Use this to look up/create only Others
end
Three:
Just call Users::Standard.create() and Users::Other.create() manually in your code.
I'm sure there are lots of other ways to accomplish this, but there are probably the simplest.
So I solved my problems after moving my instance variables to #attributes and using my second approach for the module STI:
module STI
def new(*args, &block)
type = args.dup.extract_options!.with_indifferent_access.delete(:type)
if type.blank? or (type = type.constantize) == self
super(*args, &block)
else
super(*args, &block).becomes(type)
end
end
end
class User < AR:Base
extend STI
belongs_to :account
validates :password, :presence => true, :length => 8..40
validates :password_digest, :presence => true
def password=(password)
#attributes['password'] = password
self.password_digest = BCrypt::Password.create(password)
end
def password
#attributes['password']
end
class Standard < User
after_save :some_callback
end
end
Now my instance variable (the password) is copied to the new User::Standard object and callbacks and validations are working. Nice! But it's a workaround, not really a fix. ;)