I have this model:
class Device < ActiveRecord::Base
has_many :events
def last_event
events.last
end
end
As you can see, I have a method to get the last event for the device. Now, elsewhere in the Device model I have this method:
def place
self.last_event.place
end
Now, if I don't have any records in Events for this Device I get an error "undefined method `place' for nil:NilClass".
And so I added:
def place
self.last_event.place if self.last_event.present?
end
And this pattern repeated itself throughout the app, I had to add "if self.last_event.present?" so it won't crash in other places too.
I am sure there must be a better way to handle this kind of thing without the need to check if last_event is present everywhere?
Any suggestions?
Thanks,
The try method (an addition of ActiveSupport) allows exactly that. If called on a nil object, it will just return nil too. Thus, both of the following lines are equivalent:
self.last_event.try(:place)
# equivalent to
self.last_event.place if self.last_event
Another option would be to have the method return a blank object which would respond to calls:
class Device < ActiveRecord::Base
has_many :events
def last_event
events.last || Event.new
end
def place
self.last_event.place
end
end
2.0.0p247 :001 > d = Device.new
=> #<Device id: nil, name: nil, created_at: nil, updated_at: nil>
2.0.0p247 :002 > d.place
=> nil
2.0.0p247 :003 > d.last_event
=> #<Event id: nil, device_id: nil, created_at: nil, updated_at: nil, place: nil>
The idea is that if a method always returns an object of the expected type, you never have to worry about subsequent calls encountering a nil object. Of course, this could have other implications - such as the need to determine if you have a valid object or a new one, but this can be checked later with:
2.0.0p247 :005 > d.last_event.new_record?
=> true
In that case you can use delegates
delegate :last, to: events, allow_nil: true, prefix: :event
delegate :place, to: event_last, allow_nil: true
Related
I have created a simple users model in rails 4.2. However I am unable to assign any attribute values in the rails console
2.1.5 :001 > u = User.new
=> #<User id: nil, name: nil, email: nil, auth_token: nil, created_at: nil, updated_at: nil, enabled: true>
2.1.5 :002 > u.name = 'sample'
=> "sample"
2.1.5 :003 > u.changed
=> []
2.1.5 :004 > u
=> #<User id: nil, name: nil, email: nil, auth_token: nil, created_at: nil, updated_at: nil, enabled: true>
As you can see despite setting name the value has not changed.
Here is the model file
class User < ActiveRecord::Base
self.primary_key = :id
include Tokenable
include Creatable
include Updatable
attr_accessor :name, :email, :auth_token, :created_at, :updated_at, :enabled
end
I know that this works fine in rails 3.2
One of the biggest "selling points" of ActiveRecord is that it automatically creates setters and getters in your models based on the DB schema.
These are not just your average accessors created by attr_accessor (which is plain Ruby), they cast values to the correct type and do dirty tracking among other things.
When you use attr_accessor you´re generating setters and getters that clobber those created by ActiveRecord - which means that AR will not track changes or persist the attributes.
This is what you really want:
class User < ActiveRecord::Base
include Tokenable
include Creatable
include Updatable
end
Only use attr_accessor in models when you need setters and getters for non-persisted ("virtual") attributes.
You need to save the record after assigning the new value. You can achieve that by calling update_attribute! or save! on your object. In your case:
u.name = "sample"
u.save!
or
u.update_attribute("name", "sample")
Note that update_attribute updates a single attribute and saves the record without going through the normal validation procedure
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
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.
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">