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
Related
I'm trying to build a very basic forum-like app, where Users can create Topics and reply to existing Topics.
The Topic creation works fine and I'm able to display Reply form, however, the Reply create action is not working properly. I don't have any errors, it just redirects_to topics_path.
This is following a tutorial, so the code is not mine. Is anyone able to spot the obvious cause for this? Any help much appreciated!
replies_controller.rb
def create
#topic = Topic.find(params[:topic_id])
#reply = #topic.replies.create(params[:reply].permit(:reply))
#reply.user_id = current_user.id if current_user
#reply.save
if #reply.save
redirect_to topic_path(#topic)
else
flash[:notice] = "Error."
redirect_to topics_path
end
end
reply.rb
class Reply < ApplicationRecord
belongs_to :post
belongs_to :user
end
replies/_form.html.erb
<%= form_for [#topic, #topic.replies.create] do |f| %>
<%= f.label :reply %>
<%= f.text_area :reply, class: "textarea", rows: "10" %>
<%= f.submit class: "button is-primary" %>
<% end %>
topic.rb
class Topic < ApplicationRecord
belongs_to :user
has_many :replies
end
schema.rb
create_table "topics", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "replies", force: :cascade do |t|
t.text "reply"
t.bigint "topic_id"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["topic_id"], name: "index_replies_on_topic_id"
t.index ["user_id"], name: "index_replies_on_user_id"
end
In file replies/_form.html.erb
you should use build method build instead of create. Replace line:
<%= form_for [#topic, #topic.replies.create] do |f| %>
to
<%= form_for [#topic, #topic.replies.build] do |f| %>
There are some another problems with the code:
#reply = #topic.replies.create(params[:reply].permit(:reply))
In this line you call new + save, without user, which is required.
Change this to:
#reply = #topic.replies.new(params[:reply].permit(:reply))
Then, you call save twice:
#reply.save
if #reply.save
...
First line is unnecessary.
And finally, what is the cause of rollback, in your Reply model you have:
belongs_to :post
But in the schema.rb and in the params you have topic:
Schema.rb:
t.bigint "topic_id"
Params:
"reply"=>{"reply"=>"Test reply"}, "commit"=>"Create Reply", "topic_id"=>"4"}
I have a form with a select_tag and options_from_collection_for_select that I can't seem to get to pass. In the view, when the upload id is set to uploadzip_id I get a 302 redirect and when it's set to uploadzip_ids, I get a Unknown Attribute error.
I'm a bit confused as I have my relationship set up along with the foreign key. I do have another model with checkboxes called Uploadpdf that works fine.
Here is the set up..
class Campaign < ActiveRecord::Base
has_one :uploadzip
end
class Uploadzip < ActiveRecord::Base
belongs_to :campaign
end
db/schema.rb
create_table "campaigns", force: :cascade do |t|
t.string "name"
t.text "comment"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
create_table "uploadzips", force: :cascade do |t|
t.string "file_name"
t.string "file_type"
t.datetime "date"
t.integer "size"
t.integer "pages"
t.string "file_ident"
t.string "md5"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "campaign_id"
end
add_foreign_key "uploadzips", "campaigns"
app/controllers/campaign_controller.rb
class CampaignsController < ApplicationController
def index
#campaigns = Campaign.all.order("created_at DESC")
end
def new
#campaign = Campaign.new
end
def create
#campaign = Campaign.new(campaign_params)
if #campaign.save
flash[:success] = "Campaign Successfully Launched!"
redirect_to #campaign
else
flash[:error] = "There was a problem launching your Campaign."
redirect_to new_campaign_path
end
end
.....
private
def campaign_params
params.require(:campaign).permit(:name, :comment, :uploadzip_ids, uploadpdf_ids: [])
end
end
views/campaigns/_form.rb
<%= form_for #campaign, url: {action: "create"} do |f| %>
.....some typical fields..
<%= f.label :data_file, class: "right-label" %>
<%= select_tag campaign[uploadzip_ids],
options_from_collection_for_select(
Uploadzip.all, :id, :file_name
), { include_blank: "Include a Zip File" } %>
.....some more typical fields
<% end %>
Update
I have changed the code to better reflect the foreign key as suggested. Creating a campaign is now successful but it's not associating with the chosen uploadzip Zip file selected. When calling #campaign.uploadzip, it returns nil.
Here is the updated code:
<%= select_tag "uploadzip[campaign_id]",
options_from_collection_for_select(
Uploadzip.all, :id, :file_name
), { include_blank: "Include a Zip File" } %>
I also changed the controller params.require to..
def campaign_params
params.require(:campaign).permit(:name, :comment, :campaign_id, uploadpdf_ids: [])
end
As per your association set-up,the foreign_key should be campaign_id not uploadzip_id. You should either change your associations or foreign_key according to your use-case.
And also I recommend you to follow these Guides to know more about associations.
The 302 redirect may not be a bad thing, since you're doing a redirect_to new_campaign_path. Are the records created correctly when you use uploadzip_id in both the view and controller params.permit section?
Member of a FaceBook group helped me figure it out by adding a little
extra logic in the controller..
if #campaign.save
zip = Uploadzip.find(params[:uploadzip_id])
zip.campaign = #campaign
zip.save
flash[:success] = "Campaign Successfully Launched!"
redirect_to #campaign
else
flash[:error] = "There was a problem launching your Campaign."
redirect_to new_campaign_path
end
..which was met with changing the select_tag's name.
<%= select_tag :uploadzip_id,
options_from_collection_for_select(
Uploadzip.all, :id, :file_name
), { include_blank: "Include a Zip File" } %>
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.
I have three models: User, Post and, Reply. An user has many posts and comments. A post had many replies and belongs to an user, and a reply belongs to a post and an user.
routes.rb:
resources :posts do
resources :replies
end
schema.rb:
create_table "posts", :force => true do |t|
t.text "content", :limit => 255
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "title"
end
create_table "replies", :force => true do |t|
t.text "content"
t.integer "post_id"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
This is how I'm creating comments:
comments_controller.rb:
def create
#post = Post.find(params[:post_id])
#reply = #post.replies.build(params[:reply])
if #reply.save!
flash[:success] = "reply created!"
redirect_to post_path(#post)
else
redirect_to post_path(#post)
end
end
replies/_form.html.erb:
<%= form_for([#post, #post.replies.build]) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Enter reply content" %>
</div>
<%= f.submit "Reply", class: "btn btn-large btn-primary" %>
<% end %>
After submitting the form I get this error:
Validation failed: User can't be blank
I assume its because the reply's attribute user_id is empty:
reply.rb
validates :user_id, presence: true
I'm not sure how to fill that attribute. I can't just put it in Reply attr_accesible, because that would compromise the security of the app (as far as I know).
Any suggestions to solve this?
attr_acessible only affects things when you're updating/creating a record from a hash of attributes. You can always set an attribute by calling the accessor directly, so after you've built the reply,
#reply.user = current_user
Should do the trick (assuming you're using something like devise or authlogic that defined current_user for you. You could also assign to #reply.user_id directly.
When I submit the form, the data is not being sent in the post and set via parameters. I can't for the life of me figure out why. This form is in the plan/show action, so that's why you see me setting the #action variable there. It is sent via JS.
routes.rb
resources :plans do
resources :actions
end
action.rb
belongs_to :plan
plan.rb
has_many :actions
plans_controller.rb
def show
#plan = current_user.plans.includes(:actions).find(params[:id])
#action = Action.new
respond_to do |format|
format.html # show.html.erb
format.json { render json: #plan }
end
end
actions_controller.rb
before_filter :get_plan
def create
#action = #plan.actions.new(params[:action])
#action.user_id = current_user.id
#action.save
end
private
def get_plan
#plan = current_user.plans.find(params[:plan_id])
end
create.js.erb in views/actions folder
$('div#actions').prepend("<%= escape_javascript(render #action) %>");
$('div#<%= dom_id(#action) %>').effect('highlight');
_form.html.erb partial
<%= form_for ([#plan, #action]), remote: true do |f| %>
<%= f.text_field :desc %>
<%= f.number_field :days %>
<%= f.submit %>
<% end %>
parameters sent via POST (missing action hash - why??)
Started POST "/plans/1/actions"
Parameters: {"utf8"=>"✓", "authenticity_token"=>"**removed**", "commit"=>"Create Action", "plan_id"=>"1"}
DB Schema
create_table "plans", :force => true do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "actions", :force => true do |t|
t.string "desc"
t.integer "plan_id"
t.integer "days"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
Action is a reserved word. If you call it anything else (except controller, which is also reserved) then it will work.
http://guides.rubyonrails.org/action_controller_overview.html#routing-parameters
The params hash will always contain the :controller and :action keys, but you should use the methods controller_name and action_name instead to access these values.