How would I go about splitting this hash request into two different strings?
I want to split it at #<ActionDispatch, which marks the beginning of the next image selected in the request. How do I accomplish this in ruby?
Request
{"image"=>{"picture"=>[#<ActionDispatch::Http::UploadedFile:0x10c986d88 #tempfile=#<File:/var/folders/bx/6z1z5yks56j40v15n43tjh1c0000gn/T/RackMultipart20130404-53101-3c2whv-0>,
#headers="Content-Disposition: form-data; name=\"image[picture][]\"; filename=\"background-pic.jpg\"\r\nContent-Type: image/jpeg\r\n",
#content_type="image/jpeg",
#original_filename="background-pic.jpg">,
#<ActionDispatch::Http::UploadedFile:0x10c986d60 #tempfile=#<File:/var/folders/bx/6z1z5yks56j40v15n43tjh1c0000gn/T/RackMultipart20130404-53101-bvdysw-0>,
#headers="Content-Disposition: form-data; name=\"image[picture][]\"; filename=\"bible-banner.png\"\r\nContent-Type: image/png\r\n",
#content_type="image/png",
#original_filename="bible-banner.png">],
"album_id"=>"10"},
"authenticity_token"=>"dr8GMCZOQo4dQKgkM4On2uMs8iORQ68vokjW0e4VvLY=",
"commit"=>"Submit",
"utf8"=>"✓",
"album_id"=>"10"}
Controller
class Admin::ImagesController < ApplicationController
respond_to :html, :json
def index
#album = Album.find(params[:album_id])
#images = #album.images.all
end
def new
#album = Album.find(params[:album_id])
#image = #album.images.new
##image.picture.size.times {#image.build}
end
def create
#album = Album.find(params[:album_id])
#image = #album.images.new(params[:image])
if #image.save
flash[:notice] = "Successfully added image!"
redirect_to [:admin, #album, :images]
else
render 'new'
end
end
def show
#album = Album.find(params[:album_id])
#image = #album.images.find(params[:id])
end
def edit
#album = Album.find(params[:album_id])
#image = #album.images.find(params[:id])
end
def update
#album = Album.find(params[:album_id])
#image = #album.images.find(params[:id])
if #image.update_attributes(params[:image])
flash[:notice] = "Successfully updated Image"
redirect_to [:admin, #album, :images]
else
render "edit"
end
end
def destroy
#album = Album.find(params[:album_id])
#image = #album.images.find(params[:id])
#image.destroy
#albumid = #album.id
#id = #image.id
FileUtils.remove_dir("#{Rails.root}/public/uploads/image/picture/#{#albumid}/#{#id}", :force => true)
redirect_to admin_album_images_path(#album)
end
end
Model
class Image < ActiveRecord::Base
attr_accessible :title, :description, :picture, :image_id, :album_id, :albumcover, :image
belongs_to :album
accepts_nested_attributes_for :album
mount_uploader :picture, PictureUploader
end
Form View
<%= form_for([:admin, :album, #image], :html => {:multipart => true}) do |f| %>
<%= f.hidden_field :album_id, :value => #album.id %>
<%= f.file_field :picture, :multiple => true %>
<%= f.submit "Submit" %>
<%end%>
Uploader Carrierwave
class PictureUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
# include Sprockets::Helpers::RailsHelper
# include Sprockets::Helpers::IsolatedHelper
# Choose what kind of storage to use for this uploader:
storage :file
# storage :fog
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.album_id}/#{model.id}"
end
end
As a few commenters said above, what you have is a hash (key/value pair) with the key picture where the value is already an array, which by its nature is split up into discrete objects.
In order to access the array you need to ask for the picture key, then you can access each UploadedFile with Array methods.
So starting with your initial hash:
hash_with_stuff = { "picture" => [#<UploadedFile1>,#<UploadedFile2>]}
#=> { "picture" => [#<UploadedFile1>,#<UploadedFile2>]}
In order to get the array:
array_of_pictures = hash_with_stuff["picture"]
#=> [#<UploadedFile1>,#<UploadedFile2>]
Now that you have the array, you can operate on it as you would any array:
# to get single elements out
array_of_pictures[0] # where the number is the index of the object in the array (starting with 0)
#=> #<UploadedFile1> #this is an UploadedFile object and will respond to its own methods
# to operate on each element with some method
array_of_pictures.each do |picture|
picture.create_thumbnail
end
Does this make sense or were there other questions about working with this?
Lets take a look at this hash h={"strings"=>["stirng_object_1","string_object_2"]} which is almost similar to the hash that you posted except that the objects in the array are strings instead of ActionDispatch::Http::UploadedFile Objects.
So from my example hash that I cited if I have to get first string object I would do
h["string"][0]
Similarly if you would like to access the first ActionDispatch::Http::UploadedFile Objects you can do ( assuming that your hash name is h )
h["picture"][0]
I figured out how to split this hash request into its separate strings.
Image Controller
def new
#album = Album.find(params[:album_id])
#image = #album.images.new
end
def create
params[:image][:picture].each do |image|
#album = Album.find(params[:album_id])
#params = {}
#params['picture'] = image
#image = #album.images.create(#params)
end
if #image.save
if params[:image][:picture].size > 1
flash[:notice] = "Successfully added images!"
else
flash[:notice] = "Successfully added image!"
end
redirect_to [:admin, #album, :images]
else
render "new"
flash[:notice] = "Did not successfully add image :("
end
end
Related
I've been worrying this bug for too many days and I'm not sure what I'm missing.
I have a parent model named 'product' and a child of it 'product_attachment'.
Validation within child model is successful on create to disallow a blank image field via /product_attachments#new
However, when using its parent form /product#new (files below) I'm expecting it to validate to successfully fail without an image. However, Activerecord is ignoring the error, and my controller is tryign to save as a result if the validation passing then complaining that my product_attachments is null.
I'm currently in the understanding that using the 'validates_associated' would validate the child model as part of the Parent's process but this has not been working.
Instead of passing a happy failed validation mid-form to allow user to take action, we leave form and the controller tries to process the create method which fails due to no attachment.
Since I should always have an attachment have been trying to fix this validation to no avail.
Any help appreciated, I've include similar code samples before for your feedback.
I'm fairly new to rails so I'm hoping I'm mis-using a key syntax or context.
Also curious what cause is and what best way was to troubleshoot as I'm still developing good debug practices.
product.rb
class Product < ActiveRecord::Base
has_many :product_attachments
validates_presence_of :title, :message => "You must provide a name for this product."
accepts_nested_attributes_for :product_attachments, allow_destroy: true#,
validates_associated :product_attachments
end
product_attachment.rb (carrierwave to handle uploading used here, seems to work fine)
class ProductAttachment < ActiveRecord::Base
belongs_to :product
mount_uploader :image, ImageUploader
validates_presence_of :image, :message => "You must upload an image to go with this item."
end
products_controller.rb
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
def index
#products = Product.all
end
def show
#product_attachments = #product.product_attachments.all
end
def new
#product = Product.new
#product_attachment = #product.product_attachments.build
end
def edit
end
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
params[:product_attachments]['image'].each do |a|
#product_attachment = #product.product_attachments.create!(:image => a)
end
format.html { redirect_to #product, notice: 'Product was successfully created.' }
else #- when would this fire?
format.html { render :new }
end
end
end
def update
respond_to do |format|
if #product.update(product_params)
params[:product_attachments]['image'].each do |a|
#product_attachment = #product.product_attachments.create!(:image => a, :post_id => #post.id)
end
format.html { redirect_to #product, notice: 'Product was successfully updated.' }
else #- when would this fire?
format.html { render action: 'new' }
end
end
end
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to #product, notice: 'Product was successfully destroyed.' }
end
end
private
def set_product
#product = Product.find(params[:id])
end
# we pass the _destroy so the above model has the access to delete
def product_params
params.require(:product).permit(:id, :title, :price, :barcode, :description, product_attachment_attributes: [:id, :product_id, :image, :filename, :image_cache, :_destroy])
end
end
product_attachments_controller.rb
class ProductAttachmentsController < ApplicationController
before_action :set_product_attachment, only: [:show, :edit, :update, :destroy]
def index
#product_attachments = ProductAttachment.all
end
def show
end
def new
#product_attachment = ProductAttachment.new
end
def edit
end
def create
#product_attachment = ProductAttachment.new(product_attachment_params)
respond_to do |format|
if #product_attachment.save
#product_attachment.image = params[:image]
format.html { redirect_to #product_attachment, notice: 'Product attachment was successfully created.' }
else
format.html { render :new }
end
end
end
def update
respond_to do |format|
if #product_attachment.update(product_attachment_params)
#product_attachment.image = params[:image]
format.html { redirect_to #product_attachment.product, notice: 'Product attachment was successfully updated.' }
else
format.html { render :edit }
end
end
end
def destroy
#product_attachment.destroy
respond_to do |format|
format.html { redirect_to product_attachments_url, notice: 'Product attachment was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product_attachment
#product_attachment = ProductAttachment.find(params[:id])
end
def product_attachment_params
params.require(:product_attachment).permit(:id, :product_id, :image, :image_cache)
end
end
_form.html.slim (using simple_form + slim + cocoon here...)
= simple_form_for #product do |f|
- if #product.errors.any?
#error_explanation
h2
= pluralize(#product.errors.count, "error")
| prohibited this product from being saved:
ul
- #product.errors.each do |attribute, message|
- if message.is_a?(String)
li= message
= f.input :title
= f.input :price, required: true
= f.input :barcode
= f.input :description
h3 attach product images
#product_attachment
= f.simple_fields_for :product_attachments do |product_attachment|
= render 'product_attachment_fields', f: product_attachment
.links
= link_to_add_association 'add product attachment', f, :product_attachments
= f.submit
_product_attachment_fields.html.slim
Noted I needed to name my file field this way for my controller to use files correctly, but unsure why still.
.nested-fields
= f.file_field :image , :multiple => true , name: "product_attachments[image][]"
= link_to_remove_association "remove", f
Let me know if I can provide anything else.
Thank you for your time to read/reply.
Edit1: My current method to debug I'm working through as of writing this is to strip out code chunks and test functionality by through browser. I've read I should be more familiar with rails console but have not got there yet.
Edit2:
undefined method `[]' for nil:NilClass
params[:product_attachments]['image'].each do |a|
app/controllers/products_controller.rb:47:in `block in create'
app/controllers/products_controller.rb:44:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"{mytoken}",
"product"=>{"title"=>"pleasefailwell",
"commit"=>"Create Product"}
Edit3: Reworded original section for clarity
Seeing your latest edit, I figured that was happening, which means product_attachments is nil. Which is correct, the param should be product_attachments_attributes.
This leads to another problem in your product_params method.
You have product_attachment_attributes, the the base form "field" should be pluralised: product_attachments_attributes
In short:
Remove the product attachment loops from your controller. Fix the strong params method, it should work after that.
Edit:
Also remove the name attribute from the file_field That isn't helping.
I am relatively new to Rails and would appreciate any help.
My website accepts a signature image in base64 format and I am trying to use a Paperclip adaptor to decode the image and save it to my form model as the :signature attribute. I am using the advice given here (and here) which advises to use the following code:
In model:
class Thing
has_attached_file :image
In controller:
def create
image = Paperclip.io_adapters.for(params[:thumbnail_data])
image.original_filename = "something.gif"
Thing.create!(image: image)
...
end
My assumption is that Thing.create! is setting the value of Paperclip's model attribute :image to be the value of the image variable whilst creating and saving a new Thing object. I tried to implement the same code in my FormsController (create action) before #form.save, but am receiving this error:
undefined method `before_image_post_process' for #<Class:0x007f94a2a26de8>
My FormsController:
class FormsController < ApplicationController
before_action :logged_in_user
before_action :admin_user, only: :destroy
def index
#forms = Form.all #paginate
end
def show
#form = Form.find(params[:id])
end
def new
#form = Form.new
end
def create
#form = Form.new(form_params)
# Paperclip adaptor
signature = Paperclip.io_adapters.for(params[:base64])
signature.original_filename = "something.png"
# Attempt to submit image through Paperclip
#form.signature = signature
if #form.save
flash[:success] = "The form has been successfully created!"
redirect_to #form
else
render 'new'
end
end
def edit
#form = Form.find(params[:id])
end
def update
#form = Form.find(params[:id])
if #form.update_attributes(form_params)
flash[:success] = "Form has been updated!"
redirect_to #form
else
render 'edit'
end
end
def destroy
Form.find(params[:id]).destroy
flash[:success] = "Form deleted"
redirect_to forms_path
end
private
def form_params
params.require(:form).permit(:first_name, :last_name, :email, :phone, :address, :member_type, :base64)
end
end
This is my Form model:
class Form < ActiveRecord::Base
has_attached_file :signature
validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
end
Assuming that you're using the Rails form helpers over in your view, and based on your form_params list, the :base64 key won't be at the top level of your params hash, but rather one level down at params[:form][:base64]
In my application I have a "bookings" table, and an "extras" table.
This is a many-many relationship. Therefore I have created a middle table called "additions"
I've used the "has_many :through" to establish the relationship between the tables:
class Booking < ActiveRecord::Base
has_many :additions
has_many :extras, :through => :additions
class Extra < ActiveRecord::Base
has_many :additions
has_many :extras, :through => :additions
class Addition < ActiveRecord::Base
belongs_to :booking
belongs_to :extra
This seems to work. I added a few extras to some existing bookings manually (by adding numbers to the additions table), and wrote code so that when you click to show a booking, it lists all associated extras.
Now I need to make it so that when you make a booking - the "extras" are saved into the middle (additions) table.
I have checkboxes on my bookings form page:
<%= f.label 'Extras:' %>
<%= f.collection_check_boxes :extra_ids, Extra.all, :id, :extra_info %>
But obviously, the choices just get discarded when the user clicks on save.
I need some code to go (in the controller?) to make it save these "extras" into the "additions table" ?
Any ideas, as I can't work out how to do this?!
Thanks!
class BookingsController < ApplicationController
respond_to :html, :xml, :json
before_action :find_room
# before_action :find_extra
def index
#bookings = Booking.where("room_id = ? AND end_time >= ?", #room.id, Time.now).order(:start_time)
respond_with #bookings
end
def new
#booking = Booking.new(room_id: #room.id)
end
def create
#booking = Booking.new(params[:booking].permit(:room_id, :start_time, :length, :user_id))
#booking.room = #room
if #booking.save
redirect_to room_bookings_path(#room, method: :get)
else
render 'new'
end
end
def show
#booking = Booking.find(params[:id])
end
def destroy
#booking = Booking.find(params[:id]).destroy
if #booking.destroy
flash[:notice] = "Booking: #{#booking.start_time.strftime('%e %b %Y %H:%M%p')} to #{#booking.end_time.strftime('%e %b %Y %H:%M%p')} deleted"
redirect_to room_bookings_path(#room)
else
render 'index'
end
end
def edit
#booking = Booking.find(params[:id])
end
def update
#booking = Booking.find(params[:id])
# #booking.room = #room
if #booking.update(params[:booking].permit(:room_id, :start_time, :length, :user_id))
flash[:notice] = 'Your booking was updated succesfully'
if request.xhr?
render json: {status: :success}.to_json
else
redirect_to resource_bookings_path(#room)
end
else
render 'edit'
end
end
private
def save booking
if #booking.save
flash[:notice] = 'booking added'
redirect_to room_booking_path(#room, #booking)
else
render 'new'
end
end
def find_room
if params[:room_id]
#room = Room.find_by_id(params[:room_id])
end
end
# def find_extra
# if params[:extra_id]
# #extra = Extra.find_by_id(params[:extra_id])
# end
# end
# If resource not found redirect to root and flash error.
def resource_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :notice => "Booking not found."
end
def booking_params
params.require(:booking).permit(:user_id, :extra_id)
end
end
------------------------
class AdditionsController < ApplicationController
before_action :set_addition, only: [:show, :edit, :update, :destroy]
# GET /additions
def index
#additions = Addition.all
end
# GET /additions/1
def show
end
# GET /additions/new
def new
#addition = Addition.new
end
# GET /additions/1/edit
def edit
end
# POST /additions
def create
#addition = Addition.new(addition_params)
if #addition.save
redirect_to #addition, notice: 'Addition was successfully created.'
else
render :new
end
end
# PATCH/PUT /additions/1
def update
if #addition.update(addition_params)
redirect_to #addition, notice: 'Addition was successfully updated.'
else
render :edit
end
end
# DELETE /additions/1
def destroy
#addition.destroy
redirect_to additions_url, notice: 'Addition was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_addition
#addition = Addition.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def addition_params
params.require(:addition).permit(:booking_id, :extra_id, :extra_name)
end
end
--------------------------------------
# #author Stacey Rees <https://github.com/staceysmells>
class ExtrasController < ApplicationController
# #see def resource_not_found
around_filter :resource_not_found
before_action :set_extra, only: [:show, :edit, :update, :destroy]
def index
#extras = Extra.all
end
def show
end
def new
#extra = Extra.new
end
def edit
end
def create
#extra = Extra.new(extra_params)
if #extra.save
redirect_to #extra, notice: 'Extra was successfully created.'
else
render :new
end
end
def update
if #extra.update(extra_params)
redirect_to #extra, notice: 'Extra was successfully updated.'
else
render :edit
end
end
def destroy
#extra.destroy
redirect_to extras_url, notice: 'Extra was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_extra
#extra = Extra.find(params[:id])
end
# If resource not found redirect to root and flash error.
def resource_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :notice => "Room Category not found."
end
# Only allow a trusted parameter "white list" through.
def extra_params
params.require(:extra).permit(:extraimg, :name, :description, :quantity, :price, :extracat_id)
end
end
What you're doing here is working with nested form attributes. It's a bit complex, but it's also something people do often, so there are some good resources available.
I suggest you look at this post: http://www.sitepoint.com/complex-rails-forms-with-nested-attributes/
In particular, the section named 'More Complicated Relationships' specifically has an example of using nested attributes to set up a many-to-many association using has_many :through.
The key pieces (which commenters have already pointed out) are going to be accepts_nested_attributes_for :extras in your Booking model, and a f.fields_for :extras block in the view. You'll also need to modify your booking_params method to permit the nested values. There are a couple of strong parameters gotchas that you can potentially run into with that, so you may need to review the documentation.
It turns out I was nearly there with the code I had once the accepts_nested_attributes_for was written in.
My main issue was setting up the booking_params method in the controller. I got it to work by declaring :extra_ids => [] in my params.permit.
i have this controller
class StoresController < ApplicationController
before_filter :authenticate_business!, :except => [:index, :show]
def index
##stores = Store.paginate(:page => params[:page])#, :per_page => 8)
if params[:query].present?
#stores = Store.search(params[:query], page: params[:page])
else
#stores = Store.all.page params[:page]
end
end
def show
#store = Store.friendly.find(params[:id])
if request.path != store_path(#store)
redirect_to #store, status: :moved_permanently
end
end
def new
#store = Store.new
end
def create
#store = Store.new(store_params)
#store.business_id = current_business.id
if #store.save
redirect_to #store
else
render 'new'
end
end
def edit
#store = Store.friendly.find(params[:id])
end
def update
#store = Store.friendly.find(params[:id])
if #store.update(store_params)
redirect_to #store
else
render 'edit'
end
end
def destroy
#store = Store.friendly.find(params[:id])
#store.destroy
redirect_to stores_url
end
private
def store_params
params.require(:store).permit(:name, :description, :address, :telephone, :email, :website)
end
end
and a view with a form to create a new store.
<%= form_for #store do |f| %>
.......
code
......
<% end %>
The problem is that when i submit the form, it gives me this error "param is missing or the value is empty: store", pointing at line "params.require(:store).permit(:name, :description, :address, :telephone, :email, :website)"
Any idea to solve this problem?
Thank you.
I had this same issue and it was caused by a route issue, as discussed in the comments, causing the form not to post any data.
I think what you need is to make sure 'get' requests to the 'new' route access your 'new' method, while 'post' requests to the 'new' route access your 'create' method. Something like:
get 'stores/new' => 'stores#new'
post 'stores/new' => 'stores#create'
Have a issue with a controller file which i have narrowed down to a method
Controller
def create
#gallery = Gallery.new(params[:gallery])
if #gallery.save
flash[:notice] = "Successfully created gallery."
redirect_to #gallery
else
render :action => 'new'
end
end
private
def gallery_params
params.require(:gallery).permit(:name, :gallery, :gamepic)
end
end
the problem is that there is no attr_accessible in the controller or the model
model
class Gallery < ActiveRecord::Base
has_many :gamepics
private
def gallery_params
params.require(:gallery).permit(:name, :gallery, :gamepic)
end
end
Try passing the strong parameters method into Gallery.new instead of params[:gallery].
My understanding of this is that, the hash returned from that method is what should be used anyways. So you'd have:
#gallery = Gallery.new(gallery_params)
If you only need certain params from your :permit call, just make a new strong params method and use that one.