I'm submitting a form that is sending in a child resource's id value for both the child resource and it's parent resource's id. I don't know how to get the form submission to stop duplicating the child id as the parent resource's id.
Technology Stack:
Rails 3.2.8
MongoMapper
Simple Form (although the same issues occur with the stock form_for that it wraps)
Relevant excerpt from routes.rb:
namespace :mock do
resources :patients do
resources :allergies
end
end
My models are defined in Mock::Patient and Mock::Patient::Allergy.
In my Allergy resource's view partial _form.html.erb, I have the following opening form helper usage (I use Simple Form, but the same results occur with the stock form_for helper):
<%= simple_form_for [#mock_patient, #mock_allergy], :url => mock_patient_allergy_path(#mock_allergy), do |f| %>
While this renders, it is submitting to this route:
/mock/patients/:patient_id/allergies/:id
So my allergies_controller.rb file does receive an update action (in the case of an edit operation).
However, when I look at the params, params[:patient_id] is the same as params[:id]. Both are actually the id value of the specific nested allergy resource being edited. By the parent resource (Patient in this case) has its context lost.
So, I set out to include a hidden field in the form:
<%= hidden_field_tag('patient_id', #mock_patient.id) if #mock_patient %>
When I view the page source before submitting the form, sure enough, I can see the correct patient_id value.
There seems to be some built in form handling logic that is replacing the parent resource's id with that of the child resource.
My model files, using Mongo Mapper are:
class Mock::Patient
include MongoMapper::Document
# other Patient model keys here
many :allergies, :class => Mock::Patient::Allergy
end
class Mock::Patient::Allergy
include MongoMapper::EmbeddedDocument
# other Allergy model keys here
belongs_to :patient, :class => Mock::Patient
end
To recap, I'm able to finagle the form tag to submit to the correct route, but the params hash received by the controlled is jacked up - losing the parent resource context.
Other variations of my form tag that I've tried, to no avail:
<%= simple_form_for #mock_allergy, :url => mock_patient_allergy_path(#mock_allergy), do |f| %>
and
<%= simple_form_for #mock_allergy, :html => { :class => 'form-horizontal' } do |f| %>
In the above both cases, the basic route template generated is fine, and it reaches my allergies_controller, but when I go to inspect params[:patient_id], I get an incorrect value. It is actually a duplicate of params[:id].
Concrete Example
My form tag line:
<%= simple_form_for #mock_allergy, :url => mock_patient_allergy_path(#mock_patient, #mock_allergy) do |f| %>
This generates a page that when I view source, reports:
<form accept-charset="UTF-8"
action="/mock/patients/5092c815fdb5424df800000d/allergies/5092c815fdb5424df800001c"
class="simple_form form-horizontal"
id="edit_mock_patient_allergy_5092c815fdb5424df800001c" method="post"
novalidate="novalidate">
But when it hits my controller, who looks to find a Mock::Patient based on the on params[:patient_id], it doesn't get the '5092c815fdb5424df800000d' value I would expect. On my browser, the error page indicates the parameters received were:
{ "patient_id"=>"5092c815fdb5424df800001c",
"id"=>"5092c815fdb5424df800001c" }
These two IDs are identical, and therein lies the problem.
Suggestions?
Solved:
In my update method, on success the redirect from scaffolding was:
redirect_to #mock_allergy
instead of:
redirect_to mock_patient_allergy_path(#mock_patient, #mock_allergy)
So, I was getting punted to a correctly formed route, but with a bogus patient_id.
My other mistake, was that in modeling Allergy, I should have used an embedded_in :patient instead of the belongs_to :patient line.
Derived Question:
Why is it, that #mock_allergy, and instance of Mock::Patient::Allergy, knowing it is a nested resource, couldn't generate a correct path that included the right patient_id?
Related
I'm learning ROR and working on a voting style application, I've got my four tables up and running Users, Questions, Options and Answers and I can write to the Answers table via MySQL and the total votes are displaying so far which is great.
I'm having a problem with getting a +1 button working (eventually it'll be unique per user but for now I just want to see it working).
I'm fairly certain my files are all setup correctly but I'm confused with what to put in my Questions "Show" view for the button, it might be a routes problem as it does say undefined method 'upvote_option_path' - any help would be greatly appreciated! If I've forgotten any files below let me know :-)
Question's Show View:
<% #question.options.each_with_index do |option, index| %>
<p><%= option.option_text %></p>
<p><%= pluralize(option.answers.count, "vote") %><br><br></p>
<p><%= button_to '+1', upvote_option_path(option), method: :post %></p>
<% end %>
Option's Controller upvote:
def upvote
#question = Question.find(params[:question_id])
#option = Option.find(params[:option_id])
Answer.create(user_id: current_user.id, question_id: #question, option_id: #option)
end
Routes file:
resources :questions do
resources :options do
post 'upvote'
end
end
rake routes:
question_option_upvote POST /questions/:question_id/options/:option_id/upvote(.:format) options#upvote
undefined method upvote_option_path
According to your routes, there is no upvote_option_path, it should be question_option_upvote_path. Also the path takes two arguments as keys(:question_id & :option_id), so you need to pass those two to the path.
The below should work
<%= button_to '+1', question_option_upvote_path(#question, option), method: :post %></p>
In rake routes you have this: question_option_upvote, in your view you have this: upvote_option_path. Pretty sure your view should say question_option_upvote_path(option).
Is it possible to display certain elements within a partial for a particular view in Rails? For example I'd like the submit button's text to change depending on the view: so if I'm in the new.html.erb I'd like the submit button to appear as, <%= f.submit 'Create Account' %> and <%= f.submit 'Update Account' %> for edit.html.erb. The unconventional way would be to manually add the custom code into the each view but is there a clever way to do this in my _form.html.erb partial?
First of all, I would recommend putting it into the new and edit views. However, you can switch off of params[:action] if you want to. As in
<%= f.submit(((params[:action] == 'new') ? 'Create' : 'Update') + ' Account') %>
Use simple_form with i18n for that. SimpleForm make it automatically.
Example:
<%= simple_form_for(#message) do |f| %>
<%= f.error_notification %>
<%= f.input :title %>
<%= f.input :description %>
<%= f.button :submit, class: "btn btn-primary" %>
<% end %>
I second kdeisz's answer if your intention is to use a single partial. The line he wrote will not be necessary if you use two separate views - You can just use different names on the same button in each view without any need for conditional logic.
To answer your supplemental questions: There is a tradeoff here between future changeability and DRY code. If your new and edit needs will start to differ significantly, you will have a lot of bloated, difficult-to-change conditional logic in your partial if you use it to render major features.
If you keep the views separated, this may repeat a significant amount of code, but it will also make the individual pages easier to change; the functions of each view will be tailored very specifically to the needs of each HTTP verb.
The answer isn't to conform completely to REST or to DRY "just because", but to ask yourself what will result in more work down the road. If your new and edit pages will be basically the same but for a few very minor features, the single partial (DRY) is more practical. If you see them diverging significantly in the future, keep them separated into two views (less DRY but more changeable).
Params. Each request made to Rails will automatically include an action and a controller based on the route the user requests; for example, navigating to /foo/bar might trigger action bar for controller foo, depending on how you've set up config/routes.rb. Rails fills in params[:action] and params[:controller] with these automatically. A good explanation of how this works, and how to access path and request params, can be found here.
I had a problem with a rails app I am making. It is an e-commerce site, and on the page where the user enters their billing info, posting the form causing the cart information to be lost.
I solved the problem by changing the markup in the erb file. The first code for the form was:
<form method="post" id="checkout" action="<%= url_for :action => :place_order %>" >
<%= submit_tag "Place Order" %>
</form>
This didn't work, so I tried the following:
<% form_tag :method => 'post', :controller => 'checkout', :action => 'place_order' do %>
<%= submit_tag 'Place Order 2' %>
<% end %>
Does anyone know why the second piece of code does not cause the cart information to be discarded?
I was going to write this as a comment, but it's too long
Helpers
As a rule, you generally need to use the Rails helpers (of which form_tag is one) wherever possible.
The reason being that as Rails (& HTML) develops, you'll find that the helpers will adapt to keep up with current syntax, whereas using "naked" HTML will not. This not only keeps your application conventional, but DRY
By manually typing out an HTML form, you not only open yourself up to syntax change issues, but also you won't get any of the benefits that Rails-integration provides, such as CSRF protection
form_tag
A quick note on your form_tag
You've defined the route for the form_tag by using :controller & :action. You'd be much better using one of the route_helpers that will be defined in your Rails routes file:
#config/routes.rb
resources :checkout, path_names: { create: "place_order" }
This will allow you to use the following:
<%= form_tag checkout_place_order_path do %>
It must also be noted that the form_for tag will actually employ the :post method by default
--
Params
You mention the params for your item are not being passed using your standard form.
The reason for this is actually quite simple - Rails builds a params hash whenever you send data to the controller via an HTML form.
The issue you have is that if you define your form with "naked" HTML, those params will not be generated correctly (they are built from your form element names). This means if you want to send the correct data through to your application, you'll be much better suited to using one of the helpers, such as form_for (which is what you've now discovered)
form_for
As mentioned by DMKE, you may wish to use form_for. This is different than form_tag, as it relies on having an ActiveRecord object to work with. You'd have to do it like this:
#app/controllers/checkout_controller.rb
Class CheckoutController < ApplicationController
def new
#checkout = Checkout.new
end
def create
#checkout = Checkout.new(checkout_params)
#checkout.save
end
end
You can then populate a form_for with the following:
#app/views/checkout/new.html.erb
<%= form_for #checkout do |f| %>
Rails usually adds CSRF protection token in form of hidden fields into your HTML form, but only if you use the Rails helper methods. Compare this view
<% form_tag method: 'post', controller: 'checkout', action: 'place_order' do %>
<%= submit_tag 'Place Order 2' %>
<% end %>
with this rendered output:
<form accept-charset="UTF-8" action="/checkout/place_order" method="post">
<div style="display:none">
<input name="utf8" type="hidden" value="✓">
<input name="authenticity_token" type="hidden" value="q5PYT8i+XTlnoKeVqCoz8VMtKKSJP+CXQb/E+G0Vxlk=">
</div>
<input type="submit" name="submit" value="Place Order 2">
</form>
Now, to get your orginal form up and running, you can either insert this snippet into your <form> tag:
<%= hidden_field request_forgery_protection_token, value: form_authenticity_token %>
or you can disable CSRF protection (not recommended at all!):
# in your app/controllers/application_controller.rb
skip_before_action :verify_authenticity_token
You should at least read the Rails Security Guide on why disabling the CSRF protection is a bad thing.
N.B. Is there a reason why you don't use form_for?
I have a link to a page in the same folder as this page on my rails site:
<%= link_to 'Special Access', 'followers/special_access' %>
However, when I go to this page it shows a different page on that url.
<p id="notice"><%= notice %></p>
<div id="sent">
<p>Your request has been sent</p>
<%= link_to 'Home', followers_path %>
</div>
I tried deleting the page that the html is from, but first of all I need that page and it also gives me an error.
I edited the controller to contain:
def special_access
format.html { redirect_to followers/special_access }
format.json { render :json => #post }
end
instead of
def show
but that still didn't solve the problem.
How do I get the right html to show up on the right page?
If you do not define a route for special_access, rails will just assume the the special_acces part in the path is the :id of the route for the show page (as the urls look like followers/:id).
So first off, in your routes.rb, find the resources :followers and replace with the following:
resources :followers do
collection do
get :special_access
end
end
And now you should best always use the rails path helpers, so your link would become
<% link_to 'Special Access', special_access_followers_path %>
Here I was assuming the special access is on the collection of the followers, if it should be on a specific follower (which seems more logical to me, but I have no idea of course), you should write
resources :followers do
member do
get :special_access
end
end
And your link would become
<% link_to 'Special Access', special_access_followers_path(#follower) %>
I am not quite sure what you want to do in your controller action, I hope you just want to render an html page (because redirecting to the same url seems silly, and your syntax is wrong there too).
Hope this helps.
I have a Publication model with a has_many relationship to Contributors. In the model, I have a method that's meant to create an html-ready by line:
def authors
authors = []
Contributor.where(:publication_id => self.id).each do |author|
authors << "link_to "+author.name+", Contributor.find("+author.id.to_s+")"
end
authors.to_sentence
end
In my view, I have the following line:
by <%= #publication.authors %>
But instead of rendering links, it renders the raw code, such as:
by link_to B, Contributor.find(1)
I've tried patching this by adding .html_safe to the end of #publication.authors, but to no avail. Is there a better way to transfer these links from the model to the view?
You're pushing strings into your authors array. It looks like valid code, so running eval on it should work. (Actually author.name will probably evaluate as an undefined symbol, so scratch that.)
A better way would be to use a has_many :authors, :model => 'Contributor' relationship on your Publication model, and you can bring up your array of Contributor objects by simply calling
#publication.authors
You'd want to iterate over these in your view like so:
<% #publication.authors.each do |author| %>
<%= link_to author.name, author %>
<% end %>
Note also that if you're displaying multiple Publication objects in a view this way, you'll want to use Publication.includes(:authors) in your controller when you're retrieving them to avoid the "N+1" problem.
Now, three lines of code doesn't seem very expensive to repeat, but there are ways to DRY that without violating the MVC pattern and cluttering your model:
Place the code to print a publication's authors into a partial, and call the partial as needed.
Place the code into a helper, include the helper and call the method as needed.
Here's a snippet from the source for to_sentence (you can adapt it for your needs, I think):
case length
when 0
""
when 1
self[0].to_s.dup
when 2
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
else
"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
The full source can be found here.
It looks like you are trying to use haml syntax in your line. Maybe instead of using link_to, use an html hyperlink tag itself?
That being said, why are you having a model return html?
Edit: bdares answered already with what I was trying to say