Can't mass-assign protected attributes even if I use attr_accessible - ruby-on-rails

I'm working on a Rails 3.2.2 application which has JSON APIs and I use a
CLI client for inserting some data. It works fine except for the Author
model. When I try to create a new post (Post belongs_to :author and
Author has_many :posts) I get the following error :
<h1>
ActiveModel::MassAssignmentSecurity::Error in PostsController#create
</h1>
<pre>Can't mass-assign protected attributes: name</pre>
I did lots of researches on the topic but I found no working solution
:-(
I use attr_accessible to avoid MassAssignent errors and it works for all
others models but not for the "Author" name attribute.
Here is the Author model :
class Author < ActiveRecord::Base
attr_accessible :name, :email
extend FriendlyId
friendly_id :name, use: :slugged
# some validations
has_many :posts
#authlogic
acts_as_authentic
# some stuffs
end
Actually, I have disabled whitelist_attributes and it solved my problem
but I suppose that it is not the convenient way to do this (and probably
not a good idea).
My questions are : Why the attr_accessible does not work here ? And how
can I solve the problem without disabling the whitelist ?
Thank you,
Revan
EDIT :
The method which creates the new post :
def create
#post = Post.new(params[:post])
#post.author = current_author
# respond to etc.
end
current_author finds the author using a given API Key.

I found the solution ! :-)
The problem was that I used acts_as_taggable_on_steroids plugin which does not work on Rails 3.2 ...
Since "Author" is the only model which has a :name attribute, I thought that the problem came from Author ... but the problem was in the Tag model (which is in the acts_as_taggable_on_steroid plugin). Indeed, its :name attribute is not "accessible".
So, I use the acts_as_taggable_on gem (https://github.com/mbleigh/acts-as-taggable-on)
which correctly works on Rails 3.x

Related

FriendlyID for child

I have a Project model that belongs to a User. A User has_many Projects. I have setup FriendlyID according to the gem instructions for User model, however it is not working in this scenario.
<%= link_to gravatar_for(project.user, size: 50), project.user %>
The above creates a link with the numerical ID of the user (project.user) instead of a friendly URL (i.e. http://localhost:3000/users/102 instead of what I want, http://localhost:3000/users/*random-gen-string*).
User.rb file:
class User < ApplicationRecord
extend FriendlyId
friendly_id :generated_slug, use: :slugged
def generated_slug
require 'securerandom'
#random_slug ||= persisted? ? friendly_id : SecureRandom.hex(15)
end
I think the problem is that project.user is set in the projects_controller.rb to the user ID (via current_user.projects.build...). If I could somehow access the Friendly ID for User in the projects_controller.rb I may be able to save it in the database and access it like project.user_friendly_id. Not sure..
Relevant code in projects_controller:
def create
#project = current_user.projects.build(project_params)
# code
end
What is the best way to go about making the above link link to the Friendly ID and not the user (i.e. http://localhost:3000/users/*random-gen-string* is what I want instead of http://localhost:3000/users/102)?
Update:
As discussed in the chatroom, User.find_each(&:save!) reveals the errors when saving the User model. In the above case, the lack of password input was preventing the User records from being saved. Removing the validation temporarily allowed saving the User and thus regenerating slugs.
(Original answer left for history)
You can override the to_param method in your User model like this
class User < ApplicationRecord
# some code
def to_param
slug
end
And then that is used to generate the link. More on that in the guides.
When you build your link_to you can call the user.slug to ensure that you get the proper info
<%= link_to gravatar_for(project.user, size: 50), project.user.slug %>
that will generate the link that you are expecting http://localhost:3000/users/random-gen-string
Here is an example of cities with increasing order of specificity.
def slug_candidates
[
:name,
[:name, :city],
[:name, :street, :city],
[:name, :street_number, :street, :city]
]
end
In case of conflicting slugs it is better to use user-friendly names then UUID (e.g. 2bc2-d3dd-4f29-b2ad)

Why won't rails create associated records/objects from nested form using strong parameters?

I'm trying to create a record and it's associated records from a nested form using strong parameters. My primary model is:
class MaterialDonationRequest < ActiveRecord::Base
has_many :donation_items, dependent: :destroy
accepts_nested_attributes_for :donation_items, allow_destroy: true
validates :name, presence: true
attr_accessor :due_on_event, :date, :donation_items_attributes, :event_id
end
My associated (nested) model is:
class DonationItem < ActiveRecord::Base
validates :name, presence: true
belongs_to :material_donation_request
belongs_to :global_profile
validates :name, presence: true
attr_accessor :_destroy
end
In my material_donation_requests_controller.rb, I have the following for strong parameters:
def material_donation_request_params
params.require(:material_donation_request).permit(:name, :description, :event_flag, :due_on_event, :date, :event_id, donation_items_attributes: [:id, :name, :description, :amount, :_destroy])
end
Here's the line in my create method where I create the object:
#material_donation_request = MaterialDonationRequest.new(material_donation_request_params)
After doing this, #material_donation_request is created and populated correctly from the form. But the associated donation_items do not get created. For instance, in the debugger, when I enter #material_donation_request.donation_items.first, Rails returns nil.
For reference, here is what Rails returns for material_donation_request_params in the manual tests I'm running:
{"name"=>"Name", "description"=>"", "due_on_event"=>"true", "date"=>"", "donation_items_attributes"=>{"0"=>{"name"=>"", "amount"=>"1", "_destroy"=>""}, "1427122183210"=>{"name"=>"", "amount"=>"2", "_destroy"=>""}}}
Why isn't Rails creating the associated objects from the form as well? Everywhere I've looked, it seems like this structure should create everything, and a subsequent save should save everything (or at least throw validation errors as in this case-see update below). Is there something I'm missing?
Update
Since it was brought up in the answers, yes, the material_donation_params shown above would not pass validation. That's the scenario I've been manually testing. It should generate a validation error on save, but instead, simply saves the MaterialDonationRequest with no errors of any kind, and saves nothing to DonationItems.
To be clear, though, if I fill out the form completely and get the following material_donation_request_params:
{"name"=>"Name", "description"=>"", "due_on_event"=>"true", "date"=>"", "donation_items_attributes"=>{"0"=>{"name"=>"first", "amount"=>"1", "_destroy"=>""}, "1427122183210"=>{"name"=>"second", "amount"=>"2", "_destroy"=>""}}}
and then do #material_donation_request.save, it only saves the MaterialDonationRequest, and not any of the DonationItems.
Final Update
Okay. I've deleted my previous "final update" because what I wrote, and what I wrote in some of the comments was wrong. What ended up fixing this was not an update to Rails 4.1.8. I ran the bundle update command before actually saving the gem file with the new Rails version. So really, what ended up fixing this was simply updating all the gems that didn't have fixed version numbers. God only knows why things weren't working with the previous set of gems. Sorry that this isn't so helpful...
From Rails Validations guide
presence
This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either nil or a blank string, that is, a string that is either empty or consists of whitespace.
You are requiring donation_item to be present, but your resulting params hash clearly has donation names blank, validation is failing. Calling save! when debugging these things can be helpful since it would throw a error on failure.
I figured out the answer. In total desperation, I upgraded my Rails version from 4.0.2 which is what I had been using, to 4.1.8. After doing this, with no other changes whatsoever (except gem dependencies, of course), it just started working the way it's supposed to. So I guess Rails 4.0.2 has a problem with nested forms and strong parameters.

How to make localized Paperclip attachments with Globalize3?

I have a project using Paperclip gem for attachments and Globalize3 for attribute translation. Records need to have a different attachment for each locale.
I though about moving Paperclip attributes to translation table, and that might work, but I don't think that would work when Paperclip needs to delete attachments.
What's the best way to achieve something like that?
UPDATE: to be clear, I want this because my client wants to upload different images for each locale.
Unfortunately I didn't find a way to do this using Globalize3. In theory, I could have added a separate model for image and add image_id to list of translated columns (to have something like MainModel -> Translation -> Image), but it seems that Globalize has some migration issues with non-string columns.
Instead of using Globalize3, I did this with a separate Image model with locale attribute and main model which accepts nested attributes for it. Something along the lines of:
class MainModel < ActiveRecord::Base
has_many :main_model_images
accepts_nested_attributes_for :main_model_images
# return image for locale or any other as a fallback
def localized_image(locale)
promo_box_images.where(:locale => locale).first || promo_box_images.first
end
end
class MainModelImage < ActiveRecord::Base
belongs_to :main_model
has_attached_file :image
validates :locale,
:presence => true,
:uniqueness => { :scope => :main_model_id }
end
Tricky part was getting form to accept nested attributes only for one image, instead of all images in has_many relation.
=f.fields_for :main_model_images, #main_model.image_for_locale(I18n.locale) do |f_image|
=f_image.hidden_field :locale
=f_image.label :image
You could also try the paperclip-globalize3 gem, it should handle the case you describe. https://github.com/emjot/paperclip-globalize3
Ok since you asked me to share my solution to this problem even though I am using Carrierwave as a library for uploading here is it:
Ok so I would have a model setup like this:
class MyModel < ActiveRecord::Base
# ...
translates :attr_one, :attr_two, :uploaded_file
Now what I need for CarrierWave to work is place to attach the uploader to and that can be done on the Translation model
Translation.mount_uploader :uploaded_file, FileUploader
end
Now for your question about deleting, I think though I haven't needed to do it but it should work as the README says it should but on the translation model. https://github.com/jnicklas/carrierwave#removing-uploaded-files
MyModel.first.translation.remove_uploaded_file!
I haven't taken a look at paperclip for a good 2 years and if this is not applicable knowledge I suggest you try out carrierwave.

Accepts Nested Attribute with a virtual attribute

I have a Project model which accepts nested attributes for tasks. And Task has a virtual attribute "name". So every time I change the name, it gets persisted as encrypted_task_name before update. On the project edit page the form has a input field for task name (and not encrypted_task_name). When I change the name and since name is a virtual attribute, Rails doesn't detect a change in Task and doesn't update that task while updating Project.
How do I make sure that task is saved even if its virtual attributes are changed during Project update?
One option that I don't want to use is :autosave => true on task.rb since I task is rarely updated.
I ran into the same problem. Using :autosave => true didn't even work for me. I managed to solve it by adding attribute_will_change!(:my_virtual_attribute) to the writer for my virtual attribute. So, in your case:
class Task < ActiveRecord::Base
..
def name=(the_name)
attribute_will_change!(:name)
..
end
..
end
This marks the object as unchanged or dirty, and that makes update_attributes save the nested model correctly.
Links:
http://apidock.com/rails/ActiveRecord/Dirty/attribute_will_change%21
http://ryandaigle.com/articles/2008/3/31/what-s-new-in-edge-rails-dirty-objects
For Rails 5.1 and up it's advisable to use attribute instead of attr_accessor as it dirties up the object, thus triggering the validation.
class Task < ActiveRecord::Base
attribute :name, :string
end
In general, I'd recommend RailsCasts.com - episodes 167 and 16
http://railscasts.com/episodes/167-more-on-virtual-attributes and
http://railscasts.com/episodes/16-virtual-attributes
In episode 167, Ryan does something very similar
If this doesn't help, could you post the relevant code for your Project and Task models?
Check out Attribute Filters gem. It takes care of virtual attributes tracking (automagically wrapping setter methods) by adding attr_virtual DSL keyword and lets you do other things, like declarative filtering of attributes:
class User < ActiveRecord::Base
include ActiveModel::AttributeFilters::Common::Split
split_attribute :real_name => [ :first_name, :last_name ]
before_validation :filter_attributes
attr_virtual :real_name
attr_accessor :real_name
end

friendly_id and ActiveScaffold conflict

I have friendly_id and ActiveScaffold installed for my Rails application.
Because not all of my models have unique name fields I have to use the Slugged Model to make it work. friendly_id does the job flawlessly I have friendly URLs and I can load the objects using the friendly id.
But when I want to create a new object with ActiveScaffold, it says the following error message:
ActiveScaffold::ReverseAssociationRequired
(Association slugs: In order to
support :has_one and :has_many where
the parent record is new and the child
record(s) validate the presence of the
parent, ActiveScaffold requires the
reverse association (the belongs_to).)
Of course I cannot create the belongs_to association in that side because it's created by the friendly_id module and every model which works slugged way should be included there.
The model looks like this:
class FooBar < ActiveRecord::Base
has_friendly_id :name, :use_slug => true, :approximate_ascii => true
end
In my ApplicationController:
class Admin::FooBarsController < Admin::ApplicationController
active_scaffold :foo_bar do |config|
config.list.columns = [ :id, :name ])
config.update.columns = [ :name ]
config.create.columns = config.update.columns
end
end
Is there a way to make this work?
The versions: friendly_id 3.2.0, ActiveScaffold latest in the rails-2.3 git branch.
UPDATE: Seems like it does not conflict in production mode.
calling
has_friendly_id :name, :cache_column => 'cached_slug', :use_slug => true
... creates a has_many and a has one associations pointing to a slug AR model which hasn't any polymorphic belongs to association properly defined.
So basically what you need to do to solve this error is to define the reverse associations in the controller of your parent model (the one who has friendly_id stuff)
active_scaffold :products do |config|
...
config.columns[:slug].association.reverse = :product
config.columns[:slugs].association.reverse = :product
end
and it works :-)
PS : I use friendly_id as gem and ActiveScaffold VHO master branch for rails 3
In the past I have the same problem , i have solved , but i dont remember my solution , lookin at my code the only relevant hack is to use friendly_id as plugin and load it at last with config.plugin in environemnt.rb
aviable_plugins = Dir.glob(RAILS_ROOT+"/vendor/plugins/*").collect {|i| i.split("/").last }
config.plugins = aviable_plugins + [:friendly_id] #friendly_id must be last
I'M NOT SURE ,sorry, but if you try let my know.
sorry for my english to

Resources