Creating a Model in Rails within the controller of a different Model - ruby-on-rails

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

Related

Ruby on Rails multiple images connected to one object

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.

Ruby on Rails: assign relationship on creation

I'm new to Ruby on Rails. There are two models in my project: room and guest. The association is "room has_many guests" and "guest belongs to room".
I have separated views for manage rooms and guests. Rooms don't require "guests" value on creation. However, I want to create new guests and assign it to certain room at the same time. What will be the proper way to do it? How do I transfer the input from web and match the entities in database.
The code is pretty much the same as "Getting Started with Rails". In the tutorial, they add "comments" in the "article" view and use "comment" as a sub-resource of "article". In my case, I treat the two models equally and want to manage them in separated views.
Update:
I used the collection_select and try to work with my guest_controller.
<%= form_for :guest, url: guests_path do |f| %>
<% if #guest.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#guest.errors.count, "error") %> prohibited this guest from being added:
</h2>
<ul>
<% #guest.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :phone %><br>
<%= f.text_field :phone %>
</p>
<p>
<%= f.label :room%><br>
<%= f.text_field :room %>
</p>
<p>
<%= f.label :room %><br>
<%= f.collection_select(:room_id, Room.all, :id, :title) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', guests_path %>
In my guest_controller, the create method called by the form above is :
def create
#guest = Guest.new(guest_params)
#guest.room = Room.find(params[:room_id])
if #guest.save
redirect_to #guest
else
render 'new'
end
end
However, when I create a new guest, it shows that:
ActiveRecord::RecordNotFound in GuestsController#create
Couldn't find Room with 'id'=
I checked that room_id=4 and Room.find(4) return the proper room.
What's wrong?
If you want to select one room from those that exist, use collection_select form helper, here is a relevant snippet from the docs:
f.collection_select(:city_id, City.all, :id, :name)
This outputs a dropdown list that:
fills in city_id parameter in this context
uses City.all for filling in the options in the list (I will be referring to "each" city as city)
uses city.id as data (that gets sent in the form)
shows city.name for each city in the dropdown list (hopefully, human-readable)
Bear in mind though, that in terms of security it's like "look, you can select this, and this and this!", that does not prevent users from selecting an unlisted option: either by modifying form markup by hand or sending handcrafted queries.
So should you ever be limiting access to specific rooms, and list only Room.unlocked (unlocked assumed a scope), make sure the received room_id refers to a room from that scope as well. Most of these problems are dealt with using either validations or careful association management (Room.unlocked.find_by_id(:room_id) that outputs nil if the room is not in that scope).
UPD: as for the latest problem you're having -- your understanding on how the form contents look in params seems to be wrong. It's quite a common misconception actually.
form_for :guest will construct a separate object/hash in params[:guest], with all the form's fields inside it. So it actually is inside params[:guest][:room_id], but no, don't rush with adding the missing part.
You've already built a #guest object from entire params[:guest], so if the room actually exists, it's inside #guest.room already and can be validated inside the model during save. Have a look at Rails validators.
Take a look at the fields_for tag:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for
It allows just that, to create a guest while creating a room and associating each other.

In ruby on rails 4, how do I set an attribute in the forms_for helper that is not associated with a model?

I want to be able to access :to_whom text value via params[:to_whom] in the controller. To_whom does not exist in a model.
I get the sensible error:
'undefined method `to_whom' for Conversation'
How can I add an arbitrary attribute to pass back to the controller in rails?
Also, in my view I did Message.new and Conversation.new which is incredibly ugly. I initially set #conversation = Conversation.new in the controller, however I found I had to recreate those variables in the second controller method anyways, which makes sense (after I hit the submit button). Thus instead of setting #message, #conversation in the new method, I removed all the lines from new and did the .new syntax in the view. Is there a more elegant way of writing this code so it isn't so hacky feeling?
CONTROLLER:
class ConversationsController < ApplicationController
attr_accessor :conversation, :user, :to_whom
# this is the method that generates the below view
def new
end
def create
...
end
end
VIEW:
<%= form_for([current_user, Conversation.new]) do |c| %>
<%= c.label :to_whom %>
<%= c.text_field :to_whom %>
<%= c.label :subject %>
<%= c.text_field :subject %>
<%= form_for(Message.new) do |m| %>
<%= m.label :message %>
<%= m.text_field :text %>
<div class="actions">
<%= submit_tag "send" %>
</div>
<% end %>
<% end %>
Virtual Attributes
Your attr_accessor belongs in your model (not your controller). Currently, you have it stored in your controller, which will do nothing at the model-level:
#app/models/conversation.rb
Class Conversation < ActiveRecord::Base
attr_accessor :conversation, :user, :to_whom
end
#app/controllers/conversations_controller.rb
# remove attr_accessor
You have to remember that since Ruby is object-orientated, all the data objects you get are from the model. This means if you call #conversation = Conversation.new, the attributes of that model are actually created in the conversation model
Normally, the model will set the attributes in accordance with your database columns. If you don't have a database column present, you need to create the relevant getter / setter methods using the attr_accessor module
m.text_field :to_whom is just a helper to build an html input tag. You could write your own in raw html, filling in the blanks with erb tags, or you could use other helpers, such as text_field_tag:
text_field_tag "to_whom", params[:to_whom]
I think you need to use fields_for helper, It should be something like below,
= c.fields_for :message do

How to select current model in a 3 model system?

I have 3 models. Users have multiple Portfolios and Portfolios have multiple Assets.
When a user signs in, that makes him a current_user by find_by_id. When they first register, it creates a portfolio for them. That becomes the current_portfolio.
Subsequent to this, they can create portfolios which redirects them to the list of all their portfolios. Clicking the link to each will make that portfolio the current_portfolio.
#current_portfolio = current_user.portfolios.find_by_id(params[:id])
The show view for portfolios has an assets form so that they can quickly add assets to the portfolio. This is where I get stuck. Because the assets form is on the portfolios show view, my Asset controller needs to reference current_portfolio, but this is in my Portfolio controller.
Portfolio Controller:
def show
#current_portfolio = current_user.portfolios.find_by_id(params[:id])
#asset = #current_portfolio.assets.build
end
When the form is submitted, it goes to my Assets controller Create function. Create can't simply have #current_portfolio because it's on a different controller. I also can't use find_by_id(params[:id]) because :id is not providing anything.
How do I reference the second model when I have 3 models? (Sorry I'm a newb to rails...)
EDIT Update: I thought I found the solution but realized it didn't work.
I passed <%= hidden_field_tag :portfolio_id, params[:id]%> in my form and set my Create in the Asset controller to be
def create
#asset = Asset.new(params[:asset])
#asset.save
end
but it's not getting the portfolio_id which is a required field in my model.
Full form in my view
<%= form_for(#asset) do |f| %>
<%= hidden_field_tag :portfolio_id, params[:id]%>
<%= f.text_field :asset_symbol %>
<%= f.text_field :shares %>
<%= f.text_field :cost %>
<%= f.text_field :purchase_date, :type=>'date' %>
<%= f.submit "+ Add to Portfolio" %>
<%end%>
My guess would be that the portfolio_id in the hidden field isn't coming back as part of the asset hash. Maybe it's as simple as assigning the #asset.portfolio_id = params[:portfolio_id] before the save. Check you logs to see EXACTLY what is coming back in the POST

Anyone know how to save many objects in one form?

I am trying to save many new objects in a form to one pre-existing parent object.
- form_for :parent_object do |f|
This is the beginning of my form. And then within it, I would do:
- 2.times do
- fields_for :child_object do |f|
Now if I were to save this, it would render as an ParentObject_Controller Update action which would fail because Update doesn't identify new objects.
So if I wanted to render the appropriate Save action, I would have to set up like this :
- form_for [#parent_object, #child_object] do |f|
- 2.times do
- fields_for :child_object do |f|
This form then renders the Save action, but only saves the last child_object.
I would show you my controller, but there's hardly a point because its devastatingly erroneous.
My question is, how would you save many new objects in a form to one pre-existing parent object?
I have looked extensively at Ryan Bate's work, and many many other blogs and SO posts regarding this. Nothing seems to really point at specifically creating new child objects for one pre-existing parent object.
Update:
I am under the impression that I have to toggle the parent_object's controller actions for def update.
elsif params[:parent_object][:child_object]
#child_object = Child_Object.new(params[:child_object])
if #child_object.valid? && #parent_object.referrals << #child_object
redirect_to new_parent_object_child_object_path(#parent_object)
else
render :action => :new
end
In debugger, if I I place a debugger at the root of def update, and I write :
>> params[:parent_object]
#=> nil
Interesting! That means that when child_object is send to parent_object controller, the params are not filled out for it. Haha, no idea what to do about it though..
Unfortunately that code doesn't work, it was just my attempt at getting closer. ;)
OK, let's give it another shot. Code taken from RB's screencast with replaced object names:
<% form_for #parent_object do |f| %>
<%= f.error_messages %>
<!-- some field of parent object here -->
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<% f.fields_for :child_objects do |builder| %>
<!-- some fields for child objects -->
<p>
<%= builder.label :content, "Some content for child object" %><br />
<%= builder.text_area :content, :rows => 3 %>
<%= builder.check_box :_destroy %>
<%= builder.label :_destroy, "Remove child object" %>
</p>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
This is a form for #parent_object that has fields for :child_objects. Of course, you've to replace fields with your own.
To make this work, you'll have to build child objects in the constructor:
def new
#parent_object = ParentObject.new
3.times { #parent_object.child_objects.build }
end
Similarly in the edit method, you'd do:
def edit
#parent_object = ParentObject.find(params[:id])
3.times { #parent_object.child_objects.build }
end
To make it work, you need to define the nested attributes for child object:
class ParentObject < ActiveRecord::Base
has_many :child_objects, :dependent => :destroy
accepts_nested_attributes_for :child_objects
end
Hope this helps - this is exactly what RB proposes in his screencasts. Let me know in the comments if you need some further explanation.
-- EDIT --
The update method in the parent_object_controller.rb is just a standard one:
def update
#parent_object = ParentObject.find(params[:id])
if #parent_object.update_attributes(params[:parent_object])
flash[:notice] = "Successfully updated parent object."
redirect_to #parent_object
else
render :action => 'edit'
end
end
But thanks to the accepts_nested_attributes_for in the ParentObject, the nested instances will be created as well.
I didn't include all the model and controller code in this response. You can see the rest of the code by downloading source code for this episode from github.
You can take a look at this answer I gave to a similar question. There're two options: with separate forms, or with a single form.
You'll just have to change the moderate_names_path to the correct path to your parent model instance (and of course the set of fields you want to modify). You can do it with polymorphic_path:
polymorphic_path([#parent_object, #child_object])

Resources