How can I tell Ruby (Rails) to ignore protected variables which are present when mass-assigning?
class MyClass < ActiveRecord::Base
attr_accessible :name, :age
end
Now I will mass-assign a hash to create a new MyClass.
MyClass.create!({:name => "John", :age => 25, :id => 2})
This will give me an exception:
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: id
I want it to create a new MyClass with the specified (unprotected) attributes and ignore the id attribute.
On the side note: How can I also ignore unknown attributes. For example, MyClass doesn't have a location attribute. If I try to mass-assign it, just ignore it.
Use Hash#slice to only select the keys you're actually interested in assigning:
# Pass only :name and :age to create!
MyClass.create!(params.slice(:name, :age))
Typically, I'll add wrapper method for params to my controller which filters it down to only the fields that I know I want assigned:
class MyController
# ...
def create
#my_instance = MyClass.create!(create_params)
end
protected
def create_params
params.slice(:name, :age)
end
end
Setting mass_assignment_sanitizer to :logger solved the issue in development and test.
config.active_record.mass_assignment_sanitizer = :logger
You can use strong_parameters gem, that will be in rails 4.
See the documentation here.
This way you can specify the params you want by action or role, for example.
If you want to get down and dirty with it, and dynamically let only a model's attributes through, without disabling ActiveModel::MassAssignmentSecurity::Errors globally:
params = {:name => "John", :age => 25, :id => 2}
MyClass.create!(params.slice(*MyClass.new.attributes.symbolize_keys.keys)
The .symbolize_keys is required if you are using symbols in your hash, like in this situation, but you might not need that.
Personally, I like to keep things in the model by overriding assign_attributes.
def assign_attributes(new_attributes, options = {})
if options[:safe_assign]
authorizer = mass_assignment_authorizer(options[:as])
new_attributes = new_attributes.reject { |key|
!has_attribute?(key) || authorizer.deny?(key)
}
end
super(new_attributes, options)
end
Use it similarly to :without_protection, but for when you want to ignore unknown or protected attributes:
MyModel.create!(
{ :asdf => "invalid", :admin_field => "protected", :actual_data => 'hello world!' },
:safe_assign => true
)
# => #<MyModel actual_data: "hello world!">
Related
I have a Notifications module which have classes like 1)car 2)bike 3)Aeroplane. I have a serialized column in UserFeature model.And I have a module 'Notifications' which has list of 11 classes in it.
Notifications
1)car
2)bike
3)Aeroplane
The hash structure of the column notifications in UserFeature model must be
{:car => {:mirror => :true, :door => :true}
:bike => {:x=> :true, :x => :true}
:Aeroplane => {:p => :true, :q => :true}
}
I can access user_object.Notifications
But so as to access user_object.car and also user_object.mirror I need to write getter/setter methods { Defining getter/setter dynamically because I dont want to write getter/setter for every method and also I am unsure about the number of methods I have -> which in future may extend }
Notifications.constants.each do |notification_class|
class_methods = "Notifications::#{notification_class}".constantize.methods(false)
class_methods.each do |method|
method_name = method[0..-4].split('(')[0]
setter_getter_name = "#{notification_class.to_s.underscore}_#{method_name}"
define_method("#{setter_getter_name}=") do |value|
self.notifications = GlobalUtils.form_hash(self.notifications, "#{notification_class}".to_sym, "#{method_name}".to_sym)
self[:notifications]["#{notification_class}".to_sym][ "#{method_name}".to_sym] = value
end
define_method("#{setter_getter_name}") do
self.notifications.fetch("#{notification_class_name}".to_sym, {}).fetch("#{method_name}".to_sym)
end
end
end
But still when i try to access user_object.mirror,
undefined method for #<UserFeature000043645345>
What I am doing wrong?
I need to do this using getter/setter method only
An OpenStruct is a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values. This is accomplished by using Ruby’s metaprogramming to define methods on the class itself.
example:
require 'ostruct'
hash = { "country" => "Australia", :population => 20_000_000 }
data = OpenStruct.new(hash)
p data # -> <OpenStruct country="Australia" population=20000000>
Use Ruby OpenStruct class. It will fulfill your requirements without defining such bunch of code.
Edit1, example:
require 'ostruct'
class Aeroplane < OpenStruct; end
a = Aeroplane.new(:p => :true, :q => :true)
a.p # => true
When I execute a Model.create method, if I specify a value for :id, it later gets nullified. Example:
Model.create (
:id => 50,
:name => Joe,
:enabled => yes
)
Instead what I have to do is use a .new and store it in a class variable, store my id value via the class variable, and then finally call a save:
m = Model.new (
:name => Joe,
:enabled => yes
)
m.id = 50
m.save
I am trying to execute this code in a seeds.rb, and this is NOT very DRY code. How can I do this better and achieve the same results?
id is just attr_protected. To prevent that, you can override the list of default protected attributes. Be careful doing this anywhere that attribute information can come from the outside. The id field is default protected for a reason.
class Model < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
or go with #Leo answer
This might be an answer for you. Model.create is basically a Model.new followed by a Model.save and since you are changing the id and saving again you might as well do
m = Model.new {
:name => Joe,
:enabled => yes
}
m.id = 50
m.save!
That will rid you of doing two saves.
I have a class Sample
Sample.class returns
(id :integer, name :String, date :date)
and A hash has all the given attributes as its keys.
Then how can I initialize a variable of Sample without assigning each attribute independently.
Something like
Sample x = Sample.new
x.(attr) = Hash[attr]
How can I iterate through the attributes, the problem is Hash contains keys which are not part of the class attributes too
class Sample
attr_accessor :id, :name, :date
end
h = {:id => 1, :name => 'foo', :date => 'today', :extra1 => '', :extra2 => ''}
init_hash = h.select{|k,v| Sample.method_defined? "#{k}=" }
# This will work
s = Sample.new
init_hash.each{|k,v| s.send("#{k}=", v)}
# This may work if constructor takes a hash of attributes
s = Sample.new(init_hash)
Take a look at this article on Object initialization. You want an initialize method.
EDIT You might also take a look at this SO post on setting instance variables, which I think is exactly what you're trying to do.
Try this:
class A
attr_accessor :x, :y, :z
end
a = A.new
my_hash = {:x => 1, :y => 2, :z => 3, :nono => 5}
If you do not have the list of attributes that can be assigned from the hash, you can do this:
my_attributes = (a.methods & my_hash.keys)
Use a.instance_variable_set(:#x = 1) syntax to assign values:
my_attributes.each do |attr|
a.instance_variable_set("##{attr.to_s}".to_sym, my_hash[attr])
end
Note(Thanks to Abe): This assumes that either all attributes to be updated have getters and setters, or that any attribute which has getter only, does not have a key in my_hash.
Good luck!
I have a Rails 3 app which JSON encodes objects in order to store them in a Redis key/value store.
When I retrieve the objects, I'm trying to decode the JSON and instantiate them from the data like so:
def decode(json)
self.new(ActiveSupport::JSON.decode(json)["#{self.name.downcase}"])
end
The problem is that doing this involves mass assignment which is disallowed (for good reason I'm told!) for attributes I haven't given attr_writer ability to.
Is there a way I can bypass the mass assignment protection just for this operation only?
assign_attributes with without_protection: true seems less intrusive:
user = User.new
user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
user.name # => "Josh"
user.is_admin? # => true
#tovodeverett mentioned in the comment you can also use it with new, like this in 1 line
user = User.new({ :name => 'Josh', :is_admin => true }, :without_protection => true)
EDIT: kizzx2's Answer is a much better solution.
Kind of a hack, but...
self.new do |n|
n.send "attributes=", JSON.decode( json )["#{self.name.downcase}"], false
end
This invokes attributes= passing false for the guard_protected_attributes parameter which will skip any mass assignment checks.
You can create a user also in this way which is not doing the mass assignment.
User.create do |user|
user.name = "Josh"
end
You may want to put this into a method.
new_user(name)
User.create do |user|
user.name = name
end
end
Here's some of my production code (I had to force line breaks):
task = Task.find_or_create_by_username_and_timestamp_and_des \
cription_and_driver_spec_and_driver_spec_origin(username,tim \
estamp,description,driver_spec,driver_spec_origin)
Yes, I'm trying to find or create a unique ActiveRecord::Base object. But in current form it's very ugly. Instead, I'd like to use something like this:
task = Task.SOME_METHOD :username => username, :timestamp => timestamp ...
I know about find_by_something key=>value, but it's not an option here. I need all values to be unique. Is there a method that'll do the same as find_or_create_by, but take a hash as an input? Or something else with similat semantics?
Rails 3.2 first introduced first_or_create to ActiveRecord. Not only does it have the requested functionality, but it also fits in the rest of the ActiveRecord relations:
Task.where(attributes).first_or_create
In Rails 3.0 and 3.1:
Task.where(attributes).first || Task.create(attributes)
In Rails 2.1 - 2.3:
Task.first(:conditions => attributes) || Task.create(attributes)
In the older versions, you could always write a method called find_or_create to encapsulate this if you'd like. Definitely done it myself in the past:
class Task
def self.find_or_create(attributes)
# add one of the implementations above
end
end
I also extend the #wuputah's method to take in an array of hashes, which is very useful when used inside db/seeds.rb
class ActiveRecord::Base
def self.find_or_create(attributes)
if attributes.is_a?(Array)
attributes.each do |attr|
self.find_or_create(attr)
end
else
self.first(:conditions => attributes) || self.create(attributes)
end
end
end
# Example
Country.find_or_create({:name => 'Aland Islands', :iso_code => 'AX'})
# take array of hashes
Country.find_or_create([
{:name => 'Aland Islands', :iso_code => 'AX'},
{:name => 'Albania', :iso_code => 'AL'},
{:name => 'Algeria', :iso_code => 'DZ'}
])