Let's say you have a form that has its own controller. Is there any way to embed this form in different views (governed by other controllers)? As far as I understand partial templates carry only logic in the Ruby code that is inside the template. I am thinking more of a full-blown component where maybe you can call its controller.
The form is not driven that directly by the controllers. Yeah this is the price of all this magic.
To clarify a bit:
You type in your browser http://yourhost/posts
Your request (GET /posts) hits the router, then your router says that the /post urls belongs to the PostsController and the action is index
Then your controller executes the index method, do your business logic (loads the posts from the database, for example)
loads the view (views/posts/index...) and run it by 'substituting' all the instance variables and stuff defined in your controller (eg #posts = Post.all) that you have in it
then you see the view rendered with a list of posts (if in the view you have something similar to #posts.map{|p| p.title}.join(", ") )
yes I know it's not the best /posts view in the world but it's only to grasp the idea
The same goes for form, your form tag (for example form_for) gets an instance from the controller (let's say #post) and (in edit mode) gets filled with your Post attributes.
Then when you (edit something and) click the submit button it makes a request (by default a PUT to /posts) passing all the values in the form, then your controller gets the (POST) values of the requests (the ones you see in the server log) and makes his work (like saving the post's datas)
and because of this in a controller you can use the method
render :controller => :foo, :action => :bar
to render another controller action different from the default one
Hope this will be useful!
You can create a form in any view to call any controller. In a RESTful app, you can usually just pass an empty object (using the Posts/Commments example from makevoid)
<% form_for #new_comment do |f| %>
<%= f.text_area :text %>
<%= f.submit %>
<% end %>
This form should route to the create action on CommentsController. From there, you could use redirect_to :back in order to get back to the view that triggered this controller. This does have some validation issues I think though.
If you are non-RESTful, you can use the old form_for style:
<% form_for :comment, #new_comment, :url => { :controller => "comments", :action => "create" } do |f| %>
<%= f.text_area :text %>
<%= f.submit %>
<% end %>
For either of these examples, you need to have the #new_comment, which you would create in your PostsController:
def show
#post = Posts.find(params[:id])
#new_comment = #post.comments.build
end
Related
I am working on a rails web app which manages students and courses. I have a courses controller which has the following index action:
def index
#courses = Course.paginate(:page => params[:page], :per_page => 1)
#courses.order(:startDate)
##thisCourse = Course.find(params[:page])
end
So pretty standard except for one thing - all the details of a single course are shown on one page and to show the details of the next course, you move to the next page of the pagination.
The problem is that in this index page showing the details of 1 course per pagination, I have a "Sign Up!" button which when pressed needs to create a a new record in the 'signups' db table which has the automated 'id' field and then the 'user_id' and the 'course_id' fields.
The 'user_id' is easy to find (current_user.id).
The 'course_id' is proving difficult. I imagine that pressing the Signup button should send the course_id to the signups_controller where a create function can do the work. But how can I get this exact course ID from the index page to the signups_controller's create action?
As you can see in the code I pasted from the courses_controller's index action,the '#thisCourse' variable has been commented out because I have found no way to define which course is currently being shown on the page.
The fields are rendered by the will_paginate Gem so I'm not sure how it's generating the fields but I was thinking that maybe I could create a named hidden field which includes the course_id and use that when the sign up button is pressed, however I'm not sure how to go about it.
Does anybody have any ideas?
Thanks!
Well, you can use show method (output one course) instead of index(output all courses) method, that will always get your course id through params.
Basically I changed my approach to the problem. I removed the button which was supposed to call the signups_controller and create the new record in the signups table. This button was replaced by adjusting the form_for helper so that it's submit button would send all the necessary data to the signups_controller (including the id value which was added to the form as a hidden field).
The form ended up looking like this:
<%= form_for course, :url => {:controller => "signups", :action => "create"}, :method => "post" do |f| %>
<%= hidden_field_tag :course_id, course.id %>
<%= f.label :"Course Title" %>
<%= f.text_field :courseTitle, class: 'form-control' %>
+ all fields included in the form....
<%= f.submit "Sign Up!", class: "btn btn-primary" %>
<% end %>
This parameter of form_for defines which controller and which action in that controller is the submission target:
:url => {:controller => "signups", :action => "create"}
and this parameter overwrites the default http action (default is PATCH but in this case I wanted to POST i.e. create a new entry in the signups table):
:method => "post"
I'm not sure if this is a very quick and dirty solution but technically it gets the necessary data to the correct destination controller.
I have a Books model and it has CRUD operations. In config/routes.rb, I have declared
map.resources :books
My new.html.erb looks like as:
<%= form_for :book, url: books_path do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.submit :Add %>
<% end %>
My create method in controller looks like as:
def create
book = Book.new(authorized_params)
book.save
end
So, when I submit my form from views, request would go to the 'create' method and an record for the book gets created in database. Fair enough. Now, I want to have an edit page for book. So, my edit method in controller look like as:
def edit
#book = Book.find_by(params[:id])
render :new
end
When I go to my edit view, it automatically show the value of title in the text box, which is what I expected.. But when I try to submit the form again(ofcourse after changing the title value) it again creates a new record instead of updating it..
Something basic which I missed out in my reading? I googled about it though but did not find satisfactory answer.
The issue is that you are using the 'new' view where form has the post method. If you will check the generated routes, post will be for create method, that is adding a new record. You will have to create a new view for edit where the form target URL will be edit_book_path(#book) and method will be patch. Patch method will route to 'update' function in your controller where you will call #book.update. I am not writing the exact code, but these directions should help you achieve what you want
I need to run this function on my View somehow, and i cannot figure out how to make it, everywhere are forms with controllers. How to make it work?
models/feed.rb
class Feed < ActiveRecord::Base
def self.update_from_feed(feed_url, category)
add_feed(feed_url, category)
end
end
views/feeds/new.html.erb
<%= form_for Feed.update_from_feed(feed_url, category) do |f| %>
<%= f.text_field :feed_url %>
<%= f.text_field :category %>
<%= f.submit "Create" %>
<% end %>
Make a route in config/routes.rb file. This is where your request will go upon some event like button click or form submit. Then when you submit the form(form action will be this route's url) or when you click a button a request should go to the controller method. In the controller you can call the model methods.
For example,
I have a products controller. In the Products controller there is an action called 'search'. I will set up a route in config/routes.rb file to map to this controller action 'search'. So Rails will route all requests on this url to my Products controller's search method.
You need to setup a similar action in one of your controllers(based on design) and send request to the route that maps to this controller action
Please refer http://guides.rubyonrails.org/ for better understanding.
http://guides.rubyonrails.org/form_helpers.html
I'm building a web interface to accompany a mobile app I'm building. I have a drop down select menu that lists a bunch locations.
On selection of a location I want to make a call to a method in my controller and grab some destinations within the location that was selected (each location has several destinations).
I then would like to render my show template with these results allowing the user to select a destination and make a booking.
This is what I have so far:
My view with a list of resorts:
<%= form_tag :url => { :action => :show } do %>
<%= select_tag :resort , options_for_select(#resorts), :prompt => 'Select Resort', :onchange => 'submit()' %>
<% end %>
Controller:
class HomeController < ApplicationController
def index
#resorts = ["A","B", "C", "D", "E"]
end
def new
end
def edit
end
def create
end
def show
#activities = Parse::Query.new("Activity").tap do |a|
a.eq("resort", params[:resort])
end.get
end
end
Just slightly confused. Using form_for makes more sense to me with CRUD in mind and also because the form is object based.
I'd like to just take the selected resorted and pass it into a method in my controller that goes into a database and grabs a bunch of destinations. I then want to list these destinations on my show page where a user can click and be taken to another page where they can make a booking at that destination.
My above code doesn't work. I have resources :home in my routes file.
However when I try to load my page with the form I get:
No route matches {:action=>"show", :controller=>"home"} missing required keys: [:id]
How do I pull this off?
I went on my lynda account and pulled up a rails essential tutorial which I'll have to use to refresh my memory some time tomorrow but the tutor doesn't cover use of select_tag.
Would appreciate some help here
Thanks for your time
So a few thoughts. Not sure why you are using form_tag and also not sure why you aren't using Rails idiomatic conventions.
Declare a resource in your routes for #resorts, like so:
resources :resorts
Then just use Rails form_for helper like:
<%= form_for #resorts, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
<%= f.select :resort, (insert your other options) %>
<%= f.submit "Create" %>
<% end %>
I have not tested the above code, so play around with it, but that should work.
However, let me save you some headache. Checkout SimpleForm.
For your models, you would want to setup an association between your locations and destinations.
class Location < ActiveRecord::Base
belongs_to :resort # or whatever the relation is
has_many :destinations
end
class Destination < ActiveRecord::Base
belongs_to :location # This assumes there is just a one-to-many relationship between Location and Destination
end
Make sure you have a LocationsController with all the actions.
In this case, your SimpleForm form would look something like this:
<%= simple_form_for #locations do |f| %>
<%= f.input :name %>
<%= f.association :password %>
<%= f.button :submit %>
<% end %>
That approach will make your life much easier. Take a look at the collections methods in Simple Form. Rails can make your life difficult with the built in form helpers.
Hope that helps!
In your routes, add
get '/choose_resort' => 'home#show' #you can name the get whatever
Then in your form...
<%= form_tag choose_resort_path do %>
That being said... you should have your query at a separate endpoint, and redirect to the show page. That should get you moving, methinks.
The show action needs an id of the object you are showing. Change your controller:
class HomeController < ApplicationController
def index
#resorts = [["A",1], ["B",2], ["C",3], ["D",4], ["E",5] ]
end
And your view
<%= select_tag :id , options_for_select(#resorts), :prompt => 'Select Resort', :onchange => 'submit()' %>
That gives your show action the proper resort id. You'll have to adjust that action to find the right activities relevant to the resort.
When submitting a form to create a new object i get an error message when submitting the form. When i use the same form but then with an instance variable everything seems to go fine, any clue why the submit with the :symbol fails?
The error message says: Only get, put, and delete requests are allowed.
The code for the new form with :symbol is:
<% form_for :ecard do |f| %>
<%= label(:ecard, :title) %><br/>
<%= f.text_field :title, :tabindex => "1" %>
<%= f.submit "Create Ecard" %>
<% end %>
The form goes ok when i use
<% form_for #ecard do |f| %>
<%= label(:ecard, :title) %><br/>
<%= f.text_field :title, :tabindex => "1" %>
<%= f.submit "Create Ecard" %>
<% end %>
Some code out of my controller:
# GET new_ecard_url
# return an HTML form for describing the new ecard
def new
#ecard = Ecard.new
end
# POST ecard_url
def create
# create an ecard
#ecard = Ecard.new(params[:ecard])
if #ecard.save
flash[:notice] = "Succesfully created a new Ecard"
redirect_to :action => 'index'
else
flash[:warning] = "Error when saving Ecard"
render :action => 'new'
end
end
If the first argument is a symbol, it describes the object the form is about and name the instance variable, and then you need to provide the URL.
The actual object can be used and then it will use your routes to try and determine the URL. This means that it must know of a new_ecard_path and edit_ecard_path.
It will look at the object and see if it is a new record to determine which to use.
If you are using resource routes with the default restful routes, then you can probably just use the instance object. If you need to specify the URL that the form goes to, then use the symbol and specify the URL.
There are a few examples at http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
A good way to see what's going on is to use Firebug and inspect the generated HTML. If you don't have Firebug, get it now, it's really invaluable.
If you create your form using the instance variable, you'll get something along the lines of:
<form id="new_ecard" class="new_ecard" method="post" action="/ecards">
So this will create a POST request to the /ecards action, which is the create method (by the way, your comment above the create method should be POST ecards_url, not ecard_url, unless you've defined it otherwise).
However if you only use the :ecard symbol instead of the instance variable, you'll get:
<form method="post" action="/ecards/new">
Since you haven't specified a URL, it uses the current one. This means your form will call itself in this case, and nothing will happen.
All this is due to all the so called magic Rails does - convention over configuration. But as danivo said, you can specify the URL manually and explicitly state each parameter for the form if you do not want to have this magic happen for you.