I want to make a product page for a small app. This product page should allow the user to add multiple photos. So naturally there are three models. User, Product and Photo. User has_many products and products has_many photos.
All was dandy but whenever I try to add a number of photos I get this error.
ActiveModel::MassAssignmentSecurity::Error in ProductsController#create
Can't mass-assign protected attributes: photos_attributes
products controller
def new
#product = Product.new
#photo = Photo.new
4.times{ #product.photos.build }
end
def create
#product = current_user.products.new(params[:product])
#photo = current_user.photos.new(params[:photo])
if #product.valid? && #photo.valid?
#product.save
#photo.product_id = #product.id
#photo.save
render "show", :notice => "Sale created!"
else
render "new", :notice => "Somehting went wrong!"
end
end
new product page(HAML)
= form_for #product,:url => products_path, :html => { :multipart => true } do |f|
- if #product.errors.any?
.error_messages
%h2 Form is invalid
%ul
- for message in #product.errors.full_messages
%li
= message
- if #photo.errors.any?
.error_messages
%h2 Image is invalid
%ul
- for message in #photo.errors.full_messages
%li
= message
%p
= f.label :name
= f.text_field :name
%p
= f.label :description
= f.text_field :description
%p
= f.fields_for :photos do |fp|
=fp.file_field :image
%br
%p.button
= f.submit
Product Model
class Product < ActiveRecord::Base
attr_accessible :description, :name, :price, :condition, :ship_method, :ship_price, :quantity, :photo
has_many :photos, dependent: :destroy
accepts_nested_attributes_for :photos
belongs_to :user
end
Photo model
class Photo < ActiveRecord::Base
attr_accessible :image
belongs_to :product
has_attached_file :image, styles: { medium: "320x240>", :thumb => "100x100>"}
end
schema.rb
create_table "photos", :force => true do |t|
t.integer "product_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
end
create_table "products", :force => true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "user_id"
end
Edit: when i add :photos_attributes to product model attr_accessible i get an error that says Image can't be blank !!
Just add photos_attributes to the attr_accessible list in the Product model. Or even better, try to migrate to strong_parameters gem, will save you sometime when you migrate to Rails 4.
Update
In create action, you create a #photo variable from params[:photo], which is knows to return nil, yielding the object invalid always. That said, you don't need to create that variable as Photo objects are already made for as you use accepts_nested_attributes_for already. I understand that you need to know if the Photos are created successfully, but you shouldn't be worrying about it as errors from product's new photos will propagate to the product, stopping the saving process.
Related
I've spent a few hours trying to solve this problem in a Rails 5 project that I have. The issue is that I keep getting:
Unpermitted parameters: :item_instance_ids, :note_ids
when I submit a form. I believe that the relationships between the models are wrong. I'm using a polymorphic relationship which is the first time I've used it. I've looked through so many posts on StackOverFlow as well as guides on the web but nothing seems to help me.
Basically, I have an incoming purchases form - like an ordering form and within that form you should be able to add multiple items, like a laptop, keyboard, monitor, to the order => the item instances model.
Anyways, here is my code:
incoming_purchases.rb:
class IncomingPurchase < ApplicationRecord
# Relations
has_many :item_instance, :as => :instance_wrapper
has_many :notes, :as => :notable
belongs_to :user
end
item_instance.rb
class ItemInstance < ApplicationRecord
# Relations
belongs_to :instance_wrapper, polymorphic: true
belongs_to :item
belongs_to :user
has_many :notes, :as => :notable
end
views/incoming_purchases/_form.html.erb:
<%= simple_form_for(#incoming_purchase) 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.association :item_instance, as: :check_boxes, :label_method => lambda { |item_instance| "#{item_instance.item.description}" } %>
<%= f.label(:date_ordered, "Order Date:") %>
<%= f.text_field(:date_ordered, class: 'form-control-date') %>
<%= f.association :user, :label_method => lambda { |user| "#{user.username}" } %>
<%= f.input :order_number %>
<%= f.input :vendor %>
<%= f.input :po_number %>
<%= f.input :tax %>
<%= f.input :shipping %>
<%= f.association :notes %>
</div>
<div class="form-actions">
<%= f.button :submit, class: "btn btn-outline-success" %>
</div>
<% end %>
incoming_puchases_controller.rb:
class IncomingPurchasesController < ApplicationController
before_action :set_incoming_purchase, only: [:show, :edit, :update, :destroy]
def new
#incoming_purchase = IncomingPurchase.new
end
def create
puts '*********************'
puts params
puts '*********************'
puts incoming_purchase_params
puts '**********************'
#incoming_purchase = IncomingPurchase.new(incoming_purchase_params)
respond_to do |format|
if #incoming_purchase.save
format.html { redirect_to #incoming_purchase, notice: 'Incoming purchase was successfully created.' }
format.json { render :show, status: :created, location: #incoming_purchase }
else
format.html { render :new }
format.json { render json: #incoming_purchase.errors, status: :unprocessable_entity }
end
end
end
private
def set_incoming_purchase
#incoming_purchase = IncomingPurchase.find(params[:id])
end
def incoming_purchase_params
params.require(:incoming_purchase).permit(:item_instances_id, :date_ordered, :user_id, :order_number, :vendor, :po_number, :tax, :shipping, :notes_id)
end
end
schema.rb:
ActiveRecord::Schema.define(version: 2020_08_31_200026) do
create_table "incoming_purchases", options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
t.bigint "item_instances_id"
t.date "date_ordered"
t.bigint "user_id"
t.string "order_number"
t.string "vendor"
t.integer "po_number"
t.decimal "tax", precision: 8, scale: 2
t.decimal "shipping", precision: 8, scale: 2
t.bigint "notes_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["item_instances_id"], name: "index_incoming_purchases_on_item_instances_id"
t.index ["notes_id"], name: "index_incoming_purchases_on_notes_id"
t.index ["user_id"], name: "index_incoming_purchases_on_user_id"
end
create_table "item_instances", options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
t.integer "inv_number"
t.string "serial"
t.integer "po_number"
t.date "po_date"
t.date "invoice"
t.date "date_out"
t.decimal "cost", precision: 8, scale: 2
t.string "acro"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "item_id"
t.index ["item_id"], name: "fk_rails_6ea33fd9d0"
end
add_foreign_key "incoming_purchases", "item_instances", column: "item_instances_id"
end
Oh, on the controller I tried:
params.require(:incoming_purchase).permit({ :item_instance_ids => [] }, :date_ordered, :user_id, :order_number, :vendor, :po_number, :tax, :shipping, :notes_id)
Again, I think the problem is how the relationship is set up between these two models. Thank you for any help.
I tried changing my permit params to the following:
params.require(:incoming_purchase).permit(:item_instances_id, :date_ordered, :user_id, :order_number, :vendor, :po_number, :tax, :shipping, notes_id: [], item_instances_id: [])
I was able to add an item but of course item_instances_id did not go through. When the params comes through it looks like this:
{"utf8"=>"✓", "authenticity_token"=>"d3jF73WyKCs69RSCFDvQlh7RyUAg0GQk8m7GKHX6/tt+Ve/1Y1oE5P1UtIMJfCIYS+YL0DwZth9UlDcnyW1uiA==", "incoming_purchase"=>{"item_instance_ids"=>["", "31"], "date_ordered"=>"2020-09-01", "user_id"=>"2", "order_number"=>"1", "vendor"=>"1", "po_number"=>"1", "tax"=>"1", "shipping"=>"1", "note_ids"=>[""]}, "commit"=>"Create Incoming purchase", "controller"=>"incoming_purchases", "action"=>"create"}
notice the item_instance_ids however, on the incoming_purchases model it's
item_instances_id notice the position of that s on ids and instances.
It looks like the filters you are passing into permit are not correct.
It probably needs to be note_ids: [] as this is a has_many relationship.
And when passing nested parameters into permit they should be placed at the end. So, you also have to move item_instance_ids to the end, either before or after note_ids: [].
Edit
You might be better off with a has_many :though relationship for tying items to a purchase. I'm not sure how your Item model looks like so I kept it simple.
incoming_purchase.rb
class IncomingPurchase < ApplicationRecord
has_many :purchase_items
has_many :items, through: :purchase_items
end
purchase_item.rb
class PurchaseItem < ApplicationRecord
belongs_to :incoming_purchase
belongs_to :item
end
item.rb
class Item < ApplicationRecord
has_many :purchase_items
has_many :incoming_purchases, through: :purchase_items
end
I'm trying to increment new values to an array but the old values get deleted. As you can see in the following, I had one image there and now its NULL, but the new image is there.
SQL (1.5ms) UPDATE "attachments" SET "media_files" = $1, "updated_at" = $2 WHERE "attachments"."id" = $3 [["media_files", "{NULL,image4.jpg}"], ["updated_at", "2018-10-25 09:12:05.564281"], ["id", 11]]
I'm using the carrierwave gem and this is the method I have inside the controller in order to keep the existing values and increment the new ones:
def create
files = #attachment.media_files # copy the old images
files += params[:item][:media_files] # add new file to the files
#attachment.assign_attributes(:media_files => files) # assign back
if #attachment.save
flash[:notice] = "Media files where successfully uploaded"
redirect_back fallback_location: root_path
else
flash[:alert] = "Failed to upload media files"
redirect_back fallback_location: root_path
end
end
And the form is:
<%= form_for #item, url: create_image_path(#attachment), method: :post , :html => {:id => "form"} do |f| %>
<%= f.file_field :media_files, multiple: true %>
<%= f.submit 'Add' %>
<% end %>
Models associations:
class Item < ApplicationRecord
has_many :attachments, dependent: :destroy
accepts_nested_attributes_for :attachments, allow_destroy: true
end
class Attachment < ApplicationRecord
belongs_to :item
mount_uploaders :media_files, AttachmentUploader
validates_presence_of :media_files
end
The schema for the two models:
create_table "attachments", force: :cascade do |t|
t.integer "item_id"
t.integer "account_id"
t.string "media_files", default: [], array: true
t.string "content_type"
t.boolean "success"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "items", force: :cascade do |t|
t.string "title"
t.string "description"
t.integer "category_id"
t.integer "account_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The route for the create method:
post "item/:id/uploads/media_files/:id"=> "attachments#create", :as => :create_image
Any idea what I might be missing here?
You can go with your own code the modification need to done is: -
def create
exitsting_files = #attachment.media_files # copy the old images
new_file = params[:item][:media_files] # new file to the files
new_attachment = Aattachment.new(:media_files => new_file, id: #item.id) #New attachment submitted by form
if new_attachment.save
#push back your exitsting_files along with new one
#attachment.media_files.push(exitsting_files)
#attachment.save
flash[:notice] = "Media files where successfully uploaded"
redirect_back fallback_location: root_path
else
flash[:alert] = "Failed to upload media files"
redirect_back fallback_location: root_path
end
end
However this should be done with nested_atrributes_for concept
<%= form_for #item, url: create_image_path(#attachment), method: :post , :html => {:id => "form"} do |f| %>
<%= f.fields_for : attachments do |ff| %>
<%= ff.file_field :media_files, multiple: true %>
<%end%>
<%= f.submit 'Add' %>
<% end %>
And so on... you can get lots of example for nested form
I am having a trouble that I am new with ROR and want to save some images for the organization using nest attributes or for simplicity just a String in order to try the nested attributes saving in the database but actually it is not saved.
Organization Model
class Organization < ApplicationRecord
has_secure_password
has_many :needs, dependent: :destroy
has_many :org_images, dependent: :destroy
has_many :stringas, dependent: :destroy
accepts_nested_attributes_for :org_images, :reject_if => lambda { |t| t['org_image'].blank? }
accepts_nested_attributes_for :stringas, :reject_if => lambda { |t| t['stringa'].blank? }
Schema
create_table "org_images", force: :cascade do |t|
t.string "caption"
t.integer "organization_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "photo_file_name"
t.string "photo_content_type"
t.integer "photo_file_size"
t.datetime "photo_updated_at"
end
create_table "stringas", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "organization_id"
end
Organization Controller
def new
#organization = Organization.new
3.times {#organization.org_images.build}
#organization.stringas.build # added this
end
def organization_params
params.require(:organization).permit(:org_name, :email, :password, :info,:image, :website_URL, :contacts, :logo_url , :password_confirmation ,stringas_attributes:[:name,:id,:organization_id,:created_at,:updated_at] ,org_images_attributes: [:id,:organization_id,:caption, :photo_file_name, :photo_content_type,:photo_file_size,:photo_updated_at])
end
Organization Form
<div class= "field">
<% if builder.object.new_record? %>
<p>
<%= builder.label :caption, "Image Caption" %>
<%= builder.text_field :caption %>
</p>
<p>
<%= builder.label :photo, "Image File" %>
<%= builder.file_field :photo %>
</p>
<% end %>
<% if builder.object.new_record? %>
<p>
<%= builder.label :name %>
<%= builder.text_field :name%>
</p>
<% end %>
<% end %>
Stringa and org_image Models
class OrgImage < ApplicationRecord
belongs_to :organization
has_attached_file :photo, :styles => { :small => "150x150>", :large => "320x240>" }
validates_attachment_presence :photo
validates_attachment_size :photo, :less_than => 5.megabytes
end
class Stringa < ApplicationRecord
belongs_to :organization
end
Organization cotroller create
def create
#organization = Organization.new(organization_params)
respond_to do |format|
if #organization.save
session[:organization_id]=#organization.id
format.html { redirect_to #organization, notice: 'Organization was successfully created.' }
format.json { render :show, status: :created, location: #organization }
else
format.html { render :new }
format.json { render json: #organization.errors, status: :unprocessable_entity }
end
end
end
git repository
Thanks for your help
It seems that problem lies in unpermitted org_images attributes in your OrganizationsController. You should add this to your organization_parameters method:
params.require(:organization).permit( ... , org_images_attributes: [:photo, :caption])
EDIT:
After digging a bit deeper I found out that above solution isn't always working. There's an issue on this topic in Rails repo on GitHub. If you want to find nice workaround that'll suit your needs you should read it, or check out this answer.
When I post a form to create a new inquiry with a child comment (in the app, inquiries can have multiple comments), the comment is not getting built. It works when remove the presence validations. So it has to do with the order in which things are built and saved. How to preserve the validations and keep the code clean?
(The following is an example so it may not be exactly runable)
models/inquiry.rb
class Inquiry < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :inquiry
belongs_to :user
validates_presence_of :user_id, :inquiry_id
controllers/inquiry_controller.rb
expose(:inquiries)
expose(:inquiry)
def new
inquiry.comments.build :user => current_user
end
def create
# inquiry.save => false
# inquiry.valid? => false
# inquiry.errors => {:"comments.inquiry_id"=>["can't be blank"]}
end
views/inquiries/new.html.haml
= simple_form_for inquiry do |f|
= f.simple_fields_for :comments do |c|
= c.hidden_field :user_id
= c.input :body, :label => 'Comment'
= f.button :submit
database schema
create_table "inquiries", :force => true do |t|
t.string "state"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "comments", :force => true do |t|
t.integer "inquiry_id"
t.integer "user_id"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
end
Basically, before saving you are also testing the presence of inquiry_id, the return association from comment to inquiry, that cannot be set until the comment is saved. An alternate way to achieve this and still have your validations intact would be the following:
comment = Comment.new({:user => current_user, :body => params[:body]
comment.inquiry = inquiry
comment.save!
inquiry.comments << comment
inquiry.save!
Or an alternate way would be
= simple_form_for inquiry do |f|
= f.simple_fields_for :comments do |c|
= c.hidden_field :user_id
= c.hidden_field :inquiry_id, inquiry.id
= c.input :body, :label => 'Comment'
= f.button :submit
Basically adding the following line in your comments form
= c.hidden_field :inquiry_id, inquiry.id
I'm creating basic message board where many comments belong to a post and a post belongs to only one topic. My issue is that I'm not sure how create a new Topic from the Post model's form. I'm receiving an error in my Post controller:
ActiveRecord::AssociationTypeMismatch in PostsController#create
Topic(#28978980) expected, got String(#16956760)
app/controllers/posts_controller.rb:27:in `new'
app/controllers/posts_controller.rb:27:in `create'
app/controllers/posts_controller.rb:27:
#post = Post.new(params[:post])
Here are my models:
topic.rb:
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
validates :name, :presence => true,
:length => { :maximum => 32 }
attr_accessible :name
end
post.rb:
class Post < ActiveRecord::Base
belongs_to :topic, :touch => true
has_many :comments, :dependent => :destroy
attr_accessible :name, :title, :content, :topic
accepts_nested_attributes_for :topics, :reject_if => lambda { |a| a[:name].blank? }
end
comment.rb:
class Comment < ActiveRecord::Base
attr_accessible :name, :comment
belongs_to :post, :touch => true
end
I have a form:
<%= simple_form_for #post do |f| %>
<h1>Create a Post</h1>
<%= f.input :name %>
<%= f.input :title %>
<%= f.input :content %>
<%= f.input :topic %>
<%= f.button :submit, "Post" %>
<% end %>
And it's controller action: (posts create)
def create
#post = Post.new(params[:post]) # line 27
respond_to do |format|
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
In all of the examples I find, tags belong to posts. What I'm looking for is different and probably easier. I want a post to belong to a single tag, a Topic. How can I create a Topic through the Post controller? Can someone point me in the right direction? Thank you very much for reading my question, I really appreciate it.
I'm using Rails 3.0.7 and Ruby 1.9.2. Oh and here's my schema just in case:
create_table "comments", :force => true do |t|
t.string "name"
t.text "content"
t.integer "post_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "posts", :force => true do |t|
t.string "name"
t.string "title"
t.text "content"
t.integer "topic_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "topics", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
Thanks again.
You should have:
accepts_nested_attributes_for :topic
on Post rather than the other way around.
#post = Post.new(params[:topic]) in my controller fixed the error.