I want to convert dollars to cents without using money gem and outside the model. This attribute belongs to model that is has a dashboard in Administrate gem.
When users input 10 it should be converted to cents as 10 * 100 and saved to DB.
I was trying to do it using administrate custom Field then setup getter and setter, but it doesn't work:
class ConvertToCentsField < Administrate::Field::Base
def to_s
data
end
def buyer_fee_fixed
#resource.buyer_fee_fixed
end
def buyer_fee_fixed=(value)
#resource.buyer_fee_fixed = (value.to_f * 100).to_i
end
end
Maybe I'm using getter and setter in the wrong place.
Related
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:)
I have trouble thinking of a way on how to shorten my process on titleizing values upon rendering them in my view.
I did some custom getters for the following attributes that I need to titleize. Here's my example.
user.rb
class User < ActiveRecord::Base
def department
read_attribute(:department).titleize
end
def designation
read_attribute(:designation).titleize
end
end
This method works but it seems a hassle when I want to do this to other models as well.
Is there a more efficient way to handle this which can be used by other models? If you'll mention Draper (since I don't seem to find on how to titleize selected attributes), how can I accomplish using this gem? But, I would prefer not using a gem but instead, create a custom one.
Not tested this, but you could use a Concern with added modules to handle it
--
Modularity
I found a gem called modularity which basically allows you to pass parameters to a concern & other modules. This means if you can pass the params you wish to "titleize", you may be able to pull it off like this:
#Gemfile
gem 'modularity', '~> 2.0.1'
#app/models/concerns/titleize.rb
module Titleize
extend ActiveSupport::Concern
as_trait do |*fields|
fields.each do |field|
define_method("#{field}") do
self[field.to_sym] = field.titleize
end
end
end
end
#app/models/your_model.rb
Class YourModel < ActiveRecord::Base
include Titleize[:your, :params]
end
If you want those value always titleized, what you are doing is fine, but I would actually apply the method on the setters, not on the getters, so you only do it once per record instead of at each read:
def department=(s)
write_attribute(:department, s.to_s.titleize) # The to_s is in case you get nil/non-string
end
If this is purely for presentation (ie, you want the not titleized version in the database, then it can be done in a presenter using Draper:
class UserDecorator < Draper::Decorator
delegate_all
def designation
object.designation.titleize
end
end
(or another rails presenter).
I have a user model => #user
I want to add new attribute current_time to #user for temporary use.
Don't want to do migration to add a column (just for temporary use):
#user.current_time = Time.now
Is there any way to achieve this?
NoMethodError (undefined method `current_time=' for #<User:0x007fd6991e1050>):
app/controllers/carts_controller.rb:47:in `block in search_user'
app/controllers/carts_controller.rb:45:in `search_user'
attr_accessor will set up a reader and writer for the instance variable:
class Foo
attr_accessor :current_time
end
foo = Foo.new
foo.current_time = Time.now # Writes value
foo.current_time # Reads value
You might also be interested in attr_reader and attr_writer.
Try to define few methods in User.model:
def current_time= (time)
#current_time = time
end
def current_time
#current_time
end
UPD according to precisely right comment from kristinalim
Note, that attr_accessible, being part of framework, was deprecated in Rails 4. Now strong params are used instead. At the same time getter/setter attr_accessor is part of core Ruby and works as usual.
The difference between attr_accessible and attr_accessor is in very well explained in this post
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
I am new to Ruby on Rails and have been using Scaffolding. On one of the forms I want to be able to have two fields and then have the difference between the two submitted to the database.
Instead of :pickupamount, I want (Ending Amount - Starting Amount) to be calculated and entered into the pickupamount column of my database.
Thanks in advance!
You could do this in either your model or your controller. Going along with Skinny Controller Fat Model, it might be better to put the functionality in your model. Check out ActiveRecord callbacks.
class MyModel < ActiveRecord::Base
attr_accessor :start_amount, :end_amount
before_create :calculate_pickup_amount
private
def calculate_pickup_amount
self.pickupamount = end_amount.to_i - start_amount.to_i
end
end
Then, in your controller:
def create
# Assuming params[:my_model] has all the data for initializing a MyModel,
# including start_amount and end_amount but not pickupamount:
my_model = MyModel.new(params[:my_model])
if my_model.save
# Yay, do something
else
# Fail, do something else
end
end
It might be useful to include the following extension method to Ruby's String class (thanks to sikelianos), perhaps in a file in your Rails app's lib directory:
class String
def numeric?
Float self rescue false
end
end
Then you could perform a check before setting pickupamount:
def calculate_pickup_amount
if end_amount.numeric? && start_amount.numeric?
self.pickupamount = end_amount.to_i - start_amount.to_i
else
# Throw exception, set some default value, etc.
end
end
In your view:
form_tag '/some_action' do
text_field_tag 'start_amount'
text_field_tag 'end_amount'
end
In your controller:
def create
model = Model.new
model.pickupamount = params[:end_amount].to_i - params[:start_amount].to_i
model.save!
end