How to create read-only virtual attributes in Ruby models - ruby-on-rails

I would like to create a virtual attribute that will always be included when you do model_instance.inspect. I understand that attr_reader will give me the same thing as just defining an instance method, but I would like this attribute to be part of the object's "make up"
How can I accomplish this?
Thanks!
UPDATE
Here is what is not working in more detail:
class Product < ActiveRecord::Base
attr_reader :less_secure_asset_url
def less_secure_asset_url
self.asset.url
end
end
>> p = Product.find(:all)[1]
=> #<Product id: 49, asset_file_name: "Etrade_Trade_Conf_Request.docx", asset_content_type: "application/vnd.openxmlformats-officedocument.wordp...", asset_file_size: 38152, asset_updated_at: "2010-05-04 17:45:46", created_at: "2010-05-04 17:45:46", updated_at: "2010-05-04 17:45:46", owner_id: 345, product_type_id: 1>
As you can see, when I use the console it returns no "less_secure_asset_url" attribute

Your attribute is in there, even if it doesn't show up. Rails overrides the definition of the inspect method of Object in ActiveRecord::Base to something like:
def inspect
attributes_as_nice_string = self.class.column_names.collect { |name|
if has_attribute?(name) || new_record?
"#{name}: #{attribute_for_inspect(name)}"
end
}.compact.join(", ")
"#<#{self.class} #{attributes_as_nice_string}>"
end
Basically, if it's not a column_name, it's not going to show up. I haven't tried this, but you might be able to call something like p.as(Object).inspect to get to the super classes inspect method. You have to require 'facets' to get as. See docs here.

Related

Unable to set Rails model attribute from console or controller

I'm new to Rails and am working on getting an application set up in Rails 4.2.4. I have a model called List that looks like the following in the database (PostgresQL):
List(id: integer, user_id: integer, name: string, created_at: datetime, updated_at: datetime, friendly_name: string)
and in List.rb:
class List < ActiveRecord::Base
attr_accessor :name, :friendly_name
belongs_to :user
has_many :items
end
I am trying to modify the name attribute from a controller action:
def save_name
...
list_to_edit = List.find(params[:id].to_i)
list_to_edit.name = params[:name]
list_to_edit.save!
...
end
But the changes are not being persisted. I have confirmed that params[:name] and list_to_edit are not nil. When I try to change the attribute in the Rails console like this:
> l = List.last
> l.name = 'TestName'
> l.save!
I don't see any errors. After executing the above commands and executing l.name I do see TestName. When I type l or List.last, however I still see
#<List id: 29, user_id: 17, name: nil, created_at: "2015-11-07 18:55:04", updated_at: "2015-11-07 18:55:04", friendly_name: nil>
What do I need to do to set the name attribute of a List? I can post any additional file content if it is helpful.
After trying a few more things it looks like all I needed to do was remove name from the array being passed to attr_accessor in List.rb. I believe when I was trying to change the list name with my_list.name = 'something' I was modifying the instance variable, not the attribute stored in the database.

Rails: List Required Attributes For Create

I am manually creating objects in the rails console using Model.new(<attributes here>). Is there an easy way to list out which attributes a model will require me to include in order for the .save call to succeed?
I am running rails 4.2.3
You can get an array of validators using Model.validators. You'll have to parse this in some way to extract those validations for presence, something like:
presence_validated_attributes = Model.validators.map do |validator|
validator.attributes if validator.is_a?(ActiveRecord::Validations::PresenceValidator)
end.compact.flatten
I found a simpler way to accomplish the same thing:
When you do a failed create you can check the error message on the object.
# app/models/price.rb
class Price < ActiveRecord::Base
validates_presence_of :value
end
# in console
p = Price.new()
=> #<Price id: nil, created_at: nil, updated_at: nil, value: nil>
p.save
=> false
p.errors.messages
=> {:value=>["can't be blank"]}
In case you the mandatory attributes with error messages
book = Book.new
book.valid?
book.errors.messages
In case you just want the name of attributes without an error message
book = Book.new
book.valid?
book.errors.messages.keys

Model attribute printing properly in view, but saved in the database as "nil" EDITED

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

virtual field in a model part of the model but not in the DB

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.

ActiveRecord derived attribute persistence which depends on id value

How do you persist a derived attribute which depends on the value of id in rails? The snippet below seems to work-- Is there a better rails way?
class Model < ActiveRecord::Base
....
def save
super
#derived_attr column exists in DB
self.derived_attr = compute_attr(self.id)
super
end
end
Callbacks are provided so you should never have to override save. The before_save call in the following code is functionally equivalent to all the code in the question.
I've made set_virtual_attr public so that it can be calculated as needed.
class Model < ActiveRecord::Base
...
# this one line is functionally equivalent to the code in the OP.
before_save :set_virtual_attr
attr_reader :virtual_attr
def set_virtual_attr
self.virtual_attr = compute_attr(self.id)
end
private
def compute_attr
...
end
end
I think the more accepted way to do this would be to provide a custom setter for the virtual attribute and then provide an after_create hook to set the value after the record is created.
The following code should do what you want.
class Virt < ActiveRecord::Base
def after_create()
self.virtual_attr = nil # Set it to anything just to invoke the setter
save # Saving will not invoke this callback again as the record exists
# Do NOT try this in after_save or you will get a Stack Overflow
end
def virtual_attr=(value)
write_attribute(:virtual_attr, "ID: #{self.id} #{value}")
end
end
Running this in the console shows the following
v=Virt.new
=> #<Virt id: nil, virtual_attr: nil, created_at: nil, updated_at: nil>
>> v.save
=> true
>> v
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17",
updated_at: "2009-12-23 09:25:17">
>> Virt.last
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17",
updated_at: "2009-12-23 09:25:17">

Resources