Why validate function is not called in Rails3.2.9? - ruby-on-rails

The codes are as follows:
class Seat < ActiveRecord::Base
attr_accessible :baggage, :flight_id, :name
def validate(record)
errors.add_to_base("You have too much baggage")
end
end
I expected it throws error whenever a new record is wriiten into the database.
However, nothing happened when new record wriiten into seats database by #seat.save
Does anyone have ideas about this?

validate(record) looks weird. You should try
class Seat < ActiveRecord::Base
attr_accessible :baggage, :flight_id, :name
validate :valid_baggage
def valid_baggage
errors.add_to_base("You have too much baggage")
end
end
Also note that you need to call #seat.save! (instead of #seat.save) in order to get the exception. #seat.save will return true or false only ... but that's usually what you want, so consider if you really want to raise an exception.

The record parameter is only needed if you are implementing a custom validator, otherwise the method already knows which record it is validating (self).

Related

What is the proper way to access virtual atributes in Ruby on Rails?

I have a model w/ a virtual attribute:
class Campaign < ActiveRecord::Base
def status
if deactivated
return "paused"
else
return "live"
end
end
end
now, in my view, when I access the attribute with campaign.status, I am getting the proper result. However, when I try to access it like this campaign[:status], I get nothing back.
Why is that?
[:status] uses the [] method in Ruby. 'def status' defines a method which shouldn't be mistaken with an ActiveRecord attribute or an virtual attribute (e.g. attr_reader or attr_accessor).
ActiveRecord adds the [] method to your class and makes all the (database) attributes accessible by object[:attr_name] AND object.attr_name(And even object.attributes[:attr_name]).
This is different from how f.e. Javascript works where obj[:method] is virtually the same as obj.method.
Edit: You should be able to use the attr_accessor if you use them for example in any form:
<%= form.input :status %>
Submitting the form will then set the instance variable #status. If you want to do anything with this before or after saving you can call an before_save or after_save hook:
class Campaign < ActiveRecord::Base
attr_accessible :status
attr_accessor :status
before_save :raise_status
def raise_status
raise #status
end
end
This will throw an error with the value submitted value for status.
Hope this helps.

Association won't save inside of before_validation

I'm trying to use the before_validation callback to adjust the number of child objects for a record, but for some reason, its not working the way I expect.
LineItem class:
before_validation :adjust_enrollment_count
def adjust_enrollment_count
if enrollments.size < quantity
(enrollments.size+1..quantity).each do |li|
self.enrollments.build(variant: self.variant)
end
#self.save
elsif enrollments.size > quantity
enrollments.delete_if do |e|
enrollments.size > quantity
end
end
end
What happens is that it creates the correct number of Enrollment objects as children to the LineItem, but the Variant gets set to nil (even though the LineItem has a variant defined).
Things I've tried:
Explicitly saving the line_item or the enrollment
"pry"ing into the callback and running the code manually (this actually worked the way
I expected!)
Verifying that "self" referred to the LineItem and not the closure
Is there something about the callback lifecycle that I'm missing? Is there a better way to adjust the number of Enrollment objects as the quantity changes on the LineItem?
Probably variant is not an accessible field of the Enrollment class. Try this way (also shortened)
def adjust_enrollment_count
while enrollments.size < quantity
self.enrollments.build(variant_id: self.variant) # note: variant_id
end
while enrollments.size > quantity
enrollments.pop # or .shift to delete from the head of the list
end
# don't save in a lifecycle callback, or you'll get in an awful loop
end
EDIT: a different take
def add_enrollment
enrollments.build(variant_id: variant)
end
def adjust_enrollment_count
enrollments.slice!(quantity, enrollments.size)
add_enrollment while enrollments.length < quantity
end
It turned out that the problem was something that I didn't have outlined in my question. I had defined the following:
class Enrollment < ActiveRecord::Base
belongs_to :line_item
attr_accessible :variant
attr_accessor :variant
end
I think the attr_accessor was creating an in-memory variable called variant that only lasted as long as the page load. I removed that and it seemed to solve the problem.

How to run validations of sub-class in Single Table Inheritance?

In my application, I have a class called Budget. The budget can be of many types.. For instance, let's say that there are two budgets: FlatRateBudget and HourlyRateBudget. Both inherit from the class Budget.
This is what I get so far:
class Budget < ActiveRecord::Base
validates_presence_of :price
end
class FlatRateBudget < Budget
end
class HourlyRateBudget < Budget
validates_presence_of :quantity
end
In the console, if I do:
b = HourlyRateBudget.new(:price => 10)
b.valid?
=> false
b.errors.full_messages
=> ["Quantity can't be blank"]
As, expected.
The problem is that the "type" field, on STI, comes from params.. So i need to do something like:
b = Budget.new(:type => "HourlyRateBudget", :price => 10)
b.valid?
=> true
Which means that rails is running validations in the super-class instead of instantiating the sub class after I set up the type.
I know that is the expected behaviour, since I'm instantiating a class that dosen't need the quantity field, but I wonder if there is anyway to tell rails to run the validations for the subclass instead of the super.
You could probably solve this with a custom validator, similar to the answer on this question: Two models, one STI and a Validation However, if you can simply instantiate the intended sub-type to begin with, you would avoid the need for a custom validator altogether in this case.
As you've noticed, setting the type field alone doesn't magically change an instance from one type to another. While ActiveRecord will use the type field to instantiate the proper class upon reading the object from the database, doing it the other way around (instantiating the superclass, then changing the type field manually) doesn't have the effect of changing the object's type while your app is running - it just doesn't work that way.
The custom validation method, on the other hand, could check the type field independently, instantiate a copy of the appropriate type (based on the value of the type field), and then run .valid? on that object, resulting in the validations on the sub-class being run in a way that appears to be dynamic, even though it's actually creating an instance of the appropriate sub-class in the process.
I've done something similar.
Adapting it to your problem:
class Budget < ActiveRecord::Base
validates_presence_of :price
validates_presence_of :quantity, if: :hourly_rate?
def hourly_rate?
self.class.name == 'HourlyRateBudget'
end
end
For anyone looking for example code, here's how I implemented the first answer:
validate :subclass_validations
def subclass_validations
# Typecast into subclass to check those validations
if self.class.descends_from_active_record?
subclass = self.becomes(self.type.classify.constantize)
self.errors.add(:base, "subclass validations are failing.") unless subclass.valid?
end
end
Instead of setting the type directly set the type like that... Instead, try:
new_type = params.fetch(:type)
class_type = case new_type
when "HourlyRateBudget"
HourlyRateBudget
when "FlatRateBudget"
FlatRateBudget
else
raise StandardError.new "unknown budget type: #{new_type}"
end
class_type.new(:price => 10)
You could even transform the string into its class by:
new_type.classify.constantize but if it's coming in from params, that seems a bit dangerous.
If you do this, then you'll get a class of HourlyRateBudget, otherwise it'll just be Budget.
Better yet, use type.constantize.new("10"), however this depends on that the type from params must be correct string identical to HourlyRateBudget.class.to_s
I also required the same and with the help of Bryce answer i did this:
class ActiveRecord::Base
validate :subclass_validations, :if => Proc.new{ is_sti_supported_table? }
def is_sti_supported_table?
self.class.columns_hash.include? (self.class.inheritance_column)
end
def subclass_validations
subclass = self.class.send(:compute_type, self.type)
unless subclass == self.class
subclass_obj= self.becomes(subclass)
self.errors.add(:base, subclass_obj.errors.full_messages.join(', ')) unless subclass_obj.valid?
end
end
end
Along the lines of #franzlorenzon's answer, but using duck typing to avoid referencing class type in the super class:
class Budget < ActiveRecord::Base
validates_presence_of :price
validates_presence_of :quantity, if: :hourly_rate?
def hourly_rate?
false
end
end
class HourlyRateBudget < Budget
def hourly_rate?
true
end
end

Advice on "Dynamic" Model validation

I have a model named Calendar.
The validations that will be applied to it varies from the selections made by the user.
I know that I can use custom validation + conditional validation to do this, but doesn't look very clean to me.
I wonder if I can store it on a database column and pass it to a "generic" validator method.
What do you think?
Explaining further:
A user has a calendar.
Other users that have access to this calendar, can schedule appointments.
To schedule an appointment the app should validate according to the rules defined by the calendar's owner.
There are many combinations, so what I came to is:
Create custom validator classes to each of the possible validations and make then conditional.
class Calendar
validate_allowed_in_hollydays :appointment_date if :allowedinhollydays?
(tenths of other cases)
...
end
This works, but feels wrong.
I'm thinking about storing somewhere which rules should be applied to that calendar and then doing something like:
validate_stored_rules :appointment_date
It seems a little backwards to save the data in the database and then validate it.
I think your initial thought of going with some custom validation is the best bet. validates_with looks like your best option. You could then create a separate class and build all the validation inside that to keep it separate from your main model.
class Person < ActiveRecord::Base
validates_with GoodnessValidator
end
class GoodnessValidator < ActiveModel::Validator
def validate
if record.first_name == "Evil"
record.errors[:base] << "This person is evil"
end
end
end
Code lifted straight from the Rails Validation Guide
you should use with_options it allows to put default options into your code:
class User < ActiveRecord::Base
with_options :if => :is_admin do |admin|
admin.validates_length_of :password, :minimum => 10
end
end
in the example is_admin might be an database column, attr_accessor or an method
Thank you all for your help.
I've got it working like this:
def after_initialize
singleton = class << self; self; end
validations = eval(calendar.cofig)
validations.each do |val|
singleton.class_eval(val)
end
end

Rails attr_accessible does not work for :type?

Im trying set the single table inheritance model type in a form. So i have a select menu for attribute :type and the values are the names of the STI subclasses. The problem is the error log keeps printing:
WARNING: Can't mass-assign these protected attributes: type
So i added "attr_accessible :type" to the model:
class ContentItem < ActiveRecord::Base
# needed so we can set/update :type in mass
attr_accessible :position, :description, :type, :url, :youtube_id, :start_time, :end_time
validates_presence_of :position
belongs_to :chapter
has_many :user_content_items
end
Doesn't change anything, the ContentItem still has :type=nil after .update_attributes() is called in the controller. Any idea how to mass update the :type from a form?
we can override attributes_protected_by_default
class Example < ActiveRecord::Base
def self.attributes_protected_by_default
# default is ["id","type"]
["id"]
end
end
e = Example.new(:type=>"my_type")
You should use the proper constructor based on the subclass you want to create, instead of calling the superclass constructor and assigning type manually. Let ActiveRecord do this for you:
# in controller
def create
# assuming your select has a name of 'content_item_type'
params[:content_item_type].constantize.new(params[:content_item])
end
This gives you the benefits of defining different behavior in your subclasses initialize() method or callbacks. If you don't need these sorts of benefits or are planning to change the class of an object frequently, you may want to reconsider using inheritance and just stick with an attribute.
Duplex at railsforum.com found a workaround:
use a virtual attribute in the forms
and in the model instead of type
dirtectly:
def type_helper
self.type
end
def type_helper=(type)
self.type = type
end
Worked like a charm.
"type" sometimes causes troubles... I usually use "kind" instead.
See also: http://wiki.rubyonrails.org/rails/pages/ReservedWords
I followed http://coderrr.wordpress.com/2008/04/22/building-the-right-class-with-sti-in-rails/ for solving the same problem I had. I'm fairly new to Rails world so am not so sure if this approach is good or bad, but it works very well. I've copied the code below.
class GenericClass < ActiveRecord::Base
class << self
def new_with_cast(*a, &b)
if (h = a.first).is_a? Hash and (type = h[:type] || h['type']) and (klass = type.constantize) != self
raise "wtF hax!!" unless klass < self # klass should be a descendant of us
return klass.new(*a, &b)
end
new_without_cast(*a, &b)
end
alias_method_chain :new, :cast
end
class X < GenericClass; end
GenericClass.new(:type => 'X') # => #<X:0xb79e89d4 #attrs={:type=>"X"}>

Resources