Dynamically set method on before_save - ruby-on-rails

In my controller i want to dynamically bind my instance method to the before_save callbacks.
Is there any ways we can dynamically bind methods to the callback from controller side....
EDIT :
Controller
This original code..
def amfupdate
set_properties
validate_record if params[:csv_header][:validate_record] == "Y" #On this condition...
super
end
If condition is true than i want to set custom callback that will exact call after before_save but before object is saved.
I want to call this method exact after before_save.. But if condition is true on controller side ..
In Model
def validate_record
self.csv_columns.each do |csv_column|
self.errors.add_to_base(_("Invalid column name #{csv_column.column_name}.")) \
unless self.model_name.constantize.column_names.include?(csv_column.column_name)
end
end

I think you're looking for something like the following. In the Model:
validate do |instance|
instance.csv_columns.each do |csv_column|
instance.errors.add :csv_columns, "Invalid column name #{csv_column.column_name}"
unless instance.class.column_names.include?(csv_column.column_name)
end
end
This will be called before the record is saved and will abort the save if the errors are added to
UPDATE: With suggestion for conditional validations
Add an attribute to the model
attr_accessor :some_condtional
Set this in the controller
#instance.some_conditional = true # or false
Then the validation now looks like this:
validate do |instance|
instance.csv_columns.each do |csv_column|
instance.errors.add :csv_columns, "Invalid column name #{csv_column.column_name}"
unless instance.class.column_names.include?(csv_column.column_name)
end if instance.some_conditional
end
Or something along those lines. In other words use the model to hold the state and communicate the logic

Related

Get method name that called validator in Rails 5

I have a controller with a method
def create
if passenger.valid?
....
end
In my Passenger model I have my validation rules. Those rules depend on from which method the validator has been called.
So the question is, how can I get inside my model the name method that called the validator, in this case 'create'?
Try this:
caller_locations(1,1)[0].label
I would suggest setting an attr_accessor in the model for this case
# In model
attr_accessor :validation_caller
def valodation_rules
if #validation_caller.eql?('create')
# validations
else
# validations
end
end
# in controller
model_instance.validation_caller = 'create'
Hope it helps..

RoR: Update attribute in a model from a unrelated controller

I need to pass a value to attribute in a model from a different controller with no direct relation between them. In the below example I need to update farming_year in the Field Model from the Planting controller.
The Field model:
class Field < ApplicationRecord
has_many :crops
attr_accessor :farming_year
def getting_crops
#crops_list = Crop.select('crops.name').where(field_id: self.id, year: self.get_farming_year) # doesn't get the farming_year
end
def get_farming_year
#farming_year # passing the value directly will work #farming_year=2015!!
end
def farming_year=(val)
#farming_year = val # passing the value directly won't work #farming_year=2015!!
end
end
In the Planting controller:
def new
#field = Field.new
#field.farming_year = session[:working_year]
#field.save
flash.now[:success] = #field.get_farming_year # it works and gives the correct year
end
when I changed the #farming_year in the get_farming_year method to #farming_year=2016, then the code will work and will give the correct Crops records. the flash message in the code above without any change will give the correct year from the model. I think my main issue is passing the farming year from get_farming_year method to getting_crops method.
Hint: the framing year is belong to the Crop not to the Field, so I don't need to add it to the Field table.
Any ideas how to achieve that?
Your code has a number of issues.
attr_accessor
Why are you using an attr_accessor? You should store the value on a model attribute, in the database. If your Field model doesn't already have a farming_year attribute, create a migration to add it to the database by running these commands:
$ rails g migration AddFarmingYearToField farming_year:integer
$ rails db:migrate
If you're running Rails <= 4, use rake db:migrate instead of the second command.
Doing this means you don't need to use attr_accessor, or define getters and setters.
PlantingController#new
This method isn't working for you because you haven't defined the correct methods, and you're not saving the instance.
In your Field model, you've defined a farming_year method, but you haven't defined a farming_year= method, which is what the setter should be. Change your farming_year method definition to farming_year=. Alternatively, use the method I described in 1., then you won't have to.
Make sure you're saving the model object once you're done with it - call Field#save, which returns truthy on success and falsy on failure; or call Field#save!, which returns truthy on success and raises an exception on failure.
The main issue with my code was using attr_accessor which I didn't need it, so, I've replaced "attr_accessor :farming_year" with a class variable "##work_year =''", and updated the getter and setter method as in the below code
The Field model:
class Field < ApplicationRecord
has_many :crops
attr_accessor :farming_year
##work_year =''
def getting_crops
#crops_list = Crop.select('crops.name').where(field_id: self.id, year: farming_year) #now this can request the getter method and get the year
end
def farming_year # getter method
##work_year ||= ''
end
def farming_year=(val) #setter method
##work_year = val
end
end
In the Planting controller:
def new
#field = Field.new
#field.farming_year = session[:working_year]
##field.save NO need for this line
flash.now[:success] = #field.farming_year
end
Thank you all for your kind support:)

update attribute of model only through defined method instead of default

I have a model named "MyModel" and it has attribute named "status".
I could update this attribute like this:
model.status = "new"
model.save!
However, I override this update method with my own model method "update_status". Any update through the default updating will be reject unless it uses "update_status"
What you can certainly do is track the changed attributes, and filter only those you want to allow to be updatable in before_update callback. In your update_status method you could use assignment through update_column which does not trigger callbacks.
before_update :prevent_status_update
private
def prevent_status_update
return true unless self.changed.includes? 'status'
self.errors.add_to_base "Cannot update a #{ self.to_s }"
false
end

Ruby on Rails: how do I create an initializer method in the model or should I set all the default params in a controller?

I have a field in my model for date created, this is not passed from the form and is currently set in the create method of my controller.
Should this be in my model instead in some sort of initializer method? If so what would that method look like?
I have other fields I want to set as a default each a record is created so I'm trying to find out where is the accepted standard place to put these. I'm starting to think it should be the model as if the model was ever called outside the controller it wouldn't have all this logic.
I generally create builders and never use directly the standard Rails method create.
The point is to gather all the logic in one place with particular cases etc...
Basically in controllers I end up calling the builders this way:
#my_model_instance = MyModelBuilder.new(current_user, params[:my_model]).build
#my_model_instance = MyModelBuilder.new(current_user, params[:my_model]).create
All my builders live in /app/builders
Here is a very basic example:
class MyModelBuilder
attr_accessor :params, :user, :my_model
# consider using a Struct if you keep a very basic initializer
def initialize(user, params)
self.user = user
self.params = params
end
def build
my_model
end
def create
my_model.tap{|m| m.save }
end
def my_model
#my_model ||= MyModel.new(default_values.merge(params))
end
def default_values
{
foo: 'bar'
}
end
end
Rails already manages the date of creation and update of your records.
If your model has a created_at field or an updated_at field they will be filled with the time of creation and update of your model.
You can generate those fields easily in a migration, for instance :
create_table :hello do
t.timestamps
end
Now, for default values, you can fill them in the initialize method of the model :
def initialize(*args)
self.field = default_value
super(*args)
end

how can a controller manually set validation errors for a certain field

I have a form with 3 ActiveRecord fields. One of those fields has kind of goofy, and STATE-DEPENDENT validation requirements. (For example, I only validate the field if the object is being created on a setup wizard form.)
In my POST handler to create the object, I thought I could call errors.add to insert a special error condition
#foo = Foo.new(params[:foo])
if goofy_conditions(params[:foo][:goofy_field])
#foo.errors.add(:goofy_field, "doesn't meet the goofy conditions" )
end
respond_to do |format|
if #foo.save
...
else
... redirect back to form (with error fields hilited)
However, doing #foo.errors.add() in the controller doesn't seem to do anything... it doesnt prevent the save() if the other fields pass validations.
An alternative is to put a custom validation handler into the model... I know using errors.add(:field, 'msg') works fine there... but in that case how can my controller 'pass' info to the validator telling it whether or not the field needs to be validated.
That is model logic. Look at custom validations
class GoofyThing < ActiveRecord::Base
validate :goofy_attribute_is_goofy
def goofy_attribute_is_goofy
if goofy_conditions(self.goofy_field)
self.errors.add(:goofy_field, "doesn't meet the goofy conditions" )
end
end
end
Then it'll act just like any other validation.
Edit
You can conditionally validate with the :if option:
attr_accessible :via_wizard
validate :goofy_attribute_is_goofy, :if => lambda { self.via_wizard }
and in your controller:
class WizardController < ApplicationController
before_filter :get_object, :set_wizard
#...
def get_object
#object = GoofyThing.find(params[:id])
end
def set_wizard
#object.via_wizard = true
end
end

Resources