serialize & before_save in Rails 4 - ruby-on-rails

I have a DocumentType model w/ a extensions attribute. In my form I'm allowing people to insert those extensions into the form.
I want to be able to parse that input before saving, stripping out any invalid options, convert it into an array and have Rails serialize it.
I have the following code but I just end up w/ the input that the user gave in the form instead of an array:
class DocumentType < ActiveRecord::Base
serialize :extensions
before_save :process_extensions
def process_extensions
self.extensions = [*self.extensions.gsub(/[^a-z ]+/i, '').split(' ')].uniq
end
end

The key to understanding what's happening is knowing when serialization occurs. By inspecting serialization.rb in activerecord you'll see that the serialization magic happens by overriding type_cast_attribute_for_write, which is called on write_attribute. That is, on attribute assignment. So when you do:
document_type.extensions = something
something gets serialized and written to the extensions attribute. That is way before the save takes place. In fact, you don't even have to call save on document_type to have the attribute serialized.
The best workaround I know is to override extensions= on DocumentType. Something like:
def extensions=(value)
value = [*value.gsub(/[^a-z ]+/i, '').split(' ')].uniq
write_attribute :extensions, value
end

I believe this append because the value of extensions is serialized while the model is validated by Rails, and your process_extensions method is called later (before the model is saved) and does not act as expected
Try to use before_validate instead
before_validate :process_extensions

Related

Rails ActiveRecord non db attributes?

I've got an ActiveRecord model, Instance, which is based in the database, but has some non-database attributes.
One example is 'resolution'.
I need to be able to set/get the resolution, but this attribute needs custom non-db setters/getters. Where do I put these & how do I structure my model?
I also need to be able to validate resolutions as they are set via regex. Can I use validates_format_of or do I need to code a custom Validator?
If you need standard reader/writer methods, you can use attr_accessor:
class Instance
attr_accessor :resolution
end
You can also write the reader and writer method by yourself:
class Instance
def resolution
#resolution
end
def resolution=(value)
#resolution = value
validate! # this will raise RecordInvalid if the validation fails
end
end

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.

Non persistent ActiveRecord model attributes

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

How do I change an ActiveRecord from marked to be saved to make sure it does not get saved, from within the model itself?

How do I change an AcriveRecord from marked to be saved to make sure it does not get saved, from within the model itself?
Considering I can have a method run by a hook in activerecord, such as: before_save
for (hypothetical) example:
before_save :ignore_new_delete_exisiting_if_blank(self.attribute)
def ignore_new_delete_exisiting_if_blank(attribute)
self.do_not_save_me! if attribute.blank?
#what is that magic "do_not_save_me" method?
#Is there such thing, or something to achieve the same thing?
end
Update
My particular use case requires that no errors be thrown and other models to continue to be saved, even if this one will not. I should explain:
I am using model inheritance, and I am having an issue with figuring out how to let save the parent model, but if the child model instances are blank, (no values exist in certain attributes) they should not be persisted; however, the parent should still be persisted. This scenario does not let me make use of validations on the child model as that would block the parent from being persisted as well...
Your method should just return false to make it does not save.
Or you set the errors, which will allow to be more descriptive.
For example:
def ignore_new_delete_exisiting_if_blank_attribute
if attribute.blank?
errors.add(:base, "Not allowed to save if attribute is blank.")
end
end
Note that you cannot send parameters to a before_save. If you just want to make sure a record is not saved when an attribute is not present, you should use
validates_presence_of :attribute
[UPDATE]
When saving a parent model with children, you have to do something like accepts_nested_attributes_for, and in that call, you can specify which attributes must be given or when a child-record is ignored.
For example
accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
will not save a post if the title is blank.
Hope this helps.
The "magic" is that when you return false from the method, the record won't be saved.
In your case:
def ignore_new_delete_exisiting_if_blank(attribute)
attribute.present?
end

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