I have a eBook resource with a value property:
class EBook < ApplicationRecord
include Mixin
end
and a module:
module Mixin
extend ActiveSupport::Concern
included do
# validations
belongs_to :user
end
def change_value
#value = 200
end
end
I would like to be able to call EBook.change_value and have that instance's value set to 200. How can I do this? Is this an antipattern? I can't seem to find anything that will allow me to change instance state through a module.
Using the rails console I get this output:
EBook Load (0.3ms) SELECT `e_books`.* FROM `e_books` ORDER BY `e_books`.`id` ASC LIMIT 1 OFFSET 1
=> 200
but it doesn't update or save the model.
ActiveRecord does not use separate instance variables for attributes represented in database.
Change your method to
def change_value
self.value = 200
end
in order to use setter method generated by ActiveRecord for your model.
In order to clear it up a bit more, this is what your code did:
class Ebook < ApplicationRecord
attr_reader :value
def change_value
#value = 200
end
end
2.5.1 :001 > e = Ebook.new
=> #<Ebook id: nil, value: nil>
2.5.1 :002 > e.change_value # this sets your instance_variable
=> 200
2.5.1 :003 > e
=> #<Ebook id: nil, value: nil> # ActiveRecord's value remain nil
2.5.1 :004 > e.value # reads from instance variable as we've overwritten the method with attr_reader
=> 200
2.5.1 :005 > e.read_attribute(:value) # reads from ActiveRecord's attributes
=> nil
2.5.1 :006 > e.tap(&:save)
=> #<Ebook id: 3, value: nil> # as expected, nothing is saved
Related
I have a Restriction model that represent a formula. The field formula is a string that at runtime is evaluated. This field can't be accessed from outside for security reasons so I'am trying to override the accessors.
class Restriction < ActiveRecord::Base
belongs_to :indicator
RESTRICTION_TYPES = {
less_than: "IND<X",
greater_than: "X<IND",
between: "X<IND && IND<Y"
}
def evaluate(number)
f = formula.gsub("IND", "#{number}")
eval(f)
end
def create_formula(type_name, arg)
if(type_name == :between)
f = RESTRICTION_TYPES[:between].gsub("X", "#{arg[0]}").gsub("Y", "#{arg[1]}")
else
f = RESTRICTION_TYPES[type_name].gsub("X", "#{arg}")
end
formula = f
end
private
def formula= f
write_attribute(:formula, f)
end
def formula
read_attribute(:formula)
end
def [](value)
super[value]
end
def []=(key, value)
super[key] = value
end
end
In rails console:
Loading development environment (Rails 4.0.0)
2.0.0p247 :001 > r = Restriction.new
=> #<Restriction id: nil, formula: nil, indicator_id: nil, created_at: nil, updated_at: nil, state: nil>
2.0.0p247 :002 > r.create_formula(:between, [1,2])
=> "1<IND && IND<2"
2.0.0p247 :003 > r.evaluate 1.5
NoMethodError: undefined method 'gsub' for nil:NilClass
2.0.0p247 :004 > r
=> #<Restriction id: nil, formula: nil>
formula is not change the value. What am I doing wrong?
PS: You can see that I have also overriden [](value) and []=(key, value). This is due to my previous question
Rails internally relies on the hash like access methods for reading and writing attributes. By making them private you wanted to restrict the access of [] and []= to from within the object only. But you destroyed the method, because you are using super the wrong way. When calling super you are not getting the super object of this class, but the super method, the current method overrides. Thus change it like this and it should work:
def [](value)
super(value)
end
def []=(key, value)
super(key, value)
end
P.S.: Overriding a method for declaring it private is overkill in Ruby. You can simply declare it private with
private :[], []=
I'm trying to write an app that calculates sick/vacation days and how much an employee has available in either category. Here's my trouble:
In my view, the duration equation works and shows the right numbers, but I've put the math in the view, which I know is bad. But when I try to use the duration equation in my employee class (so I can move the math out of the view) it doesn't work, and I think that it's because duration is saving as 'nil' for some reason. I don't know why it's doing that, as everything else has been saving in the database with whatever information I input into the form.
Maybe it's because duration isn't inputted manually in the form, but rather reacts to the date-range?
Here's where I want to call duration in the employee model to get the math out of the view:
def remaining_vacation_days
vacation_days - #furlough.duration if #furlough.description == "Vacation"
end
def remaining_sick_days
sick_days - #furlough.duration if #furlough.description == "Sick"
end
Here's the model where duration is defined:
class Furlough < ActiveRecord::Base
attr_accessible :duration # and other stuff
belongs_to :employee
validates_presence_of :duration # and other stuff
def duration
only_weekdays(date_from..date_to) - psb_holidays
end
def only_weekdays(range)
range.select { |d| (1..5).include?(d.wday) }.size
end
def psb_holidays
Holidays.between(date_from, date_to, :us, :observed).size
end
end
What's tripping me out is that in the console this is what I see:
1.9.3-p0 :008 > ryan = Furlough.find(18)
Furlough Load (0.3ms) SELECT "furloughs".* FROM "furloughs" WHERE "furloughs"."id" = ? LIMIT 1 [["id", 18]]
=> #<Furlough id: 18, duration: nil, date_from: "2013-12-20", note: "Christmas vacation!", created_at: "2013-05-08 14:33:03", updated_at: "2013-05-08 14:34:07", employee_id: 16, description: "Vacation", date_to: "2013-12-29">
See, it's nil, but then I get this:
1.9.3-p0 :009 > ryan.duration
=> 5
I'm at a loss.
You are supposed to use instance of the class, not the class itself, thats why you are getting all those errors.
def sick_days_used
Furlough.duration if Furlough.description == "Sick"
end
should be :
def sick_days_used
#furlough.duration if #furlough.description == "Sick"
end
or
def sick_days_used
self.duration if self.description == "Sick"
end
if your are defining it in model
The attributes are attributes of a Furlough instance, not the Furlough class itself.
If you are going to use the methods as class methods then you need to add 'self' to the method definition:
def self.duration
...
end
Then you can call Furlough.duration.
The other way around (def duration) you are defining an instance method, which can only be called on an instance (an specifiic Furlogh instance).
I have a standard model with a few fields that are saved to a DB, and I need 1 field that doesn't have to be saved.
I tried attr_accessor but that doesn't cover it. Using Attr_accessor I can set and get the field, but it is not part of the model. If I add the models to an array and then see what is in the virtual field is not part of it. I also tried to add the field :headerfield to attr_accessible but that didn't change anything.
How can I get a field that is part of the model but not saved to the database?
The model
class Mapping < ActiveRecord::Base
attr_accessible :internalfield, :sourcefield
attr_accessor :headerfield
end
console output:
1.9.3-p194 :001 > m = Mapping.new
=> #<Mapping id: nil, internalfield: nil, sourcefield: nil, created_at: nil, updated_at: nil, data_set_id: nil>
1.9.3-p194 :002 > m.headerfield = "asef"
=> "asef"
1.9.3-p194 :003 > m
=> #<Mapping id: nil, internalfield: nil, sourcefield: nil, created_at: nil, updated_at: nil, data_set_id: nil>
Because ActiveRecord::Base has custom implementations for the standard serializiation methods (including to_s and as_json), you will never see your model attributes that do not have backing database columns unless you intervene in some way.
You can render it to JSON using the following:
render json: my_object, methods: [:virtual_attr1, :virtual_attr2]
Or you can use the as_json serializer directly:
my_object.as_json(methods: [:virtual_attr1, :virtual_attr2])
The return you see in the console is nothing else but the value of to_s. For this case, code should be better than natural language, take a look in the following code and see if you understand
class A
end
=> nil
A.new
=> #<A:0xb73d1528>
A.new.to_s
=> "#<A:0xb73d1528>"
class A
def to_s
"foobar"
end
end
=> nil
A.new
=> ble
A.new.to_s
=> "ble"
You can see this output because ActiveRecord::Base defines a method to_s that take into account only the attributes that are defined in the database, not the attr_accessor methods, maybe using the attributes call.
Note : Before Answering the question understand this does not have to do anything with attr_accessible or attr_protected the RAILS is in question is 3.1.3 and Ruby 1.9.2p290
Without futher due Here my params that I try to mass assign to DriverLicense Model
{"utf8"=>"✓",
"authenticity_token"=>"cKhruPzVvBK63bqCQPFY8xPZb12V5lhLPOpXgjIToJk=",
"driver_license"=>
{"license_number"=>"LICENCE-001",
"license_class"=>"A-CLASS",
"validity_start_date"=>"02/01/2011",
"validity_end_date"=>"08/24/2011",
"issuing_authority"=>"RTO",
"remarks"=>"Remarks .."},
"save_button"=>"Save",
"action"=>"create",
"controller"=>"driver_licenses",
"driver_id"=>"2"}
Here the controller code
#driver_license = #driver.driver_licenses.new(params[:driver_license])
Here the definition of model look like
class DriverLicense < ActiveRecord::Base
acts_as_tenant(:tenant)
validates :driver_id,:license_number,:validity_start_date,:validity_end_date,:presence => true
validate :date_validity ,:if => :is_date_present?
validate :overlapping_validity,:if => :validity_start_date_present?
belongs_to :driver
scope :active_licenses , where('validity_start_date <= ? and validity_end_date >= ?',Date.today,Date.today)
.... ....
.... ....
private
def is_date_present?
validity_start_date.present? and validity_end_date.present?
end
def date_validity
errors.add(:validity_start_date,"must be earlier then validity end date") if validity_start_date > validity_end_date
end
def validity_start_date_present?
validity_start_date.present?
end
def overlapping_validity
arel = self.class.where('validity_end_date >= ?',validity_start_date).where('driver_id = ?',driver_id)
arel = arel.where('id != ?',id) if id.present?
unless arel.count.zero?
errors.add(:validity_start_date,"overLapping date with other licenses")
end
end
end
The mass-attributes work for all attributes except validity_end_date
#<DriverLicense id: nil, driver_id: 2, license_number: "LICENCE-001", license_class: "A-CLASS", issuing_authority: "RTO", validity_start_date: "2011-01-02", validity_end_date: nil, remarks: "Remarks ..", tenant_id: 2, created_at: nil, updated_at: nil>
Once can check that on screenshot as well further where the mass-assignment work for all attributes except validity_end_date
Found that the date format is not the same in the both the side
& hence it setting it to nil
My model name in one of rails app is OrganizationUser and is there any way to create alias name for this model as OU or OrgUser so that I can use in rails console..
If kishie's answer does not suit you you could create another model that inherits from OrganizationUser:
class OU < OrganizationUser
end
or
class OrgUser < OrganizationUser
end
To work on a more cleaner side . Suppose you have a model
class Home < ActiveRecord::Base
class << self
def agent
p "This is a Dummy String"
end
end
end
Step 1
Create a alias.rb inside your lib. Which will contain your Alias mappings and Constants holding those mapping
module Alias
C = Home #to make a alias of class
H = Home.new #a class object alias
end
Step 2
Goto rails c
rails c
"inside it for loading"
Loading development environment (Rails 3.2.1)
ruby-1.9.3-preview1 :001 > require 'alias'
=> true
ruby-1.9.3-preview1 :002 > include Alias
=> Object
ruby-1.9.3-preview1 :003 > C
=> Home(id: integer, created_at: datetime, updated_at: datetime)
ruby-1.9.3-preview1 :004 > H
=> #<Home id: nil, created_at: nil, updated_at: nil>