I have a form like this in my html page
<%= form_for '/purchases', html: {class: "form form-horizontal validate-form", novalidate: "novalidate"} do |f| %>
But the form action is like this when i inspect the form
action = '/purchases/new'
I want the action to be just /purchases
and my controller method is like
#purchase = OrderItems.new
If you have your routes configured properly, you just need:
<%= form_for #purchase, html: {class: "form form-horizontal validate-form", novalidate: "novalidate"} do |f| %>
of course, provided you set #purchase in your new action:
#purchase = Purchase.new
I think you're getting confused between form_for and form_tag
--
form_for is for objects -
#app/controllers/purchases_controller.rb
Class PurchasesController < ApplicationController
def new
#purchase = Purchase.new
end
def create
#purchase = Purchase.new(purchase_params)
#purchase.save
end
private
def purchase_params
params.require(:purchase).permit(:purchase, :attributes)
end
end
The important thing to note with form_for is how it will build your form out of the ActiveRecord object you define. This is vitally important, and is at the root of your error:
<%= form_for #purchase do |f| %>
This will build all the different attributes of the form (including the action attribute), from the ActiveRecord object itself. This means if you populate your form_for with anything other than an object, you're going to get into trouble (as exhibited by your error)
--
form_tag is for data -
<%= form_tag your_path do %>
...
<% end %>
This might be better suited to your circumstances, as it allows you to create a "standalone" form - one which gives you the ability to send non-model-centric data to your application
We use form_tag implementations for the likes of search facilities etc
Solution
As Marek pointed out, you need to populate your form_for with an ActiveRecord object. To do this, you need first ensure you have initialized the object in your controller's new action, before passing the value to the form:
#app/controllers/purchases_controller.rb
Class PurchasesController < ActiveRecord::Base
def new
#purchase = Purchase.new
end
...
end
#app/views/purchases/new.html.erb
<%= form_for #purchase do |f| %>
I would just like to elaborate on the answers presented here. The way form_for works is it bases the form input names on the first argument passed to it. You can pass string or symbol as the first argument, and it should work, just like your example.
= form_for :purchase do |f|
= f.text_field :order
will result in a form that will submit to the current url and will contain 1 text field with the name purchase[order].
Marek's answer is correct however it doesn't answer your question. In cases where the object of the form, by convention, doesn't match the controller that will handle the request, you can pass a url option to form_for.
# controller
def new
#purchase = OrderItem.new
end
# view
= form_for #purchase, url: '/purchases' do |f|
= f.text_field :order
This will create a form with action set to /purchases but the name of the text field will be order_item[order].
FINAL NOTE
form_for is not exclusively used for active record objects as stated in the API docs
Related
I'm trying to replicate railscasts #343 in my rails app with this code:
class Article < ActiveRecord::Base
def self.text_search(query)
if query.present?
where("description ## :q", q: query)
else
where(nil)
end
end
class ArticlesController < ApplicationController
def index
#articles = Article.text_search(params[:query])
end
and this view form:
<%= simple_form_for articles_path, method: :get do |f| %>
<%= f.input :query, autofocus: true, label: false %>
<%= f.submit 'Search', name: nil %>
<% end %>
In console text_search method works. From log:
Parameters: {"utf8"=>"✓", "/en/articles"=>{"query"=>"word"}, "locale"=>"en"}
I see that params are sent but I still get all articles listed and no search is performed: Article Load (0.5ms) SELECT "articles".* FROM "articles"
I have rails 4.2.2, PostgreSQL 9.3.7, ruby 2.2.5
You need to pass the search params as:
Article.text_search(params[:search]["query"])
And add #article to the simpleform_for line:
<%= simple_form_for #article, articles_path, method: :get do |f| %>
And add #article = Article.new to your new action in the controller.
Your attribute will then be nested inside :article! You can see it in the console output that the parameters sent by the articles form will always be wrapped in the class name the form is for (Article) via Rails Form helpers.
Update:
Because the simple_form_for wasn't passed #article specifically, it will be wrapped in the name of the submit action which in this case is search. Leaving you with: params[:search]["query"]
A more Rails approach would be to include #article thus wrapping your parameter hash as described above (params[:article][:query]):
<%= simple_form_for #article, {url here}, {options like 'method: :get' or 'remote: true' here } do |f| %>
For extra info. since everyone loves extra info, Rails provides a way to specify the wrapper name for your params hash in the form_for call thus you can also do something like:
<%= simple_form_for (#article, as: :search), articles_path, method: :get do |f| %>
This would be best practice if you really want to keep the params wrapped in 'search' IMO because you're specifying the class object in the form call and letting yourself know what it's doing with that object.
I'm trying to follow a tutorial on using basic AJAX to add a record to a list in place, and I'm having issues using form form_for.
Here is my code.
<%= form_for ([#product, #product.new]) do |p| %>
<p>
<%= p.label :product_part %>
<%= p.text_field :product_part%>
</p>
<p>
<%= p.submit %>
</p>
<% end %>
The error I am getting is
undefined method `new' for nil:NilClass
I understand why I am getting the error (#products hasn't been "initialized") but I have no idea how to fix this issue (I am sure it's simple). I have seen something about putting a resource in the routes file, but I do not know for sure.
If you're trying to make a form for a new product, you should (in your controller) be setting #product to an instance of a new Product:
# app/controllers/products_controller.rb
def new
#product = Product.new
end
In your view, using [#product, #product.new] makes no sense. You can't invoke new on an instance of a product. It's very unclear why you're not simply using the following, which is the correct use of form_for with a new instance of an ActiveRecord model:
form_for #product do |p|
Do this:
#app/controllers/products_controller.rb
class ProductsController < ApplicationController
def new
#product = Product.new
render :form
end
def edit
#product = Product.find params[:id]
render :form
end
end
#app/views/products/form.html.erb
<%= form_for #product, remote: true do |f| %>
<%= p.label :product_part %>
<%= p.text_field :product_part%>
<%= f.submit %>
<% end %>
This will do everything you need for both the new and edit methods (which you raised concerns about in your comments with #meagar).
This should be corroborated with the following routes (you can see why here):
#config/routes.rb
resources :products
I would say In case you need to look the form_for helper ; to understand the behavior of the method.
The method form_for It accept the argument as record, options = {}. The value of record could be a symbol object or a newly object of respective class in your case Person.new.
Second argument could be
:url, :namespace, :method, :authenticity_token, :remote , :enforce_utf8, :html
Among them :remote => true option is used as the Ajaxify your request.
form_for is a helper that assists with writing forms. form_for takes a :remote option. It works like this:
<%= form_for(#article, remote: true) do |f| %>
....
<% end %>
This will generate the following HTML:
<form accept-charset="UTF-8" action="/articles" class="new_article" data-remote="true" id="new_article" method="post">
...
</form>
Note the data-remote="true". Now, the form will be submitted by Ajax rather than by the browser's normal submit mechanism.
For more info about Form-For helper
Hope this solve your problem!!!.
I have a form where I pass related data like this:
form_for(#question, url: create_question_path(category_id: #category.id)) do |f|
f.text_field :content
submit_tag "Save"
save
How do I get the category_id on the same nested level as content, so as strong parameters evaluates it?
you can include it as a tag and if i understand you correctly you want it hidden so:
hidden_field_tag 'category_id', #category.id
now it will be submitted as params[:category_id] and not params[:questions][:category_id] to solve it in your controller create before_filter:
def prepare_params
params[:question][:category_id] = params[:category_id]
params.delete :category_id
end
So I have a site that I'm trying to make, in which you can enter a title and a price for a object called Info. When I enter in the title and price and press submit, the page that it gets redirected to says "The action 'update' could not be found for InfosController". I want it to flash a message notifying the user that the object was created successfully. Any Help??
controllers/infos_controller.rb
class InfosController < ApplicationController
def index
#info = Info.new
end
def create
#info = Info.new(params[:info])
if #info.save
flash[:notice] = 'Object created successfully, thank you'
render :index
else
flash[:notice] = 'Sorry! Object was not created successfully'
render :new
end
end
def update
#info = Info.find(params[:id])
if #info.update_attributes(params[:info])
# if update is successful
else
# if update is unsuccessful
end
redirect_to infos_path
end
end
views/infos/index.html.erb
<h1>Please enter information about an Info object</h1>
<%= form_for #info, url: {action: "create"} do |f| %>
<%= f.label :title %><br />
<%= f.text_field :title %> <br />
<%= f.label :price %><br />
<%= f.text_field :price %> <br />
<%= f.submit "Submit" %> <br />
<% end %>
models/info.rb
class Info < ActiveRecord::Base
attr_accessible :price, :title
end
New error
ActiveRecord::RecordNotFound in InfosController#update
Couldn't find Info with id=create
Rails.root: C:/Sites/2/information
Application Trace | Framework Trace | Full Trace
app/controllers/infos_controller.rb:18:in `update'
Request
Parameters:
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"aXaLYDb5yhl2NXNriOf9gub2JcArrkUOdBogX4kcfKA=",
"info"=>{"title"=>"strawberries",
"price"=>".89"},
"commit"=>"Submit",
"id"=>"create"}
also, here is views\infos\create.html.erb
<h1>Infos#create</h1>
<p>Find me in app/views/infos/create.html.erb</p>
<h2><% if flash[:notice] %> <%= flash[:notice] %></h2>
If you've posted your InfosController in its entirety, you're missing the update action. Add it akin to as follows:
# app/controllers/infos_controller.rb
def update
#info = Info.find(params[:id])
if #info.update_attributes(params[:info])
# if update is successful
else
# if update is unsuccessful
end
redirect_to infos_path
end
UPDATE:
The likely reason why you're not hitting the create action is because your form is submitting to a path that matches the update path, rather than the create path.
First, if you have implemented RESTful routes for your infos resource (as you indicated you have), you shouldn't need to explicitly declare get "infos/create" as you have – the create route is implicitly created in your resourceful route declaration.
Then, if you indeed want a form_for helper to POST to your create action, try passing a hash to the url key in your form_for declaration:
<%= form_for #info, url: {action: "create"} do |f| %>
UPDATE 2:
The reason you are not hitting your update action is that the path you're posting your form to is /infos/create, when according to the canonical Rails guides, you should be posting to /infos. You can resolve this simply be removing the url argument in your form_for declaration:
<%= form_for #info do |f| %>
By default, Rails knows to route a form_for submission for a new object to the corresponding create action of the controller.
The reason why you're hitting the update action is that Rails takes the path you're currently submitting to, e.g. /infos/create, and routes to the update action with a params[:id] of create (rather than a numerical id). To properly invoke the update action, you should pass an existing Info object (for instance, Info.first) to your form_for helper. This will automatically route your submission to the update action:
# app/controllers/infos_controller.rb
def edit
#info = Info.find(params[:id]) # let's assume the id == 42
end
# app/views/infos/edit.html.erb
<%= form_for #info do |f| %>
Because you're passing an existing Info object, this form_for helper will automatically POST the request to the path /infos/42. Because the HTTP request method is POST, the request will automatically be routed to the update action, where params[:id] will equal 42 in the example above.
get "infos/create" should be dropped from routes.rb entirely, since the create route is already implicitly created in your resources :infos declaration.
Let me be clearer.
my app has two tables/models: Bikes and Cars
I have a form on page views/cars/index
However, even though the form is in views/cars/index, it has attributes (:pedals and :handlebars) for the Bikes table.
1) Which controller should the form on cars/index with attributes for the Bikes model be submitted to ?
2) What does the form have to look like (in terms of submitting from a view in the Cars index to Bikes model? <%= form_for(#car) do |f| %> OR <%= form_for(#bike) do |f| %>
3) What changes do I have to make to the route when I submit from the index of views/cars/index to insert data into the bikes table? why did I get a missing controller error message when I tried the solution posted by #axsuul
below this line is an earlier (maybe less clear) attempt to explain the problem but i don't think it was clear
this is a form that I have in views/cars/new.html.erb
<%= form_for(#car) do |f| %>
<% if #car.errors.any? %>
and the results are visible in views/cars/show and views/cars/index
However, if I want to submit a form from views/cars/index (Yes I know you don't usually submit from index), and then have it show in a completely different model views/bikes/index, what do I have to change to this?
<%= form_for(#car) do |f| %>
<% if #car.errors.any? %>
If i understand your question right you have a form for creating bikes on your cars/index page.
In that case, create a new bike in the cars controllers index action,
class CarsController < ApplicationController
...
def index
#cars = Car.all
#bike = Bike.new
end
...
end
and use that to create the form,
<%= form_for(#bike) do |f| %>
...
which left like that will be handled by bikes controllers create action.
I still consider this RESTful...
You can choose whatever URL you want to submit to by doing
<%= form_for(#car), :url => create_car_path do |f| %>
for the route
post '/views/cars/index', "cars#index", :as => :create_car
If you are asking to customize which view to render after submitting, you do that in the controller
def index
#car = Car.new(params[:car])
if #car.save
render 'bikes/index'
end
end
Hmm, so from what you explained, you are probably looking for something along the lines of
bikes_controller.rb
def create
#bike = Bike.new(params[:bike])
if #bike.save
redirect_to cars_path and return # this should go back to /cars if successful
end
render 'cars/index' # or render /views/cars/index.html.erb
end
cars_controller.rb
def index
#bike = Bike.new
end
index.html.erb
<%= form_for #bike, :url => create_bike_path do |f| %>
routes.rb
get 'cars' => "cars#index", :as => :cars
post 'cars' => "bikes#create", :as => :create_bike