Non persistent ActiveRecord model attributes - ruby-on-rails

I want to add to an existing model some attributes that need not be persisted, or even mapped to a database column.
Is there a solution to specify such thing ?

Of course use good old ruby's attr_accessor. In your model:
attr_accessor :foo, :bar
You'll be able to do:
object.foo = 'baz'
object.foo #=> 'baz'

I was having the same problem but I needed to bootstrap the model, so the attribute had to persist after to_json was called. You need to do one extra thing for this.
As stated by apneadiving, the easiest way to start is to go to your model and add:
attr_accessor :foo
Then you can assign the attributes you want. But to make the attribute stick you need to change the attributes method. In your model file add this method:
def attributes
super.merge('foo' => self.foo)
end

In case anyone is wondering how to render this to the view, use the method arguments for the render method, like so:
render json: {results: results}, methods: [:my_attribute]
Please know that this only works if you set the attr_accessor on your model and set the attribute in the controller action, as the selected answer explained.

From Rails 5.0 onwards you could use attribute:
class StoreListing < ActiveRecord::Base
attribute :non_persisted
attribute :non_persisted_complex, :integer, default: -1
end
With attribute the attribute will be created just like the ones being persisted, i.e. you can define the type and other options, use it with the create method, etc.
If your DB table contains a matching column it will be persisted because attribute is also used to affect conversion to/from SQL for existing columns.
see: https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute

In my case I wanted to use a left join to populate custom attribute. It works if I don't add anything but I also want to be able to set the attribute on a new object and of course it doesn't exist. If I add attr_accessor then it always returns nil after a select. Here's the approach I've ended up with that works for setting on new object and retrieving from left join.
after_initialize do
self.foo = nil unless #attributes.key?("foo")
end
def foo
#attributes["foo"]
end
def foo=(value)
#attributes["foo"] = value
end

Related

Use new attribute values to validate before_update

In my project, I have a model called PaymentCondition and another called PaymentPortion.
PaymentCondition has_many payment_portions and PaymentPortion belongs_to payment_condition.
When I create a new PaymentCondition, I have this method that creates n payment_portions. Being n the value of a :amount attribute from PaymentCondition.
If I create a new PaymentCondition with amount: 2, for instance, I'd have 2 payment_portions.
So far, so good.
My problem:
I'm using a nested form to this view, so I can edit everything at once.
PaymentCondition has a attribute called catchments.
PaymentPortion has a attribute called catchment.
Before submiting this form, I'd like to check if the sum of PaymentPortion.catchment is equal to PaymentCondition.catchments. If not, I must raise an error.
As for now, I can't get the new values of PaymentCondition before saving it...
I'm doing this inside payment_conditions_controller:
before_update :check_catchments
def check_catchments
errors.add(:catchments, "Values must check") unless catchments_check? || new_record?
end
def catchments_check?
catchment == portion_catchments
end
def portion_catchments
payment_portions.sum(:catchments)
end
Using sum(), I get only the values that are on the database. What I need are the values that are being send...
Is there a way to do so?
Thanks in advance.
May be working directly in the controller is an option? You can use methods like 'build' or 'first_or_initialize' to get\create AR objects. And then just validate them in the controller and save the data if necessary.

Updating association without saving it

I have a model:
class A < ActiveRecord::Base
has_many :B
end
And I want to reset or update A's B association, but only save it later:
a = A.find(...)
# a.bs == [B<...>, B<...>]
a.bs = []
#or
a.bs = [B.new, B.new]
# do some validation stuff on `a` and `a.bs`
So there might be some case where I will call a.save later or maybe not. In the case I don't call a.save I would like that a.bs stay to its original value, but as soon as I call a.bs = [], the old associations is destroyed and now A.find(...).bs == []. Is there any simple way to set a record association without persisting it in the database right away? I looked at Rails source and didn't find anything that could help me there.
Thanks!
Edit:
I should add that this is for an existing application and there are some architecture constraint that doesn't allow us to use the the regular ActiveRecord updating and validation tools. The way it works we have a set of Updater class that take params and assign the checkout object the value from params. There are then a set of Validater class that validate the checkout object for each given params. Fianlly, if everything is good, we save the model.
In this case, I'm looking to update the association in an Updater, validate them in the Validator and finally, persist it if everything check out.
In summary, this would look like:
def update
apply_updaters(object, params)
# do some stuff with the updated object
if(validate(object))
object.save(validate: false)
end
Since there are a lot of stuff going on between appy_updaters and object.save, Transaction are not really an option. This is why I'm really looking to update the association without persisting right away, just like we would do with any other attribute.
So far, the closest solution I've got to is rewriting the association cache (target). This look something like:
# In the updater
A.bs.target.clear
params[:bs].each{|b| A.bs.build(b)}
# A.bs now contains the parameters object without doing any update in the database
When come the time to save, we need to persist cache:
new_object = A.bs.target
A.bs(true).replace(new_object)
This work, but this feel kind of hack-ish and can easily break or have some undesired side-effect. An alternative I'm thinking about is to add a method A#new_bs= that cache the assigned object and A#bs that return the cached object if available.
Good question.
I can advice to use attributes assignment instead of collection manipulation. All validations will be performed as regular - after save or another 'persistent' method. You can write your own method (in model or in separated validator) which will validate collection.
You can delete and add elements to collection through attributes - deletion is performed by additional attribute _destroy which may be 'true' or 'false' (http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html), addition - through setting up parent model to accept attributes.
As example set up model A:
class A < ActiveRecord::Base
has_many :b
accepts_nested_attributes_for :b, :allow_destroy => true
validates_associated :b # to validate each element
validate :b_is_correct # to validate whole collection
def b_is_correct
self.bs.each { |b| ... } # validate collection
end
end
In controller use plain attributes for model updating (e.g update!(a_aparams)). These methods will behave like flat attribute updating. And don't forget to permit attributes for nested collection.
class AController < ApplicationController
def update
#a = A.find(...)
#a.update(a_attributes) # triggers validation, if error occurs - no changes will be persisted and a.errors will be populated
end
def a_attributes
params.require(:a).permit([:attr_of_a, :b_attributes => [:attr_of_b, :_destroy]])
end
end
On form we used gem nested_form (https://github.com/ryanb/nested_form), I recommend it. But on server side this approach uses attribute _destroy as mentioned before.
I finally found out about the mark_for_destruction method. My final solution therefor look like:
a.bs.each(&:mark_for_destruction)
params[:bs].each{|b| a.bs.build(b)}
And then I can filter out the marked_for_destruction? entry in the following processing and validation.
Thanks #AlkH that made me look into how accepts_nested_attributes_for was working and handling delayed destruction of association.

attr_accessor, not able to access property

This is probably very stupid question but here we go
class Foo < ActiveRecord::Base
attr_accessor :group_id
end
From irb
# gets record which has group_id set to 106
foo = Foo.find(1)
foo.group_id
=> nil
If I go and remove
attr_accessor :group_id
All works as it should
foo = Foo.find(1)
foo.group_id
=> 106
My question is why? Shouldn't attr_accessor create accessor / mutator for property :group_id and that why all should be working. What am I missing?
Update
Good answers bellow, just as explanation for my motivation here is I want to use mass assignment of certain properties (you need this since Rails 3.2.x). For that you need attr_accessible , I find that code is much cleaner that way, of course if used responsibly :)
Looks like group_id is already a property on your Foo object (shown by the fact that it returns 106 when attr_accessor is omitted). By adding attr_accessor you are overriding what's already there and creating a method reader and writer called group_id. The return of the newly defined group_id is nil since you don't define anything.
Conceptually, you're ending up with something like this:
class Foo < ActiveRecord::Base
def group_id # overriding previous definition of 'group_id'
nil
end
end
Edit:
If your goal is expose properties then yes, use attr_accessible
This happens because ActiveRecord automatically maps the attributes in your database table to attributes in the actual model. When you add attr_accessor you overshadow the database attribute with you own, newly defined attribute.
Basically, rails automatically defines attr_accessor (or something similar) for all attributes in the corresponding database table.
If you're looking for some way on how to make only certain of your attributes accessible for multi-assignment, you should instead look at attr_accessible.
Without the accessor, for sure you have a 'belongs_to :group' in your Foo model, so if you call 'group_id' to an instance of 'Foo' you will get the value of this field.
For instance, if you set an accessor called like that attribute( group_id) it will overwrite the original field and it will return nil, if havenĀ“t assigned any value.
class Foo < ActiveRecord::Base
attr_accessor :group_id
end
# gets record which has group_id set to 106
foo = Foo.find(1)
foo.group_id
=> nil
foo.group_id = "wadus"
=> "wadus"
foo.group_id
=> "wadus"
If you have a column by the name group_id in the table, than ActiveRecord defines the accessor for you. When you overwrite it as you did, your calls simply searches for a regular instance variable named group_id, which returns nil.

Ruby on Rails - Overriding the association id creation process

I'm trying to override the way rails apply and id to an associated object, for example:
There are 2 simple models:
class Album < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :album
end
And then I want to do this:
album = Album.new :title => 'First Album'
album.photos.build
album.save #=> true
On this case I've created a plugin that overrides the id property and replaces it to a hashed string, so what I want to do is find the methods where this album_id is being replaced for my custom method instead of the int and be able to converted before it's saved.
But I want to act globally inside Rails structure because since it will be a sort of plugin I want to make this action work on dynamic models, that's why I can't create an before_save validation on the model.
I'm not sure if it's easy to understand, but I hope someone could help me on that..
Here's a screenshot of my current table so you can see what is happening:
SQLite3 DB http://cl.ly/1j3U/content
So as you can see the album_id it's being replaced for my custom ruby object when its saved...I've disabled the plugin and then it saved normally with records 11 and 12...
I want just act on a rails action and converted with my custom methods, something like
def rails_association_replaced_method(record)
#take the record associations and apply a to_i custom method before save
super(record)
end
something like this :)
Well I hope this didn't get too complicated
Cheers
It seems if I only override theActiveRecord::Base save method do the job if handled properly
define_method 'save' do
int_fields = self.class.columns.find_all { |column| column.type == :integer }
int_fields.each do |field|
if self.attributes[field.name]
self.attributes[field.name] = self.attributes[field.name].to_i
end
end
super
end
And this shall replace all the integer fields from the Current Model applying a to_i method over the result.
Rails is unfriendly to that kind of change to the defaults. What's your end goal here?

usage of attr_accessor in Rails

When do you use attr_reader/attr_writer/attr_accessor in Rails models?
Never, unless you have specific need for it. Automatic database-backed accessors are created for you, so you don't need to worry.
Any attr_accessors you do create will change the relevant #attr on the rails object, but this will be lost when the object is destroyed, unless you stick it back in the database. Sometimes you do want this behavior, but it's unusual in a rails app.
Now in ruby, it's a different story, and you end up using these very frequently. But I'd be surprised if you need them in rails---especially initially.
attr_accessor can be used for values you don't want to store in the database directly and that will only exist for the life of the object (e.g. passwords).
attr_reader can be used as one of several alternatives to doing something like this:
def instance_value
"my value"
end
Rails models are just ruby classes that inherit from ActiveRecord::Base. ActiveRecord employs attr_accessors to define getters and setters for the column names that refer to the ruby class's table. It's important to note that this is just for persistence; the models are still just ruby classes.
attr_accessor :foo is simply a shortcut for the following:
def foo=(var)
#foo = var
end
def foo
#foo
end
attr_reader :foo is simply a shortcut for the following:
def foo
#foo
end
attr_writer :foo is a shortcut for the following:
def foo=(var)
#foo = var
end
attr_accessor is a shortcut for the getter and setter while attr_reader is the shortcut for the getter and attr_writer is a shortcut for just the setter.
In rails, ActiveRecord uses these getters and setters in a convenient way to read and write values to the database. BUT, the database is just the persistence layer. You should be free to use attr_accessor and attr_reader as you would any other ruby class to properly compose your business logic. As you need to get and set attributes of your objects outside of what you need to persist to the database, use the attr_s accordingly.
More info:
http://apidock.com/ruby/Module/attr_accessor
http://www.rubyist.net/~slagell/ruby/accessors.html
What is attr_accessor in Ruby?
If you are using it to validate the acceptance of the terms_of_service, you should really consider using validates :terms_of_service, :acceptance => true. It will create a virtual attribute and is much more concise.
http://guides.rubyonrails.org/active_record_validations.html#acceptance.
One example is to have a number of options stored in one serialized column. Form builder would complain if you try to have a text field for one of these options. You can use attr_accessor to fake it, and then in the update action save it in the serialized column.

Resources