Mongoid and mass assignment - ruby-on-rails

I have the following mogoid document definition/class:
class Exercise
include Mongoid::Document
field :name, :type => String
field :description, :type => String
belongs_to :group
validates_presence_of :name, :description
end
I then have the following controller and save method:
class ExercisesController < ApplicationController
respond_to :json
def create
#exercise = Exercise.create(params[:exercise])
#exercise.save!
respond_with #exercise
end
end
This seems wrong to me and open to mass assignment problems.
How do people normally protect against this and would using the strong parameters gem be a good idea?

Yes you should use the strong_parameters gem, it will be the default mass-assignment protection in rails 4

You can use attr_accessible as 'standard' protection. This of course still has the disadvantage that you expose a lot of fields to the interface, whereas you might want to expose only a few, but need to expose those fields in other controllers.

Related

Conditional attributes and methods in rails serializer 0.10

class Api::V1::BookSerializer < ActiveModel::Serializer
attributes :id, :status, :name, :author_name, :published_date
attributes :conditional_attributes if condition_1?
belongs_to :user if condition_2?
end
Here I want to put condition on action basic of the controller.
For example I will like to send conditional_attributes for only index action and not for other actions.
But rails "active_model_serializers", "~> 0.10.0" does not give any such things according to my knowledge.
Something like this should do the trick:
class Api::V1::BookSerializer < ActiveModel::Serializer
attributes :id, :status, :name, :author_name, :published_date
attribute :conditional_attribute, if: :some_condition?
belongs_to :conditional_association, if: :some_other_condition?
private
def some_condition?
# some condition
end
def some_other_condition?
# some other condition
end
end
You can also use :unless for negated conditions.
You can use instance_options or instance_reflections in your conditions if you need them (see https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/howto/passing_arbitrary_options.md) or you can use scopes (see https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/serializers.md#scope)
Note: To the best of my knowledge, this only works with attribute and association methods – it doesn't work with attributes (see https://github.com/rails-api/active_model_serializers/blob/0-10-stable/lib/active_model/serializer.rb#L204-L210) since it doesn't pass options along.
I read your comment regarding sticking with AM Serializers, but I'll still point it out: If you're looking for a more robust and flexible solution than AM Serializers, jsonapi-serializer or Blueprinter work quite well and both have support for conditional fields as well as conditional associations.
I assume you're trying to render from the controller.
You can pass options to your serializer from the call to render:
render json: #track, serializer: Api::V1::BookSerializer, return_user: return_user?, return_extra_attributes: return_extra_attributes?
You can then access that option in your serializer definition, via #instance_options[:your_option].
Here, you would likely have something like:
class Api::V1::BookSerializer < ActiveModel::Serializer
attributes :id, :status, :name, :author_name, :published_date
attributes :conditional_attributes if return_conditional_attributes?
belongs_to :user if return_user?
def return_conditional_attributes?
#instance_options[:return_extra_attributes]
end
def return_user?
#instance_options[:return_user]
end
end
return_extra_attributes? and return_extra_attributes? would be method defined in your controller
documentation here: https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/howto/passing_arbitrary_options.md

Virtual attribute in a Rails app - why is attr_writer not making my input work with my getter and setter methods?

I'm trying to roll my own tagging system. My setup is (at the moment) much like acts_as_taggable_on, with Tags, Taggable objects, and Taggings to relate the one to the other. Taggable is a module, which will be included in Events, Users, and probably a few other kinds of objects that will be taggable. At the moment I'm just trying to hook it up to work with Events.
I'm following Railscast #167.
In the railscast, the virtual attribute tag_names is made accessible with attr_writer :tag_names.
My problem is, I can't get the tag_names field to accept input unless I use attr_accessible :tag_names (ie, 'attr_accessible' instead of 'attr_writer').
when specifying attr_writer :tag_names, I submit the form and get the error: "Can't mass-assign protected attributes: tag_names". When I put attr_accessible :tag_names instead, it seems to work okay, but this is a security issue, right? (Please note: there isn't a tag_names field in the DB for the Event objects.)
And why can't I replicate the Railscast? I'm running Rails 3.2.11, and the Railscast is from 2009, but I can't find anything saying that attr_writer has been replaced with attr_accessible in this later version or anything like that.
Thanks for any help!
The relevant part of my Event form:
<%= f.input :tag_names, label: "Tags (separate by commas)" %>
My Event model:
class Event < ActiveRecord::Base
include Taggable
# Default - order by start time
default_scope :order => 'events.start_time ASC'
belongs_to :creator, :foreign_key => "creator_id", :class_name => "User"
validates_presence_of :creator
(etc)
My Taggable module:
module Taggable
extend ActiveSupport::Concern
included do
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
attr_accessible :tag_names
end
def tag(name)
name.strip!
tag = Tag.find_or_create_by_name(name)
self.taggings.find_or_create_by_tag_id(tag.id)
end
def untag(name)
name.strip!
t = Tag.find_by_name(name)
self.taggings.find_by_tag_id(t).destroy
end
# Return an array of tags applied to this taggable object
def tag_list
Tag.joins(:taggings).where(taggings: {taggable_id: self})
end
# Getter method for virtual attribute tag_names
def tag_names
#tag_names || tags.map(&:name).join(', ')
end
# Setter method for virtual attribute tag_names
def tag_names=(names)
#tag_names = names.split(",").map do |n|
Tag.find_or_create_by_name(n.strip)
end
end
end
attr_accessible and attr_writer are two completely different things. The former is a concept pre-Rails 4 where you are whitelisting attributes that are mass-assignable. The latter is creating an instance method on your class that lets you set a value publicly, but not read it.
There are also attr_reader and attr_accessor.
attr_accessor is maybe what you're confusing with attr_accessible. This method is similar to attr_writer, except it provides both a reader and writer method. attr_reader is the opposite of attr_writer in that it gives you an instance method for reading values, but not writing them.

Does ActiveModel have a module that includes an "update_attributes" method?

I've set up an ActiveModel class in my Rails app like this:
class MyThingy
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Conversion
attr_accessor :username, :favorite_color, :stuff
def initialize(params)
#Set up stuff
end
end
I really want to be able to do this:
thingy = MyThingy.new(params)
thingy.update_attributes(:favorite_color => :red, :stuff => 'other stuff')
I could just write update_attributes on my own, but I have a feeling it exists somewhere. Does it?
No, but there's common pattern for this case:
class Customer
include ActiveModel::MassAssignmentSecurity
attr_accessor :name, :credit_rating
attr_accessible :name
attr_accessible :name, :credit_rating, :as => :admin
def assign_attributes(values, options = {})
sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
send("#{k}=", v)
end
end
end
It's from here. See the link for examples.
If you find yourself repeating this approach often, you can extract this method into a separate module and include include it on demand.
It looks like they pulled it out of active record and moved it to active model in Rails 5.
http://api.rubyonrails.org/classes/ActiveModel/AttributeAssignment.html#method-i-assign_attributes
You should be able to include the module:
include ActiveModel::AttributeAssignment

Rails ActiveRecord: validate single attribute

Is there any way I can validate a single attribute in ActiveRecord?
Something like:
ac_object.valid?(attribute_name)
You can implement your own method in your model. Something like this
def valid_attribute?(attribute_name)
self.valid?
self.errors[attribute_name].blank?
end
Or add it to ActiveRecord::Base
Sometimes there are validations that are quite expensive (e.g. validations that need to perform database queries). In that case you need to avoid using valid? because it simply does a lot more than you need.
There is an alternative solution. You can use the validators_on method of ActiveModel::Validations.
validators_on(*attributes) public
List all validators that are being used to validate a specific
attribute.
according to which you can manually validate for the attributes you want
e.g. we only want to validate the title of Post:
class Post < ActiveRecord::Base
validates :body, caps_off: true
validates :body, no_swearing: true
validates :body, spell_check_ok: true
validates presence_of: :title
validates length_of: :title, minimum: 30
end
Where no_swearing and spell_check_ok are complex methods that are extremely expensive.
We can do the following:
def validate_title(a_title)
Post.validators_on(:title).each do |validator|
validator.validate_each(self, :title, a_title)
end
end
which will validate only the title attribute without invoking any other validations.
p = Post.new
p.validate_title("")
p.errors.messages
#=> {:title => ["title can not be empty"]
note
I am not completely confident that we are supposed to use validators_on safely so I would consider handling an exception in a sane way in validates_title.
I wound up building on #xlembouras's answer and added this method to my ApplicationRecord:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
end
Then I can do stuff like this in a controller:
if #post.valid_attributes?(:title, :date)
render :post_preview
else
render :new
end
Building on #coreyward's answer, I also added a validate_attributes! method:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
def validate_attributes!(*attributes)
valid_attributes?(*attributes) || raise(ActiveModel::ValidationError.new(self))
end
end

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