I'm following the Getting Started with Rails tutorial.
When generate the view for create new article controller, i use:
Case 1: :article
<%= form_for :article do |f| %>
and get the error No route matches [POST] "/articles/new"
I thought that it should be [POST] "/articles
Case 2 I change to: #artical
<%= form_for #article do |f| %>
and it's OK.
Note that in case 1, the submit button's text is: Save article, and that is Create article in case 2
It does the opposite way with This SOF answer.
It's too ambiguous to me!!! So could somebody help to me to explain it?
This is my source code on github
Short answer
This is how Rails work, so basically, you should pass an object to form_for.
Long answer
After looking around the source code, here what is happening.
When you pass an object to form_for, the action will be computed with:
url_for(polymorphic_path(#object))
which will give /articles if the object is an article not persisted, or /articles/:id if the object is a persisted article.
When you pass a string or a symbol, the action results in url_for called
with an empty hash, resulting in the current path, in your case /articles/new. Note that if you show the form from, let's say /articles/custom, the path will be /articles/custom.
For the button, if you pass an object, the submit input value will be either
I18n.t('helpers.submit.create') or I18n.t('helpers.submit.update') depending on either your model is persisted or not. However, if you pass a string or a symbol, the value will be I18n.t('helpers.submit.submit').
Here are the relevant line of codes from Rails source.
# actionview/lib/action_view/helpers/form_helper.rb#L422
def form_for(record, options = {}, &block)
# .....
when String, Symbol
object_name = record
object = nil
else
object = record.is_a?(Array) ? record.last : record
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
# .....
html_options = html_options_for_form(options[:url] || {}, html_options)
form_tag_with_body(html_options, output)
end
# actionview/lib/action_view/helpers/form_helper.rb#L451
def apply_form_for_options!(record, object, options) #:nodoc:
# ....
options[:url] ||= if options.key?(:format)
polymorphic_path(record, format: options.delete(:format))
else
polymorphic_path(record, {})
end
end
# actionview/lib/action_view/helpers/form_tag_helper.rb#L840
def html_options_for_form(url_for_options, options)
options.stringify_keys.tap do |html_options|
# ....
html_options["action"] = url_for(url_for_options)
# ....
end
end
# actionview/lib/action_view/helpers/form_helper.rb#L1873
def submit_default_value
object = convert_to_model(#object)
key = object ? (object.persisted? ? :update : :create) : :submit
# .....
defaults = []
defaults << :"helpers.submit.#{object_name}.#{key}"
defaults << :"helpers.submit.#{key}"
defaults << "#{key.to_s.humanize} #{model}"
I18n.t(defaults.shift, model: model, default: defaults)
end
# locale/en.yml#L142
helpers:
select:
prompt: Please select
submit:
create: Create %{model}
submit: Save %{model}
update: Update %{model}
For the action, you can see that apply_form_for_options! is not called when you pass a string, so options[:url] remains nil if it was. When you pass an object, apply_form_for_options! set options[:url] to polymorphic_path(#object) if it was not set. This is then passed to html_options_for_form, where the action is set applying url_for to the value.
For the submit value, you can see how the key is taken depending on whether the form target is an object or not, and then translated.
form_for :article created a form, but default url to submit is /articles/new
action new of articles used to display the form to submit, not handle creating a new article
=> resources :articles will map it with GET method
you can specified an url for that form, or change the request method (not recommended)
<%= form_for :article, url: articles_path do |f| %>
Related
I have this link_to helper passing an :email_sequence instance and an extra :set_active params.
I then try to update the :email_sequence instance in the controller using strong params but I'm getting an error saying:
param is missing or the value is empty: email_sequence
link_to:
<%= link_to "Activate", admin_email_sequence_path(base_email.email_sequence, set_active: :true), method: :patch %>
Controller:
class Admin::EmailSequencesController < AdminController
before_action :set_email_sequence
def update
if #email_sequence.update(active: email_sequence_params[:set_active])
flash[:success] = "Sequence updated succesfully"
redirect_to_forwarder_or(params[:forwarding_uri], admin_account_emails_path)
end
end
private
def set_email_sequence
#email_sequence = current_account.email_sequences.find(params[:id])
end
def email_sequence_params
params.require(:email_sequence).permit(:set_active)
end
end
This is what gets sent in the params:
{"_method"=>"patch", "authenticity_token"=>"[FILTERED]", "set_active"=>"false", "id"=>"1"}
Can anybody tell me what am I doing wrong?
By params.require(:email_sequence).permit(:set_active) you expect parameters to be { email_sequence: {set_active: "ANY SCALAR VALUE HERE"} } but you pass only set_active you can fix it by permitting the only one parameter
params.permit(:set_active)
You don't need strong parameters here in the first place. Contrary to popular belief strong parameters does not magically sanitize the parameters. It just prevent mass assignment vulnerabilities by requiring that you whitelist the parameters when passing a hash of parameters. Since you are only using a single parameter there is no mass assignment vulnerability:
class Admin::EmailSequencesController < AdminController
before_action :set_email_sequence
def update
if #email_sequence.update(active: params[:set_active])
flash[:success] = "Sequence updated succesfully"
redirect_to_forwarder_or(params[:forwarding_uri], admin_account_emails_path)
else
# provide an error response!
end
end
private
def set_email_sequence
#email_sequence = current_account.email_sequences.find(params[:id])
end
end
If you later want to use multiple parameters nested in a hash the use of link_to is pretty questionable even if it can be done.
<%= link_to "Activate",
admin_email_sequence_path(
base_email.email_sequence,
"email_sequence[set_active]" => true,
"email_sequence[foo]" => "bar"
),
method: :patch
%>
Use button_to or form_with/form_for to create a form element and style the button to look the way you want instead as this places the parameters in request body instead of the query string.
I need to make a form that can handle the creation of multiple records of the same model, and submit the information in a way that passes through Rails’ Strong Parameters. On the front-end, I dynamically add two new text fields for puppy name and breed and want to save them all to a form. But I'm having trouble passing the array of puppy name and breed through Strong Params; getting
unknown attribute ‘puppies’ for Puppy
I think it's something very simple I'm overlooking. Please help generate these puppies!
new.html.erb
<%= form_tag puppies_path do %>
<%= text_field_tag “puppies[puppies][]puppy[name]” %>
<%= text_field_tag “puppies[puppies][]puppy[breed]” %>
<%= submit_tag “Add Puppy” %>
<% end %>
which generates these parameters
"puppies"=>{"puppies"=>[{"puppy"=>{"name"=>"ribbon", "breed"=>"poodle"}}]}
and the Strong Params in the controller
class PuppiesController < ApplicationController
def index
#puppies = Puppy.all
end
def new
end
def create
puppies_array = puppies_params[:puppies].each do |puppy|
puppy[:puppy]
end
Puppy.create(puppies_array)
end
def show
#puppy = Puppy.find(params[:id])
end
private
def puppies_params
params.require(:puppies).permit({
puppies: [
puppy: [
:name,
:breed
]
]
})
end
end
Any ideas what I’m missing?
Pseudo code of what I want to pass through:
[(name, breed), (name, breed), (name, breed)]
How can I write it correctly? I'm following this tutorial https://medium.com/#dmccoy/how-to-build-html-inputs-to-submit-an-array-of-hashes-with-a-rails-form-tag-that-work-with-strong-f70a6c03d78e
You haven't added rest of your controller code, but I am assuming you are using something like: Puppy.create(puppies_params)
But since you are nesting the params in your view, you will need to use Puppy.create(puppies_params[:puppies]) to ensure you are able to pass in the correct array of name and breed into the create action.
Edit
Since puppies array contains a hash with puppy key, we will need to extract those to get the final array of puppy attributes.
def create
puppies_array = puppies_params[:puppies].collect do |puppy|
puppy[:puppy]
end
Puppy.create(puppies_array)
end
My comment below has a shorthand for setting puppies array, you can use whichever version looks more readable and understandable to you
To get to the array of attributes that you want, you can do this:
puppies_array = puppies_params[:puppies][:puppy].map{|puppy| puppy.values}
This takes the hash puppies within puppy params, and takes the array of hashes in puppy and then takes just the value part of the hashes in that array.
You'll get your pseudo-code result.
For info, note that puppy in the map block has no special significance... it just indicates the instance of the array. You could just as easily do
puppies_array = puppies_params[:puppies][:puppy].map{|some_dog| some_dog.values}
I have a routes question concerning Rails 5. I have this in my config/routes.rb file
resources :votes
The "show" method in my VotesController can take an ID of either a numeric or string form.
def show
id = params[:id]
if id.present?
# If they entered a name
if id.to_i == 0
name = id.gsub "_", " "
#person = Person.find_by_name(name)
else
#person = Person.find_by_id(id)
end
I used to be able to construct this link_to to link to the method to generate a link with a numeric ID ...
<%= link_to person.name, vote_path(person), :class => 'unvotedPersonLink' %>
However, I would like to generate a link with a string value for the ID, as defined by a method in my model called "person.seo_name". So I tried this
<%= link_to person.name, vote_path(:id => person.seo_name), :class => 'unvotedPersonLink' %>
but got the error
No route matches {:action=>"show", :controller=>"votes", :id=>nil} missing required keys: [:id]
How can I build my link_to tag so that it passes in my desired "string" parameter instead of a numeric one?
You can pass in whatever you want. If the error is telling you that the ID you're passing is nil, then your seo_name attribute for the Person you're using is nil.
As long as vote_path is expecting an ID, which you can check via rake routes | grep vote, then this will work fine for you as far as generating a link goes:
<%= link_to person.name, vote_path(person.seo_name) %>
But keep in mind that it'll require you to make the necessary adjustments in your controller to search your persons table based on the seo_name column, instead of id.
Also, I highly recommend that you utilize a gem called friendly_id for what you're trying to accomplish. It'll make things a lot cleaner and DRYer, as you can use it easily in any model. And you can easily use either the ID or a URL-friendly slug to query your table, simply by adding friendly to the query:
Person.friendly.find(1)
# OR #
Person.friendly.find('this-is-a-url-friendly-string')
In your view
<%= link_to person.name, vote_path(person), :class => 'unvotedPersonLink' %>
or
<%= link_to person.name, vote_path(id: person.seo_name), :class => 'unvotedPersonLink' %>
in your controller
#person = Person.find_by(id: params[:id]) || Person.find_by(name: params[:id])
make sure seo_name method is not returning nil
The exception is coming from vote_path, so we can focus on that and not worry about link_to or the controller.
When I'm having route problems like this, the first thing I do is run rake routes (or nowadays rails routes) to make sure I've got the right name. Second I play around in the console with the app variable. For example:
> app.vote_path(1)
=> "/votes/1"
> app.vote_path(id: 1)
=> "/votes/1"
> app.vote_path("foo")
=> "/votes/foo"
> app.vote_path(id: "foo")
=> "/votes/foo"
> app.vote_path(nil)
ActionController::UrlGenerationError: No route matches {:action=>"show", :controller=>"votes", :id=>nil}, possible unmatched constraints: [:id]
> app.vote_path(id: nil)
ActionController::UrlGenerationError: No route matches {:action=>"show", :controller=>"votes", :id=>nil}, possible unmatched constraints: [:id]
You say elsewhere you've checked already that person.seo_name is not returning nil, but I would check again, but it really looks like that's the problem. Try this:
p = Person.find 5 # or whatever is the numeric id
app.vote_path(p.seo_name)
The params can contain any information, but you have to process them in the controller based on your need. By default, the routes requires to have an params called :id. If you want other name to be used in your route you can override the default resource identifier :id
Check the rails routing
Make sure the person.seo_name is not returning nil. So, you can use before_save callback in Person model. There, you can set the seo_name field with specific data.
class Subscription < ActiveRecord::Base
before_save :populate_seo_name, on: :create
private
def populate_seo_name
self.seo_name = ...
end
end
This has been answered in the past https://stackoverflow.com/a/26600064/4652075.
You are passing a string to a field that is expecting an integer and it is being rendered null. You should be passing a string value to a field that is expecting a string. If your "votes" model is generated by ActiveRecord then it is likely an integer.
In your case:
class Person < ApplicationRecord
def to_param
identifier
end
# or
alias_method :to_param, :identifier
end
class VotesController < ApplicationController
before_action :set_voter
def show end;
private
def set_voter
if !params[:id]?
#person = Person.find_by(identifier: params[:seo_name])
else
#person = Person.find(params[:id])
end
end
end
# in the view
<%= link_to person.name, votes_path(identifier: person.seo_name), class: "whatever" %>
Should work.
I stored all the tablename I've created to Menu table. And every time I add the table in Menu, it will automatically create a link under Menu list
see below.
I want each table in Menu to have a Listing, New, Edit, and Delete.
see below.
I have a controller prj_menus_controller, I will just pass the id of the table from Menu table.
here is the code for index and new in my controller.
Class PrjMenusController < ApplicationController
def index
#prj_menus = Menu.find(params[:id]).tablename.singularize.classify.constantize.all
end
def new
#prj_menu = Menu.find(params[:id]).tablename.singularize.classify.constantize.new
end
def create
#prj_menu = Menu.find(params[:id]).tablename.singularize.classify.constantize.new(prj_menu_params)
if #prj_menu.save
redirect_to :action => 'index'
else
render :new
end
end
private
def prj_menu_params
params.require("HERE IS MY PROBLEM").permit(:name)
end
end
and in my
new.html.erb
<%= simple_form_for (#prj_menu),:url => prj_menus_path, :method => :post do |f| %>
<%= f.input :name %>
<%= f.submit 'Save', class: 'btn btn-primary' %>
<%= link_to "Cancel", :back, {:class=>"btn btn-default"} %>
<% end %>
I can get the list in my index.html.erb, it is working. My problem is that I don't know how to get all params when I click the submit in new.html.erb. I got this hash
{"sample1_table"=>{"name"=>"test 6"}, "commit"=>"Save","controller"=>"prj_menus", "action"=>"create"}
It is correct but I don't know what to put in my controller. I tried this params.require(["#{#prj_menu}"]).permit(:name), it creates new record but params[:name] does not save.
I am still a noob to Ruby On Rails and I don't know what to search for this.
I think you are mostly confused on what parameter whitelisting does and how parameters are passed from the form to the controller.
I does not really matter if the name of the form hash matches the name of the database table. It just does in most cases since that makes the most sense. It's simply representative of the REST interface of your app.
Let's say you have a action which creates Pets:
POST /pets
And in our form we have a bunch of inputs like so:
<input name="pet[name]">
Rails will map create a params[:pet] hash { name: 'Spot' }. But we want to save the pets as an Animal.
class PetsController < ApplicationController
def new
#pet = Animal.new()
end
def create
#pet = Animal.new(pet_params)
if #pet.save
# ...
end
def pet_params
params.require(:pet).permit(:name)
end
end
Animal does not care what the params key is, it just gets a hash. But we also need to tell simple_form what parameter key we want to use since it looks at the model_name attribute.
simple_form_for(#pet, as: :pet)
Gives us pet[name] instead of animal[name].
I don't get why you are so adamant about making things so difficult for yourself though unless you are creating a database administration tool in the vein of PHP_MyAdmin. And even that case you don't even want to be altering the schema of the app database at runtime.
You are going to run into huge problems when it comes to creating effective queries for getting all the menus.
what does this line of code do it is from the form_for method in rails ?
object = record.is_a?(Array) ? record.last : record
First off, here is the actual line of code, and the full context:
def form_for(record, options = {}, &block)
raise ArgumentError, "Missing block" unless block_given?
html_options = options[:html] ||= {}
case record
when String, Symbol
object_name = record
object = nil
else
object = record.is_a?(Array) ? record.last : record
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
[...]
end
It says: if record is an array, then assign the last element of the array to object, otherwise assign record itself to object.
So basically, it describes how to handle the case when you don't know whether you'll be getting an array of records or just one record.
There are a couple of cases where you pass an array into form_for.
Namespaced routes:
<%= form_for([:admin, #post]) do |f| %>
...
<% end %>
Nested resources:
<%= form_for([#document, #comment]) do |f| %>
...
<% end %>
Note that in each case, it's the last element in the array that the form is actually for; the earlier elements provide context (on either namespaces or nesting). More in the docs.
It checks if the variable record is an array, and if it is, gets the last element in the array, and if it isn't gets the record itself.
This is useful for nested routing, for instance if you had a model Book that belongs to User, and in your routes you nest them:
# config/routes.rb
resources :users do
resources :books
end
Then for the resource you'd need to specify form_for [#user, #book] do |f|
More on form_for with nested resources: form_for with nested resources
Also it uses the is_a? method: http://ruby-doc.org/core-1.9.3/Object.html#method-i-is_a-3F
And the ternary operator: Ruby ternary operator without else