Rails attr_readonly doesn't work - ruby-on-rails

According to this question and the documentation of attr_readonly the following should be possible:
class MyModel < ActiveRecord::Base
attr_accessible :foo
attr_readonly :bar
end
m = MyModel.create(foo: '123', bar: 'bar') # Should work
m.update_attributes(bar: 'baz') # Should not work
However the first one fails, saying that I can't mass-assign bar. What am I mising?

From documentation
attr_accessible takes a list of attributes that will be accessible.
All other attributes will be protected.
So attr_accessible made bar attribute as protected from mass-assignment.

You can make the attribute , suppose ,key as:-
attr_accessible :key
and then add one more validation
validate :check_if_key_changed, :on=> :update
private
def check_if_key_changed
if self.key_changed?
errors.add(:key,"cant change key")
end
end
In this way you will be able to mass-assign it once on creation and can also make sure that it do not get updated.

Related

Custom validator with validation helpers in it

I've defined a class which is getting fat because of many validations defined in it. So, I created a custom validator which includes all validations specific to a given context, and it's working fine.
But the issue is that, while validaing any attribute, the options which are passed while defining a validation aren't getting considered.
Consider this Post class,
class Post
include Mongoid::Document
field :state
field :description
validates_with PublishableValidator, on: :publish
end
Now, while publishing a post, its description is mandatory. So I am validating it with publish context.
#post.valid?(:publish)
Custom validator for all publishable validations is defined as,
class PublishableValidator < ActiveModel::Validator
include ActiveModel::Validations
validates :description, presence: true, unless: :admin?
def validate(post)
self.class.validators.each do |validator|
validator.validate(post)
end
end
end
Now, there is constraint in description validation that, for admin, don't run this validation( not a good idea but admin can do whatever they want :] ).
But when I validate it with blank description and admin privilege, it still gives error, without considering provided constraint.
Suggestions ??
I managed to solve it by using SimpleDelegator class.
First, I inherited PublishableValidator from SimpleDelegator.
Delegated PublishableValidator object to #post.
Ran validations on publishable object.
And last, merged publishable errors to post object.
Updated PublishableValidator
class PublishableValidator < SimpleDelegator
include ActiveModel::Validations
validates :description, presence: true, unless: :admin?
def validate(post)
self.__setobj__(post)
super
post.errors.messages.merge!(self.errors.messages)
end
end
Thanks to this blog

Rails 2.3. validates_uniqueness_of a serialized field

I have a field in a model that is serialized and when I attempted to validates the uniqueness of it, it doesn't work. (Still on Rails 2.3 on this app)
app/models/foo.rb
class foo < ActiveRecord::Base
serialize :rules
validates_uniqueness_of :rules
end
I have attempted to store the content in a hash field instead and validate the content hash's uniqueness. Then I ran to another problem of the order of the callbacks.
require 'digest/md5'
class foo < ActiveRecord::Base
before_save :update_content_hash
validates_uniqueness_of :content_hash
def update_content_hash
self.content_hash = OpenSSL::Digest::SHA1.hexdigest(self.rules.flatten)
end
end
However, having looked at the Active Record callbacks order, before_save is executed after validation, so it will always be valid because the default value is nil and after that it's updated to the new content hash.
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Maybe I'm not thinking straight, any solution to this?
Many thanks in advance.
Try this:
before_validation :update_content_hash

How to override enumeration data in rail_admin gem

In my models, BookHeader has many Category
So, when edit or create new BookHeader, the form show like this
Enum fix?
I wanna change the "category #{id}" to category name by define a category_enum method but it still don't work. Please help!
Code for BookHeader model
class BookHeader < ActiveRecord::Base
attr_accessible :autho, :category_id, :description, :title, :book_type, :year,:publisher_id,:detail
has_many :books
belongs_to :category
belongs_to :publisher
TYPE = {:ebook=>"Ebook",:paper_book=> "PaperBook",:magazine=> "Magazine",:media=> "Media"}
DEFAULT_TAB = :paper_book
BOOKS_PER_PAGE = 1 # books to show in a pages (pagination)
extend FriendlyId
def book_type_enum #it worked here
TYPE.map{|key, val| [val]}
end
def category_enum #but dont' work here
["a","b"]
end
Code for edit form
edit do
field :title
field :description, :text do
ckeditor do true end
end
field :autho
field :book_type
field :category
end
See the Division attribute in this link
alias_attribute :name, :you_field_you_want_to_display
I think it's more flexible way, there is no need to rename something and everything will work properly
Yeah, I just found the answer, rename a column in your model to "name", it seem to be very magical, but it worked!

Better validates_associated method for Rails 3?

Rails 3 includes the validates_associated which is automatically called when saving a nested model. The problem with the method is the message is terrible - "Model(s) is invalid"
There have been a few posts attacking this issue for Rails 2:
http://rpheath.com/posts/412-a-better-validates-associated
http://pivotallabs.com/users/nick/blog/articles/359-alias-method-chain-validates-associated-informative-error-message
and there are probably more. It would be great to have a better version as described in these posts that is Rails 3 compatible. The main improvement would be to include why the associated model fails.
On the relationship, you can use :autosave => true instead which will try to save children models when you save the parent. This will automatically run the validations of the children and they will report with proper error messages.
Moreover, if you add a presence validation on the child that the parent must be set, and you construct the child objects through the association, you don't even need the autosave flag, and you get a beautiful error message. For example:
class Trip < ActiveRecord::Base
validates :name, :presence => true
attr_accessible :name
has_many :places, dependent: :destroy, :inverse_of => :trip
end
class Place < ActiveRecord::Base
belongs_to :trip
validates :name, :trip, presence: true
attr_accessible :name
end
Then you can get an nice error message with the following usage scenario:
> trip = Trip.new(name: "California")
=> #<Trip id: nil, name: "California">
> trip.places.build
=> #<Place id: nil, name: nil, trip_id: nil>
> trip.valid?
=> false
> trip.errors
=> #<ActiveModel::Errors:0x00000004d36518 #base=#<Trip id: nil, name: "California">, #messages={:places=>["is invalid"]}>
> trip.errors[:places]
=> ["is invalid"]
I think validates_associated is a relic of the era before autosaving of children and isn't the best way to do things any more. Of course that's not necessarily documented well. I'm not 100% sure that this also applies to Rails 2.3, but I have a feeling it does. These changes came when the nested attributes feature was added (which was sometime in 2.x).
This is a simplified snippet of code from a training project I posted on github.
I was having this problem, and in the end I used the solution given here by Ben Lee:
validates associated with model's error message
Ben says:
You can write your own custom validator, based on the code for the built-in validator.
Looking up the source code for validates_associated, we see that it uses the "AssociatedValidator". The source code for that is:
module ActiveRecord
module Validations
class AssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
record.errors.add(attribute, :invalid, options.merge(:value => value))
end
end
module ClassMethods
def validates_associated(*attr_names)
validates_with AssociatedValidator, _merge_attributes(attr_names)
end
end
end
end
So you can use this as an example to create a custom validator that bubbles error messages like this:
module ActiveRecord
module Validations
class AssociatedBubblingValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
(value.is_a?(Array) ? value : [value]).each do |v|
unless v.valid?
v.errors.full_messages.each do |msg|
record.errors.add(attribute, msg, options.merge(:value => value))
end
end
end
end
end
module ClassMethods
def validates_associated_bubbling(*attr_names)
validates_with AssociatedBubblingValidator, _merge_attributes(attr_names)
end
end
end
end
You can put this code in an initializer, something like /initializers/associated_bubbling_validator.rb.
Finally, you'd validate like so:
class User < ActiveRecord::Base
validates_associated_bubbling :account
end
NOTE: the above code is completely untested, but if it doesn't work outright, it is hopefully enough to put you on the right track
validates_associated runs the validations specified in the associated object's class. Errors at the parent class level simply say 'my child is invalid'. If you want the details, expose the errors on the child object (at the level of the child's form in the view).
Most of the time validates_existence_of is all I need.

How to create nested objects using accepts_nested_attributes_for

I've upgraded to Rails 2.3.3 (from 2.1.x) and I'm trying to figure out the accepts_nested_attributes_for method. I can use the method to update existing nested objects, but I can't use it to create new nested objects. Given the contrived example:
class Product < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id, :body
end
If I try to create a new Product, with a nested Note, as follows:
params = {:name => 'Test', :notes_attributes => {'0' => {'body' => 'Body'}}}
p = Product.new(params)
p.save!
It fails validations with the message:
ActiveRecord::RecordInvalid: Validation failed: Notes product can't be blank
I understand why this is happening -- it's because of the validates_presence_of :product_id on the Note class, and because at the time of saving the new record, the Product object doesn't have an id. However, I don't want to remove this validation; I think it would be incorrect to remove it.
I could also solve the problem by manually creating the Product first, and then adding the Note, but that defeats the simplicity of accepts_nested_attributes_for.
Is there a standard Rails way of creating nested objects on new records?
This is a common, circular dependency issue. There is an existing LightHouse ticket which is worth checking out.
I expect this to be much improved in Rails 3, but in the meantime you'll have to do a workaround. One solution is to set up a virtual attribute which you set when nesting to make the validation conditional.
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id, :unless => :nested
attr_accessor :nested
end
And then you would set this attribute as a hidden field in your form.
<%= note_form.hidden_field :nested %>
That should be enough to have the nested attribute set when creating a note through the nested form. Untested.
check this document if you use Rails3.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#label-Validating+the+presence+of+a+parent+model
Ryan's solution is actually really cool.
I went and made my controller fatter so that this nesting wouldn't have to appear in the view. Mostly because my view is sometimes json, so I want to be able to get away with as little as possible in there.
class Product < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :note
end
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id unless :nested
attr_accessor :nested
end
class ProductController < ApplicationController
def create
if params[:product][:note_attributes]
params[:product][:note_attributes].each { |attribute|
attribute.merge!({:nested => true})
}
end
# all the regular create stuff here
end
end
Best solution yet is to use parental_control plugin: http://github.com/h-lame/parental_control

Resources