Ruby on Rails multiple images connected to one object - ruby-on-rails

I've been trying to create a form that would get parameters for multiple models. I have a photo model that belongs to a product model and I want to make it so that when you create a new product you can also upload images that are linked to that product by id.
<%= form_for #product, html:{multipart:true} do |f| %>
<div class="field">
<%= f.label :price %>
<%= f.text_field :price %>
</div>
<%=form_for #photo do |t| %>
<%t.productID = f.id%>
<div class="field">
<%= t.label (:image) %>
<%= t.file_field (:image) %>
</div>
<%end%>
<div class="actions">
<%= f.submit %>
</div>
<%end%>
right now I'm using paperclip for image attachments and the photo model accepts the images as parameters. I've used paperclip before but the product could only have one image connected to it. If I use the form above I get "First argument in form cannot contain nil or be empty" error and it points to where the form_for #photo starts.I have controllers for both with the usual methods of new, create, update, etc. I've routed resources to both product and photos but I'm still pretty new to rails and don't fully understand how this stuff works.

I think what you're trying to do is a good application for nested forms using the fields_for helper.
First, you'll need to ensure that your product model and photo model have the right associations (A product probably has_many photos, and a photo belongs to a product, right?). Then you'll make sure the product class 'accepts nested attributes for photo's which allows you to add attributes to the photos model from a products form.
in products.rb
class Product
has_many :photos
accepts_nested_attributes_for :photos
end
and in photo.rb
class Photo
belongs_to :product
end
Then you'll want to make sure any attributes you need for the photo are white-listed in your product params.
in products_controller.rb
private
def product_params
params.require(product).permit(:first_product_attribute, :second_produtc_attribute, photo_attributes: [:image])
end
Last, you'll create the form using the special helper fields_for
in your view
<%= form_for #product, html:{multipart:true} do |f| %>
<div class="field">
<%= f.label :price %>
<%= f.text_field :price %>
</div>
<%= f.fields_for :photo do |t| %>
<div>
<%= t.label :image %>
<%= t.file_field :image, :multiple => true %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<%end%>
You'll also need to make sure you're actually creating new photo objects in your product's create action:
in products_controller.rb
def create
#product = Product.new(product_params)
if #product.save!
params[:photo]['image'].each do |img|
#photo = #product.photos.create!(:image => img)
end
flash[:success] = 'product saved!'
redirect_to #product
end
end
Some of this is based on my experience doing the same thing but with Carrierwave instead of Paperclip so your specific implementation might be a little different.

I dont think this is a proper method <%t.productID = f.id%>. Maybe try <% t.text_field :productID, value = f.id, type = hidden %> or something along those lines?
heres some docs for the form helper so you know what to put after t.abcd
http://apidock.com/rails/v3.2.3/ActionView/Helpers/FormHelper/form_for

You're getting the
"First argument in form cannot contain nil or be empty"
..error because #photo is nil, you need to set it in your controller #photo = Photo.new.
Also, form tags inside form tags are invalid HTML.
https://www.w3.org/TR/html5/forms.html#the-form-element
Forms
Content model: Flow content, but with no form element
descendants.
You want to use f.fields_for instead. Learn how to use it here here
I have controllers for both with the usual methods of new, create,
update, etc.
You only ever hit one controller and action when you go to a path, say /photos will only hit the photos controller (as configured in your routes.rb). This I think is where you're messing up the #photo variable. Set both in the same controller in order for the view to be able to see both variables.

Related

Creating a Model in Rails within the controller of a different Model

I'm trying to implement a quote saving feature in a Rails app. I have a User Model that has_many :quotes and a Quote Model that belongs_to :user. Now I have a separate Book Model that would be the source of these quotes.
Within the Book's show.html.erb file, I have a form to save quotes for the current user
<%= form_for (#new_quote) do |f| %>
<div class="field">
<%= f.hidden_field :book_id, :value => #new_comment.book_id %>
<%= f.text_field :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And in the Book controller, I have
def show
#new_quote = current_user.quotes.create(book_id: params[:id])
end
The quote saves fine but the problem is, since I have this Quote creation statement in the show method, everytime I go to the show.html.erb page of my Book model, it creates a Quote with an empty body.
What can I do to solve this? I was thinking it probably would involve moving this Quote creation to the actual create method of the Quote controller but I don't know how to exactly pass the parameters through.
You could just build that quote, but not save it to the database. Then the user need to send the form to save that record. Just change your show method to:
def show
#new_quote = current_user.quotes.build(book_id: params[:id])
end

Nested fields not being added on form submit

I am using the cocoon gem to try and achieve adding an object which belongs to another with nested fields. I have a 'user_resolution' which has many 'milestones'. I have set up the associations accordingly in both of these models. For some reason, milestones are failing to be created, however if I add one manually in the database I can successfully update it. I am able to dynamically add the fields and remove them using the cocoon gem but that is all. When I click 'add milestone' it redirects me to the show view of the user resolution and throws the success message saying user resolution has been updated, no errors are thrown but the milestone(s) is/are not created.
user_resolution.rb
has_many :milestones
accepts_nested_attributes_for :milestones, reject_if: :all_blank, allow_destroy: true
milestone.rb
belongs_to :user_resolution
I have set up the nested form within the edit view as for now I only want users to add a milestone to a resolution in the edit view.
user_resolutions/edit.html.erb
<%= form_for(#user_resolution) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<%= f.fields_for :milestones do |milestone| %>
<%= render 'milestone_fields', f: milestone %>
<% end %>
<%= link_to_add_association 'Add Milestone', f, :milestones %>
<%= f.submit "Add Milestone" %>
<% end %>
_milestone_fields.html.erb
<div class="nested-fields">
<div class="field-row">
<%= f.label :name, 'Name' %>
<%= f.text_field :name %>
</div>
<div class="field-row">
<%= f.label :description, 'Name' %>
<%= f.text_area :description %>
</div>
<div class="field-row">
<%= f.label :severity, 'severity' %>
<%= f.check_box :severity %>
</div>
<div class="field-row">
<%= f.label :target_date, 'target_date' %>
<%= f.date_select :target_date %>
</div>
<%= link_to_remove_association 'Remove', f %>
</div>
The permitted parameters within the user resolutions controller also contain the following
milestones_attributes: [:id, :user_resolution_id, :name, :description, :target_date, :severity, :complete, :_destroy]
The milestones themselves have no views, they only have a model and a controller. The controller create action (which i'm unsure is required for nested forms) contains the standard following code
def create
#milestone = Milestone.new(milestone_params)
if #milestone.save
redirect_to user_resolutions_path,
:flash => { :success => "You successfully created a milestone" }
else
redirect_to new_milestone_path,
:flash => { :error => "Oops something went wrong. Try again." }
end
end
I've been as informative as I can but if you need anything else let me know. Thanks guys.
which i'm unsure is required for nested forms
You don't need a create action for milestones - they'll be populated from the user_resolutions#create controller action.
There are several things to look at with this. I'll detail some here. This won't be a specific answer, but may help point you in the right direction.
Firstly, you need to make sure you're receiving the correct params.
Cocoon does a great job building the nested form - you need to make sure it's obliging Rails' nested attribute structure.
To do this, you should right-click > view source.
In the f.fields_for section (it won't be called that in the HTML), you'll be looking for the equivalent to the following:
<input type="text" name="milestones_attributes[0][name]" value="">
The important thing to note is the name...
Each time you use a form, or any Rails view helper for that matter, you're really just building standard HTML. form_for just creates an HTML form, and thus any params contained within it need to adhere to a certain structure for Rails to recognize the params.
The f.fields_for elements will typically be called x_attributes[:id][:param] - this is passed to Rails, which cycles through each [:id] to determine the number of nested params to add.
You need to check the source for the above naming structure. If you see it, that's good. If not, it means you haven't built your form properly.
Secondly, you need to make sure your objects are being built in the controller.
I'm not sure how Cocoon does this, but essentially, each time you use f.fields_for, you have to build the associated object before:
def new
#user_reservation = UserReservation.new
#user_reservation.milestones.build #-> this is what makes f.fields_for work
end
If the first step shows incorrect element naming, it means your associative objects are not being built (which is why they're not being recognized).
To test it, you should build the associative objects in the new method, before sending.
Finally, you'll want to post your params.
These tell you in explicit detail what Rails is doing with the nested attributes, allowing you to determine what's happening with them.
Sorry for the long-winded answer. You'll not have received any answers anyway, so I felt it prudent to give you something.

How do I submit a collection_check_box through the controller with a has_many through relationship?

Im on rails 4. I have three models, Blends, Addons and AddonPairings. Users create Blends and
Blend
have_many :addon_pairings
have_many :addons, through: :addon_pairings
Addon
have_many :addon_pairings
have_many :blends, through: :addon_pairings
AddonPairing
belong_to :blend
belong_to :addon
My addons are all premade in the db for users to choose as many as they want to attach to a blend.
In my new.html.erb for my blends
<%= form_for [current_user, #blend] do |f| %>
<div class="form-group">
<%= f.label :addons, "Add some addons to your blend" %>
<%= f.collection_check_boxes :addon_ids, Addon.all, :id, :name %>
</div>
<div class="form-group">
<%= f.submit class: "btn btn-lg btn-primary" %>
</div>
<% end %>
My Blend Controller
def create
#user = User.find(params[:user_id])
#blend = #user.blends.build(blend_params)
if #blend.save
redirect_to #user, notice: "Your blend has been created."
else
flash.now[:notice] = "Something went wrong. Please check the errors below."
render 'new'
end
end
private
def blend_params
params.require(:blend).permit(:name, :addon_ids)
end
How do I make my controller create the records in my addon_pairings table that connect the blend to the addons chosen? Thanks.
You have implemented it somekind badly.
Youll need to use 'accepts_nested_parameters' to do so.
This way, youll use fields_for tag to create a 'form in the form' that actually creates the fields in the other model making this entry owned by the current object that triggered the controller and generated the main form. So, since your collection_check_box create objects owned by the current one, but in another model, it needs to be inside a block like (just an example):
<%= fields_for #owner.belonged_name %>
<%= collection_check_box %>
<% end %>
I recommend you railscasts.com/episodes/196-nested-model-form-part-1 and this link (http://www.createdbypete.com/articles/working-with-nested-forms-and-a-many-to-many-association-in-rails-4/) to understand the philosphy about nested parameters.
Note that youll have to permit the attributes on your controller too since attr_params are deprecated.
Hope it helps.
in your blend_params method just change
params.require(:blend).permit(:name, :addon_ids)
to
params.require(:blend).permit(:name, addon_ids: [])

Rails 3 - Create View to Insert Multiple Records

I have what seems like a simple query. I need to create a view that will accept multiple records based on a single model. In my case the model is Project, which has 1 foreign key (person) and 2 fields time, role. I need to create a view (form) to insert 5 roles.
<%= form_for(#project) do |f| %>
<% 5.times do |index|%>
<div class="field">
<%= f.label :position %><br />
<%= f.text_field "fields[#{index}][stime]" %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I get an error message: undefined method `fields[0][stime]'
I do not think the railscasts for nested models is what I need.
How would I go about creating this?
EDIT: The Project model code is below:
class Project < ActiveRecord::Base
belongs_to :person
attr_accessible :role, :stime
end
The Projects_Controller code for the new method is below:
def new
#project = Project.new
end
I see you're planning to make some 1-to-many relationship (Product has_many :roles).
Here's some advices.
First, take a look at the accepts_nested_attributes_for method. You need to add it to your model to be able to perform mass-create.
Second, fields_for is what you need to design nested forms.
I'll give you some example of mass-creating for a simple Product has_many :line_items case:
<%= form_for #product do |f| %>
<%= f.fields_for :line_items, [LineItem.new]*5 do |li_fields| %>
<%= li_fields.text_field :quantity %>
<%= li_fields.text_field :price %>
<br>
<% end %>
<%= f.submit "Create line items" %>
<% end %>
All you need is to write in you controller something like:
#product.update_attributes params[:product]
and 5 line_items will be created at once.
Don't forget to white-list association_attributes (see params in your logs to see it). But I think if you get the mass-assignment error you'll do it anyway :)
I hope it helps.

The better way to pass the foreign_key value to the Rails controller

It's been almost a week since I've began to dig deeper in forms , associations , hashes , symbols... But it seems I cannot solve the puzzle without your help .
I am working on a project for displaying different galleries content . The basic idea is when the user sees the names of galleries (names are links ) to be able to click on chosen one. Then all the images ,that belong to this gallery , are displayed . On the bottom there should be a link "Add image in this gallery" .
My models :
class Gallery < ActiveRecord::Base
attr_accessible :name
has_many :pictures
end
class Picture < ActiveRecord::Base
attr_accessible :image
belongs_to :gallery
end
I have created index on gallery_id for the 'pictures' table .
My big problem appears here , how to pass the gallery_id to the controller's action 'new' . As I've seen in "Agile web development with Rails" it could be :
<%= link_to 'Add a picture here...',new_picture_path(:gallery_id=>#gallery.id) %>
As it seems in this case the foreign_key :gallery_id is exposed in the URL bar of the browser . The second problem is that :gallery_id is available for the controller 'new' function , but "disappears" for the 'create' function (causing an error " Couldn't find Gallery without an ID ") .
The problem is gone when I add a hidden field in the _form for pictures , in my case :
<%= form_for(#picture) do |f| %>
<div class="field">
<%= f.hidden_field :gallery_id , :value=>params[:gallery_id] %>
<%= f.label :image %><br />
<%= f.file_field :image %>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
Here are my definitions in the 'pictures' controller :
def new
#gallery=Gallery.find(params[:gallery_id])
#picture=#gallery.pictures.build
end
def create
#gallery = Gallery.find(params[:gallery_id])
#picture = #gallery.pictures.new(params[:picture])
if #picture.save
redirect_to(#picture, :notice => 'Picture was successfully created.')
else
redirect_to(galleries ,:notice => 'Picture was NOT created.')
end
end
And finaly the link_to definition in show.html.erb for galleries:
<% for picture in selpics(#gallery) %>
<div id= "thumb" >
<%= image_tag picture.image %>
</div>
<% end %>
<%= link_to 'Add a picture here...',new_picture_path(:gallery_id=>#gallery.id) %>
Here is the debug output before submitting the image :
--- !map:ActiveSupport::HashWithIndifferentAccess
gallery_id: "6"
action: new
controller: pictures
and after submitting the 'create' button (with exception raised ) :
{"utf8"=>"✓",
"authenticity_token"=>"IGI4MfDgbavBShO7R2PXIiK8fGjkgHDPbI117tcfxmc=",
"picture"=>{"image"=>"wilsonblx.png"},
"commit"=>"Create"}
As you see , there is nothing like "gallery_id" in the "pictures" hash .
Summarizing my questions to you :
Is there a way to pass the foreign_key without hidden_field ?
Could I hide somehow passing the foreign key form showing in the URL bar ?
Is there an alternative on passing arguments using 'link_to' ?
Thank you .
You may want to consider reading the Rails Guide on nested resources:
http://guides.rubyonrails.org/routing.html#nested-resources
In a nutshell:
routes.rb
resources :galleries do
resources :pictures do
end
# Generates the routes: /galleries/:gallery_id/pictures
pictures_controller.rb
def new
#gallery = Gallery.find(params[:gallery_id])
#picture = Picture.new
end
def create
#gallery = Gallery.find(params[:gallery_id]) # gallery_id is passed in the URL
#picture = #gallery.build(params[:picture])
if #picture.save
# success
else
# fail
end
end
pictures/new.html.erb
<%= form_for [#gallery, #picture] do |f| %>
<div class="field">
<%= f.hidden_field :gallery_id , :value=>params[:gallery_id] %>
<%= f.label :image %><br />
<%= f.file_field :image %>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
Ok, so the gallery_id is still passed through the URL, but I don't really see anything wrong with that. You have to pass it somewhere, right? You really only have 3 sane choices on where to pass it: a hidden field, as a querystring parameter, or tucked away inside the URL (nested resource). Of the 3, the latter is IMHO the cleanest method.
If you want to make things even easier on yourself, I highly recommend looking into Jose Valim's Inherited Resources gem that takes care of a lot of this boilerplate nastiness for you:
https://github.com/josevalim/inherited_resources
You need not use the numeric ID's in your RESTful routes. Look at permalink_fu, and use the :permalink field rather than the :id to refer to each gallery resource.
/galleries/louvre
/galleries/moma/382
And
... new_picture_path(:gallery_id => #gallery.permalink)
The key here is using a symbolic, unique key that's not the ID, permalink's are pretty good for that.
You can choose to pass the permalink in as :id and update your controller actions to expect that.

Resources