Can't mass-assign protected attributes: product, user - ruby-on-rails

Hello I'm working in a application where you can vote for a product and from the New action of my vote view I get this error:
ActiveModel::MassAssignmentSecurity::Error in VotesController#create
Can't mass-assign protected attributes: product, user
I make a test on rails console and it works. So I don't know what it's going on.
Here are the models:
class Product < ActiveRecord::Base
attr_accessible :title
has_many :votes
has_many :users, :through => :votes
has_attached_file :photo, :styles => { :medium => "300x300" }
before_save { |product| product.title = title.titlecase }
validates :title, presence: true, uniqueness: { case_sensitive: false }
validates :photo, :attachment_presence => true
end
class User < ActiveRecord::Base
has_many :votes
has_many :products, :through => :votes
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
end
class Vote < ActiveRecord::Base
attr_accessible :product_id, :user_id
belongs_to :product
belongs_to :user
end
Here is the vote controller
class VotesController < ApplicationController
# GET /votes
# GET /votes.json
def index
#votes = Vote.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #votes }
end
end
# GET /votes/1
# GET /votes/1.json
def show
#vote = Vote.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #vote }
end
end
# GET /votes/new
# GET /votes/new.json
def new
#vote = Vote.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #vote }
end
end
# GET /votes/1/edit
def edit
#vote = Vote.find(params[:id])
end
# POST /votes
# POST /votes.json
def create
#vote = Vote.new(params[:vote])
respond_to do |format|
if #vote.save
format.html { redirect_to #vote, notice: 'Vote was successfully created.' }
format.json { render json: #vote, status: :created, location: #vote }
else
format.html { render action: "new" }
format.json { render json: #vote.errors, status: :unprocessable_entity }
end
end
end
# PUT /votes/1
# PUT /votes/1.json
def update
#vote = Vote.find(params[:id])
respond_to do |format|
if #vote.update_attributes(params[:vote])
format.html { redirect_to #vote, notice: 'Vote was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #vote.errors, status: :unprocessable_entity }
end
end
end
# DELETE /votes/1
# DELETE /votes/1.json
def destroy
#vote = Vote.find(params[:id])
#vote.destroy
respond_to do |format|
format.html { redirect_to votes_url }
format.json { head :no_content }
end
end
end
and here is the new vote view
<%= form_for(#vote) do |f| %>
<% if #vote.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#vote.errors.count, "error") %> prohibited this vote from being saved:</h2>
<ul>
<% #vote.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :product %><br />
<%= f.text_field :product %>
</div>
<div class="field">
<%= f.label :user %><br />
<%= f.text_field :user %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Please I really need your help to solve this issues, it was very difficult to find a tutorial with has_many :through that include the complete MVC example, I think my problem is on the view, but I don't know.
Thanks!

That error message tells you everything you need to know if you look closely at it.
ActiveModel::MassAssignmentSecurity::Error in VotesController#create
Can't mass-assign protected attributes: product, user
you may not be familiar with the term "mass-assignment". its the assignment of 1 or more of an objects attributes at the time of creation. i.e. in VotesController#create.
when unprotected, mass-assignment opens you up to hackers assigning values to any and all of an objects attributes in your site's forms wether you meant to give access or not.
thats where attar_accessible comes in. it forces you to be explicit about what attributes of a model your users should have access to. any not passed as symbols into the macro will be protected attributes as in Can't mass-assign protected attributes: product, user.
the scaffolding set attr_accessible :product_id, :user_id when it created your model but it didnt know you were going to assign these with objects rather than id values.
you can fix this one of 2 ways.
change your form so that the hash-like params variable assigns like this
params[vote][product_id]
or change your model like
attr_accessible :product, :user

Related

Undefined Method Error - Single Table Inheritance - Routing error

I am creating a Single Table Inheritance model to build a comment thread and I am getting a confusing routing error
I have set up my Model
class Question < ApplicationRecord
has_many :answers, class_name: 'Questions::Answer'
end
class Answer < ApplicationRecord
has_many :answers, class_name: 'Answers::Answer'
end
with associated sub classes
module Questions
class Answer < ::Answer
belongs_to :question
validates :body, presence: true
validates :question, presence: true
end
end
module Answers
class Answer < ::Answer
belongs_to :answer
has_one :question, through: :answer
validates :body, presence: true
validates :answer, presence: true
end
end
Routes
resources :questions do
resources :answers, shallow: true
end
In my view, I render answers and a form to add an answer
<%= render #answers %></br>
<%= render "answers/form" %>
Which takes the following instances from my Question controller
def show
#answer = #question.answers.new
#answers = #question.answers.page(params[:page]).per(5)
end
However, this gives me an undefined method error:
undefined method `questions_answers_path'
In my routes, indeed the only paths are
question_answers GET /questions/:question_id/answers(.:format) answers#index
POST /questions/:question_id/answers(.:format) answers#create
new_question_answer GET /questions/:question_id/answers/new(.:format) answers#new
edit_answer GET /answers/:id/edit(.:format) answers#edit
answer GET /answers/:id(.:format) answers#show
PATCH /answers/:id(.:format) answers#update
PUT /answers/:id(.:format) answers#update
DELETE /answers/:id(.:format) answers#destroy
The question then is, why is it looking for the plural questions_answers_path, rather than question_answers_path.
The form is relatively standard:
<%= simple_form_for(#answer) do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
<div class="form-inputs">
<%= f.input :body %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
When I force the path by using <%= simple_form_for #answer, url: question_answers_path(#question) do |f| %>
I get an error trying to submit the form. Body can't be blank
Any ideas on what is wrong with my code?
For completeness:
Answers Controller
class AnswersController < ApplicationController
before_action :set_answer, only: [:edit, :update, :destroy]
before_action :set_question
def create
#answer = #question.answers.build(answer_params)
respond_to do |format|
if #answer.save
format.html { redirect_to question_path(#question) }
format.json { render :show, status: :created, location: #answer }
else
format.html { render :new }
format.json { render json: #answer.errors, status: :unprocessable_entity }
end
end
end
def update
authorize #answer
respond_to do |format|
if #answer.update(answer_params)
format.html { redirect_to question_answers_path(#question), notice: 'Answer was successfully updated.' }
format.json { render :show, status: :ok, location: #answer }
else
format.html { render :edit }
format.json { render json: #answer.errors, status: :unprocessable_entity }
end
end
end
def destroy
authorize #answer
#answer = Answers.find(params[:id])
#answer.discard
respond_to do |format|
format.html { redirect_to question_answers_path(#question), notice: 'Answer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_answer
#answer = Answer.find(params[:id])
end
def answer_params
params.fetch(:answer, {}).permit(:body, :type).merge(user_id: current_user.id, question_id: #question.id)
end
def set_question
#question = Question.find(params[:question_id])
end
end

collection_select is not creating the association table

I'm currently trying to add a collection_select of ranches to my staff
And I saw that it's better to create an extra table to make this association.
And I follow some tutorial, but is not working on my side
This is my code :
Staffs/_form :
<%= form_for(#staff) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= fields_for(#staff_ranch) do |x| %>
<div class="field">
<%= x.collection_select(:ranch_id, #all_ranch, :id, :name, { }, {:multiple => true}) %>
</div>
<%end%>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My models :
- Ranch :
has_many :ranchstaffs
has_many :staffs, :through => :ranchstaffs
- Staff :
has_many :ranchstaffs
has_many :ranches, :through => :ranchstaffs
-Ranchstaff :
belongs_to :ranch
belongs_to :staff
Staff controller :
class StaffsController < ApplicationController
before_action :set_staff, only: [:show, :edit, :update, :destroy]
# GET /ranches
# GET /ranches.json
def index
#staffs = current_user.staffs
end
# GET /ranches/1
# GET /ranches/1.json
def show
end
# GET /ranches/new
def new
#staff = Staff.new
#all_ranch = current_user.ranches
#staff_ranch = #staff.ranchstaffs.build
end
# GET /ranches/1/edit
def edit
end
# POST /ranches
# POST /ranches.json
def create
#staff = Staff.new(staff_params)
#staff.update(user_id: current_user.id)
respond_to do |format|
if #staff.save
format.html { redirect_to #staff, notice: 'Staff was successfully created.' }
format.json { render :show, status: :created, location: #staff }
else
format.html { render :new }
format.json { render json: #staff.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /ranches/1
# PATCH/PUT /ranches/1.json
def update
respond_to do |format|
if #staff.update(staff_params)
format.html { redirect_to #staff, notice: 'Staff was successfully updated.' }
format.json { render :show, status: :ok, location: #staff }
else
format.html { render :edit }
format.json { render json: #staff.errors, status: :unprocessable_entity }
end
end
end
# DELETE /ranches/1
# DELETE /ranches/1.json
def destroy
#staff.destroy
respond_to do |format|
format.html { redirect_to staffs_url, notice: 'Ranch was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_staff
#staff = Staff.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def staff_params
params.require(:staff).permit(:name, :user_id, :cat, :ranch_id)
end
end
Can you explain me why the model ranchstaff was not created after a creation of a new staff ?
As you are using fields_for you are using nested form but you are not permitting the parameters properly. First make change in your form:
<%= f.fields_for(#staff_ranch) do |x| %>
<div class="field">
<%= x.collection_select(:ranch_id, #all_ranch, :id, :name, { }, {:multiple => true}) %>
</div>
<% end %>
And then in your controller:
def staff_params
params.require(:staff).permit(:name, :user_id, :cat, ranchstaff_attributes: [ranch_id: []])
end
And in your Staff model write:
accepts_nested_attributes_for :ranchstaffs
Then your ranchstaff should be created when the User is being created.
Your ranch_id is coming in an array. So u have to specify that ranch_id would be array in strong parameters.
so your staff_params method would look like this
def staff_params
params.require(:staff).permit(:name, :user_id, :cat, :staff_ranch_attributes =>[:ranch_id => []])
end

Storing Carrierwave as json into an existing jsonb column

I'm not sure if this is even possible using Carrierwave, I'm still learning my way around Ruby and Rails so bear with me.
I have a simple form to record observations. Each observation has data that can be recorded against it and I store this in a JSONB column within the observation (I will eventually store multiple sets of data against an observation). I want to be able to upload an image through Carrierwave and store it with the data within the JSONB column rather than saving it to its own column. So my saved data for this column would be like this for example...
{
"ph":"",
"ecoli":"",
"nitri":"Less than 0.5",
"oxygen":"",
"clarity":"bottom visible",
"nitrates":"Less than 0.5",
"conditions":[
"Wildlife Death",
"Shoreline Alterations",
"Water Quality"
],
"observed_on":"2015-06-21T04:45",
"invasive_species":[
"Phragmites",
"Loosestrife",
"Dog-Strangling Vine"
],
"phosphates_threshold":"less than 0.2",
"image":[
"url": "the/url",
"thumbnail: "the/thumbnail/url",
"medium":"the/thumbnail/url"
.....
]
}
Some of the supporting code for reference...
observation.rb
class Observation < ActiveRecord::Base
belongs_to :user
has_many :comments
serialize :observation_data, HashSerializer
store_accessor :observation_data
end
observation_data.rb - tableless model
class ObservationData < ActiveRecord::Base
belongs_to :observation
mount_uploader :image, ImageUploader
end
_form.html.erb
<%= form_for #observation do |f| -%>
...Other form items
<fieldset>
<legend>Observation data</legend>
<%= render "observation_data", :f => f %>
</fieldset>
<div class="actions">
<%= f.submit "Submit", class: "button" %>
</div>
<% end %>
_observation_data.html.erb
<%= f.fields_for :observation_data, OpenStruct.new(#observation.observation_data) do |o| %>
...Other form items
<div class="field">
<%= o.label :image, "Upload an image" %>
<%= o.file_field :image %>
</div>
...Other form items
<% end %>
observations_controller.rb
class ObservationsController < ApplicationController
before_action :set_observation, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#observations = Observation.all
#user = current_user.id
end
def show
#observations = Observation.find_by(id: params[:id])
#observation_data = #observation.observation_data
#comments = #observation.comments.all
#comment = #observation.comments.build
#user = User.find_by(id: #observation.user_id)
end
def new
#observation = Observation.new
end
def edit
end
def create
#observation = Observation.new(observation_params)
#observation.user_id = current_user.id
respond_to do |format|
if #observation.save
format.html { redirect_to #observation, notice: 'Observation was successfully created.' }
format.json { render action: 'show', status: :created, location: #idea }
else
format.html { render action: 'new' }
format.json { render json: #observation.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #observation.update(observation_params)
format.html { redirect_to #observation, notice: 'Observation was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #observation.errors, status: :unprocessable_entity }
end
end
end
def destroy
#observation.destroy
respond_to do |format|
format.html { redirect_to root_url }
format.json { head :no_content }
end
end
private
def set_observation
#observation = Observation.find(params[:id])
end
def observation_params
params.require(:observation).permit(:user_id, :lat, :lng, :name, :description, :comment, observation_data: [:observed_on, :image, :ph, :oxygen, :ecoli, :phosphates_threshold, :clarity, :nitri, :nitrates, wildlife: [], conditions: [], invasive_species: []])
end
end
The problem for me is mount_uploader :image, ImageUploader is always looking for an :image column, how can I merge the Carrierwave response this with JSON in another column? Is it even possible?

Couldn't find Product without an ID build association

I have a problem with Ruby on Rails.When I try to create a line_items which is the association of product and cart ..siguiendo the book Agile web development with Rails.
Here's the code:
def create
product = Product.find(params[:product_id])
##line_item = LineItem.new(line_item_params)
#line_item = #cart.line_items.build(product: product)
respond_to do |format|
if #line_item.save
format.html { redirect_to #line_item.cart,
notice: 'Line item was successfully created.' }
format.json { render action: 'show',
status: :created, location: #line_item }
else
format.html { render action: 'new' }
format.json { render json: #line_item.errors,
status: :unprocessable_entity }
end
end
end
if I uncomment the line:
line_item = LineItem.new(line_item_params)
and comment
#product = Product.find(params[:product_id])
##line_item = #cart.add_product(product: product)
will it work?
I know in the line_item_params method which is the next
def line_item_params
params.require(:line_item).permit(:product_id, :cart_id)
end
allowable parameters defined to create the object.
Can someone help me build this?
Thanks
This is my form Code for the line_items, I can't copy the code because I blocked certain parts of code
<%= form_for(#line_item) do |f| %>
<% if #line_item.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#line_item.errors.count, "error") %> prohibited this line_item from being saved:</h2>
<ul>
<% #line_item.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :product_id %><br>
<%= f.text_field :product_id %>
</div>
<div class="field">
<%= f.label :cart_id %><br>
<%= f.text_field :cart_id %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
This is the new method
def new
#line_item = LineItem.new
end
This is the Product Model
class Product < ActiveRecord::Base
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_item
validates :title, :description, :image_url, presence: true
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)\Z}i,
message: 'must be a URL for GIF, JPG or PNG Image.'
}
validates :title, length: {minimum: 10}
#para ultimo producto para cache
def self.latest
Product.order(:updated_at).last
end
private
def ensure_not_referenced_by_any_line_item
if line_items.empty?
return true
else
errors.add(:base, 'Line Items present')
return false
end
end
end
This is the Cart Model
class Cart < ActiveRecord::Base
has_many :line_items, dependent: :destroy
def add_product(product_id)
current_item= line_items.find_by(product_id: product_id)
if current_item
current_item.quantity +=1
else
current_item= line_items.build(product_id: product_id)
end
current_item
end
end
This is the line_item Model
class LineItem < ActiveRecord::Base
belongs_to :product
belongs_to :cart
end
And this is my Module Cart
module CurrentCart
extend ActiveSupport::Concern
private
def set_cart
#cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
#cart = Cart.create
session[:cart_id] = #cart.id
end
end
Change this and try:
def create
#cart = Cart.find(params[:line_item][:cart_id])
#product = Product.find(params[:line_item][:product_id])
#line_item = #cart.line_items.build(product: #product)
respond_to do |format|
if #line_item.save
format.html { redirect_to #line_item.cart,
notice: 'Line item was successfully created.' }
format.json { render action: 'show',
status: :created, location: #line_item }
else
format.html { render action: 'new' }
format.json { render json: #line_item.errors,
status: :unprocessable_entity }
end
end
end
Thanks a lot Choco , I did what you sent and run
#cart = Cart.find(params[:line_item][:cart_id])
#product = Product.find(params[:line_item][:product_id])
#line_item = #cart.line_items.build(product: #product)
The arrays [:line_item][:cart_id].

Show list of attachments using Paperclip in Rails Application

I am having issues with paperclip in my rails application. I am able to attach multiple files (PDFs) to my form, but when I try to show more than 1 attachment in the show.html.erb file I get errors.
The code that works in the edit and new views:
<%= f.fields_for :assets do |asset| %>
<% if asset.object.new_record? %>
<%= asset.file_field :document %>
<% end %>
<% end %>
</div>
<div class="existingPaperclipFiles">
<% f.fields_for :assets do |asset| %>
<% unless asset.object.new_record? %>
<div class="thumbnail">
<%= link_to( image_tag(asset.object.document.url(:thumb)), asset.object.document.url(:original) ) %>
<%= asset.check_box :_destroy %>
</div>
<% end %>
<% end %>
</div>
I have created a separate assets model to keep all the attachments related to my equipment model. When I create a "link_to asset.object.document.url" in the show view I get NoMethod errors. I want to attach both .doc, PDF, and image files to my application if there is a better way than paperclip please help!
The assets model:
class Asset < ActiveRecord::Base
belongs_to :equipment
has_attached_file :document, :styles => {:thumb => '150x150#', :medium => '300x300#', :large => '600x600#' }
end
The equipment model:
class Equipment < ActiveRecord::Base
validates :equipment_id, presence: true
validates :location, presence: true
has_many :assets, :dependent => :destroy
accepts_nested_attributes_for :assets, :allow_destroy => true
end
My equipment_controller:
class EquipmentController < ApplicationController
def index
#equipment = Equipment.paginate(page: params[:page])
end
def show
#equipment = Equipment.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #equipment }
end
end
def new
#equipment = Equipment.new
5.times {#equipment.assets.build}
end
def create
#equipment = Equipment.new(params[:equipment])
respond_to do |format|
if #equipment.save
format.html { redirect_to #equipment, notice: 'Equipment was successfully added.' }
format.json { render json: #equipment, status: :created, location: #equipment }
else
format.html { render action: "new" }
format.json { render json: #equipment.errors, status: :unprocessable_entity }
end
end
end
def edit
#equipment = Equipment.find(params[:id])
5.times {#equipment.assets.build}
end
def update
#equipment = Equipment.find(params[:id])
respond_to do |format|
if #equipment.update_attributes(params[:equipment])
format.html { redirect_to #equipment, notice: 'Equipment was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #equipment.errors, status: :unprocessable_entity }
end
end
end
def destroy
#equipment = Equipment.find(params[:id])
#equipment.destroy
respond_to do |format|
format.html { redirect_to equipment_url }
format.json { head :no_content }
end
end
private
def correct_user
#Equipment = Equipment.find(params[:id])
redirect_to(root_path) unless current_user?(#Equipment)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
Any help is greatly appreciated.
You didn't give the code for the show view where you're having the error, but you should be able to do something like this to show thumbnails with links to the original files:
<% #equipment.assets.each do |asset| %>
<%= link_to image_tag(asset.document.url(:thumb)), asset.document.url(:original) %>
<% end %>
I'm not sure why you have "object" in your Paperclip URLs, as I haven't seen that format used for Paperclip.
I actually was able to figure this out by doing the following:
<% for asset in #equipment.assets %>
<p>
<%= link_to asset.document_file_name,
asset.document.url(:original) %>
</p>
<% end %>

Resources