Setters from Rails4 InstanceMethods for non-existing fields - ruby-on-rails

I am writing a gem rails4 where i can add dynamic attributes which wont persist in the actual model table, rather it will save in my second table with a reference.
Now i have before_validation to an instance method, where i am trying to set the default value to dynamically added attribute.
I am getting the error as
can't write unknown attribute
The code is
self[attr_name.to_sym]=attr_type[:default_value]
Kindly advice on this, how to do setters from the instance method for the non-existing fields.
I have used instance_variable_set for the same in ClassMethods.

You can use attr_accessor method for this purpose in your model.
class Sample << ActiveRecord::Base
attr_accessor :attr_name
#setter method
def attr_name=(val)
self[:attr_name] = val
end
#getter method
def attr_name
self[:attr_name]
end
end
Now, you can call any where instance method like following.
self[attr_name.to_sym]=attr_type[:default_value]

Related

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:)

What is the right way to override a setter method in Ruby on Rails?

I am using Ruby on Rails 3.2.2 and I would like to know if the following is a "proper"/"correct"/"sure" way to override a setter method for a my class attribute.
attr_accessible :attribute_name
def attribute_name=(value)
... # Some custom operation.
self[:attribute_name] = value
end
The above code seems to work as expected. However, I would like to know if, by using the above code, in future I will have problems or, at least, what problems "should I expect"/"could happen" with Ruby on Rails. If that isn't the right way to override a setter method, what is the right way?
Note: If I use the code
attr_accessible :attribute_name
def attribute_name=(value)
... # Some custom operation.
self.attribute_name = value
end
I get the following error:
SystemStackError (stack level too deep):
actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70
===========================================================================
Update: July 19, 2017
Now the Rails documentation is also suggesting to use super like this:
class Model < ActiveRecord::Base
def attribute_name=(value)
# custom actions
###
super(value)
end
end
===========================================================================
Original Answer
If you want to override the setter methods for columns of a table while accessing through models, this is the way to do it.
class Model < ActiveRecord::Base
attr_accessible :attribute_name
def attribute_name=(value)
# custom actions
###
write_attribute(:attribute_name, value)
# this is same as self[:attribute_name] = value
end
end
See Overriding default accessors in the Rails documentation.
So, your first method is the correct way to override column setters in Models of Ruby on Rails. These accessors are already provided by Rails to access the columns of the table as attributes of the model. This is what we call ActiveRecord ORM mapping.
Also keep in mind that the attr_accessible at the top of the model has nothing to do with accessors. It has a completely different functionlity (see this question)
But in pure Ruby, if you have defined accessors for a class and want to override the setter, you have to make use of instance variable like this:
class Person
attr_accessor :name
end
class NewPerson < Person
def name=(value)
# do something
#name = value
end
end
This will be easier to understand once you know what attr_accessor does. The code attr_accessor :name is equivalent to these two methods (getter and setter)
def name # getter
#name
end
def name=(value) # setter
#name = value
end
Also your second method fails because it will cause an infinite loop as you are calling the same method attribute_name= inside that method.
Use the super keyword:
def attribute_name=(value)
super(value.some_custom_encode)
end
Conversely, to override the reader:
def attribute_name
super.some_custom_decode
end
In rails 4
let say you have age attribute in your table
def age=(dob)
now = Time.now.utc.to_date
age = now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
super(age) #must add this otherwise you need to add this thing and place the value which you want to save.
end
Note:
For new comers in rails 4 you don't need to specify attr_accessible in model. Instead you have to white-list your attributes at controller level using permit method.
I have found that (at least for ActiveRecord relationship collections) the following pattern works:
has_many :specialties
def specialty_ids=(values)
super values.uniq.first(3)
end
(This grabs the first 3 non-duplicate entries in the array passed.)
Using attr_writer to overwrite setter
attr_writer :attribute_name
def attribute_name=(value)
# manipulate value
# then send result to the default setter
super(result)
end

Is there a way to make Rails ActiveRecord attributes private?

By default, ActiveRecord takes all fields from the corresponding database table and creates public attributes for all of them.
I think that it's reasonable not to make all attributes in a model public. Even more, exposing attributes that are meant for internal use clutters the model's interface and violates the encapsulation principle.
So, is there a way to make some of the attributes literally private?
Or, maybe I should move on to some other ORM?
Jordini was most of the way there
Most of active_record happens in method_missing. If you define the method up front, it won't hit method_missing for that method, and use yours instead (effectively overwriting, but not really)
class YourModel < ActiveRecord::Base
private
def my_private_attribute
self[:my_private_attribute]
end
def my_private_attribute=(val)
write_attribute :my_private_attribute, val
end
end
Stumbled upon this recently. If you want private writing and reading and public reading something like this
class YourModel < ActiveRecord::Base
attr_reader :attribute
private
attr_accessor :attribute
end
seems to work fine for me. You can fiddle with attr_reader, attr_writer and attr_accessor to decide what should be exposed and what should be private.
well, you could always override the methods...
class YourModel < ActiveRecord::Base
private
def my_private_attribute
self[:my_private_attribute]
end
def my_private_attribute=(val)
self[:my_private_attribute] = val
end
end
For me methods from both Otto and jordinl are working fine and make rspec for object of Class to pass:
object.should_not respond_to :attribute
But when I use jordinl method, I have a deprecation message to not write to database directly, but use attr_writer instead.
UPDATE:
But the truly "right" metod to do it turns out to be simple. Thanks to Mladen Jablanović and Christopher Creutzig from here. To make predefined method private or protected... simply redefine it:
Class Class_name
private :method_name
protected :method_name_1
end
What's important, you needn't rewrite previously defined method's logic.
You can make an existing method private:
YourClass.send(:private, :your_method)
Making the setting private does generate ActiveRecord error.
I put access control code in the overwritten method of the public setter by checking the caller:
def my_private_attribute=(val)
if (caller.first.include?('active_record/base.rb') || caller.first.include?('app/models/myclass.rb'))
self[:my_private_attribute] = val
else
raise Exception.new("Cannot set read-only attribute.")
end
end
I don't think there is 100% reliable way to do this. It's also worth checking the most popular ways to access attributes:
http://www.davidverhasselt.com/set-attributes-in-activerecord/

alias_attribute and creating and method with the original attribute name causes a loop

Im trying to dynamically create a method chain in one attribute in my model.
By now I have this function:
def create_filtered_attribute(attribute_name)
alias_attribute "#{attribute_name}_without_filter", attribute_name
define_method "#{attribute_name}" do
filter_words(self.send("#{attribute_name}_without_filter"))
end
end
so I receive a string with the attribute name, alias it for '_without_filter' (alias_method or alias_method_chain fails here, because the attribute isnt there when the class is created),
and I create a new method with the attribute name, where I filter its contents.
But somehow, when I call "#{attribute_name}_without_filter" it calls my new method (i think because the alias_attribute some how), and the program goes into a stack loop.
Im trying to rename that attribute, so I can use its name for a method...
Can someone please enlighten me on this.
There is a difference between alias_method and alias_attribute. alias_method actually makes a copy of the old method, whereas alias_attribute just defines new methods, which call old ones.
Note, that model.attribute and model.attribute= methods in ActiveRecord simply call read_attribute and write_attribute, so you always can access your attribute, even if you override it's getter or setter:
define_method "#{attribute_name}" do
filter_words(self.read_attribute(attribute_name))
end

decorating an attribute in rails

I have a name attribute on a Person model and every time I access the name attribute, I want name.capitalize to be returned.
Doing the following inside the model won't work,
def name
name.capitalize
end
so what is the alternative?
I suggest you to create a secondary method with your custom formatters.
class Person
def formatted_name
name.capitalize
end
end
This is a better solution compared with overwriting the default implementation because setter and getters might called when updating/writing/saving the record to the database.
I remember once when I overwrote the default implementation of an attribute and each time a record was saved, the attribute was updated with the formatted value.
If you want to follow this way you can use alias_method_chain or take advantage of inheritance including an external module.
class Person
def name_with_formatter
name_without_formatter.capitalize
end
alias_method_chain :name, :formatter
end
Also you can overwrite name and call read_attribute(:name) from within your custom method.
def name
read_attribute(:name).capitalize
end
def name
self[:name].capitalize
end
Again, don't do this. Go ahead and create a custom method.
But happens when name is null?
capitalize will throw an undefined method for nil::Class if self[:name] returns nil.
The following covers that:
def name
self[:name] ? self[:name].capitalize : nil
end
But I agree that you should create the formatted method and leave the name method as is. Never know when you might need the raw data.
FYI: the reason why your method didn't work was because it was causing, what I like to call, a self referring loop. You are redefining the method but calling the method in the new method. So you have to use self[:name] or read_attribute to get to the internal model data.
Try this:
def name
self[:name].capitalize
end

Resources