How do rails forms work without a model? - ruby-on-rails

I am trying to challenge my learning by creating a simple project using rails forms, however I have gotten really stuck and can't find any information that seems to help online.
What I am trying to do:
I want to create a rails application with no model (so no persistence of data). Simply put I want a user to enter a Soundcloud URL which then gets transferred to the controller where I can do more logic. Essentially I am really trying to understand the connection between the Rails form and the controller in rails. I have spent all day reading about HTML forms, as well as googling this exact question without really fully getting it.
I understand there are different form helpers, but what I cannot seem to understand is how to use these without a model. My biggest hang up right now is I cannot get the form values transferred to the controller. I thought I understood RESTful routes, PUT/GET etc.. but this has made me super frustrated that I cannot seem to get my head around this. Any advice is super appreciated.
The code:
Below is the specific code I am struggling with, currently when I get submit the form it crashes giving me an error based on routes, and that's where I am stuck.
Problem code is found in the _form.html.erb file in views:
<%= form_tag '/show' do %>
<%= label_tag(:soundcloud_url, "Please enter a valid Soundcloud Artist URL:") %>
<%= text_field_tag(:soundcloud_url) %>
<%= submit_tag("Let's go!") %>
<% end %>
Routes.rb:
Rails.application.routes.draw do
resources :soundcloud_query
root 'soundcloud_query#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Error:
Routing Error
No route matches [POST] "/show"
rake routes output:
Prefix Verb URI Pattern Controller#Action
soundcloud_query_index GET /soundcloud_query(.:format) soundcloud_query#index
POST /soundcloud_query(.:format) soundcloud_query#create
new_soundcloud_query GET /soundcloud_query/new(.:format) soundcloud_query#new
edit_soundcloud_query GET /soundcloud_query/:id/edit(.:format) soundcloud_query#edit
soundcloud_query GET /soundcloud_query/:id(.:format) soundcloud_query#show
PATCH /soundcloud_query/:id(.:format) soundcloud_query#update
PUT /soundcloud_query/:id(.:format) soundcloud_query#update
DELETE /soundcloud_query/:id(.:format) soundcloud_query#destroy
root GET / soundcloud_query#index
Github link:
https://github.com/gaelant/simple_soundcloud_app/commit/09c4c4df524bb721a0f472b4378cd8c1ff18177f
Note: I understand this is a basic question but I have just gotten really confused with this. I know the way the code above is written is not correct, but I have tried so many different things and I just don't understand what is going on, or if this is even possible without a model.

Your /show form action points to nowhere. rails routes shows you which url is valid and to which controller#action each of them lead.
The correct form action would be
<%= form_tag '/soundcloud_query' do %>
<%= label_tag(:soundcloud_url, "Please enter a valid Soundcloud Artist URL:") %>
<%= text_field_tag(:soundcloud_url) %>
<%= submit_tag("Let's go!") %>
<% end %>
A far more better way is to use the url_helpers available through your defined ressource in Routes.rb
Doing so your form action would look like this
<%= form_tag soundcloud_query_index_path do %>
<%= label_tag(:soundcloud_url, "Please enter a valid Soundcloud Artist URL:") %>
<%= text_field_tag(:soundcloud_url) %>
<%= submit_tag("Let's go!") %>
<% end %>
The hardcoded and the url_helper based solution will route your request to a controller class named SoundcloudQuery where the action create will be called. Inside this action you have to put all needed logic.
It's also possible do define that /show should point to a specifc controller and action. This would look like this.
post '/show', to: 'mycontroller#myaction', as: 'mypathnameforhelper'
A much more better explanation with many examples about routes and how to use them can be found in this quite good guide.
Rails Routing from the Outside In
Hint: You should stay close to the ROR naming conventions. Controllers should have pluralized names. So you should define your routes like this:
Rails.application.routes.draw do
ressources :soundcloud_queries
root 'soundcloud_queries#index'
end
and then rename your controller class and file accordingly into SoundcloudQueries and soundcloud_queries.rb. But this is not mandatory.

I think in this case you should not use the url in the form_tag but the controller action.
Let's say you've got a view called my_form.html.erb and a submit_form method in your soundcloud_controller.rb
def submit_form
params[:soundcloud_url] your logic
end
I'd set up the routes like this:
get 'soundcloud_query' => 'soundcloud_query#my_form' // didn't want to use 'resources' but it doesn't matter
post 'soundcloud_query' => 'soundcloud_query#submit_form'
The form would then look like:
<%= form_for :this_doesnt_matter, action: :submit_form do |f| %>
<%= f.text_field :soundcloud_url %>
<%= f.submit %>
<% end %>

Related

How to create a href tag with link_to and action 'create'

I want to create a href tag like href=contacts/create. In my contacts_controller, I have a create GET action. I know this is against rails convention. I still need to create the above link using options = {controller=> 'contacts', action=>'create'}. It works for any other arbitrary action name
You can the hardcoded path option:
<%= link_to "Create", "contacts/create" %>
or the Rails generated path option:
<%= link_to "Create", { controller: "contacts", action: "create" } %>
This is not just against Rails' convention, but against sounds HTTP usage. This often causes serious problems that you can't predict in advance. Web crawling is just one of them, where something like the Google bot accidentally creates a new contact in your database, simply by crawling the page. Or script kiddies who find you have a create link, and send 100,000 clicks to it in quick succession.
Numerous other issues happen like this, including, at one well-known time, Google Chrome pre-fetching GET urls from the page to "speed up the user experience"; this was felt far and wide by sites that had used this technique. It's not an idle warning or a style issue: this can have a disastrous impact on your site.
First off this is really bad idea since GET requests should be idempotent. You're not just flouting convention - you're setting yourself and your users up for a really bad time since for example pressing the back and forward buttons will cause resources to be created - over and over. And there is guaranteed a better way to solve whatever you are trying to do such as:
# a "discrete form"
<%= button_to "Create contact", contacts_path, method: :post %>
# or use the rails ujs
<%= link_to "Create contact", contacts_path, method: :post %>
If you ABSOLUTELY have do this:
Rails.application.routes.draw do
get "contacts/create"
end
You can now do:
<%= link_to "Create", { controller: 'contacts', action: 'create' } %>
Congratulations, you broke the internets.
Like you mentioned, this is against rails convention, but if absolutely necessary, you can do this from your controller:
options = {controller=> 'contacts', action=>'create'}
view_context.link_to url_for(options)
If you need the href to only be the path, you can do:
options = {controller=> 'contacts', action=>'create'}
view_context.link_to url_for(options.merge(only_path: true))

Form_for namespaced route for a resource with multiple states

I have a Blog model which has different states. In order to keep a skinny controller and follow the convention of only having CRUD operations per controller, I followed DHH's namespacing controllers pattern and namespaced out the Blog controller.
Now I have a Blogs::NewDraft controller, a Blogs::AwaitingApproval controller, and a Blogs::Active controller.
I have the following in my _form.html.erb for creating a new draft for a blog. It works for creating a new record, but it does not work for updating an existing record:
# app/views/blogs/new_drafts/_form.html.erb
<%= form_for #blog, url: :blogs_new_drafts do |f| %>
...
<% end %>
When I go to update the draft it throws this error:
No route matches [PATCH] "blogs/new_drafts"
I'm having trouble figuring out why it is saying there is a routing error when I update here because when I run rake routes it does have a route for a PATCH to blogs/new_drafts#update
Change your URL:
<%= form_for #blog, url: :blogs_new_draft(#blog) do |f| %>
...
<% end %>
The URL for your create action will differ from the update. The update action URL needs to know which blog you are trying to update.
That said, if you are trying to share _form with the new and edit views, having a single form won't work, since you are providing custom URLs which will differ between those two actions. There's ways to make it work (pass in the URL to the partial / instantiate the URL in your controller as a variable / add the form tag directly to your new/edit views / etc), but something will need to be done.

Submitting a form with GET method which doesn't match route

I want to filter results by category and I'd like to use the GET method instead of POST. However, I am doing something wrong that I can't figure out: the form's action does not match the defined route, so it triggers a different method.
Here's the form:
<div>
<%= form_tag '/expenses/search', method: 'get' do %>
<%= select_tag 'category_name', options_from_collection_for_select(Category.order(:name), :name, :name) %>
<%= submit_tag 'search' %>
<% end %>
</div>
Sending this form produces an URL like the following:
http://localhost:3000/expenses/search?utf8=%E2%9C%93&category_name=Alcohol&commit=Search
However the route is defined like this:
resources :expenses
get 'expenses/search/:category_name', to: 'gastos#search_by_category'
This means the URL where the form is submitted isn't the one I'm trying to submit it to. It's matched with the one corresponding to the show method, as you can imagine.
How can I submit the form to the matching URL? What is the usual way to deal with this situation?
You didn't set your route properly as it has unrecognized :category_name segment. Your route should be defined like this:
get 'expenses/search', to: 'gastos#search_by_category`
If your route is nested on expenses I recommend to use block function
resources :expenses do
collection do
match 'search', to: 'gastos#search_by_category`, via: :get
end
end
Is a good practice to use rials routes helper, try to avoid put routes with plain text, in your case will be:
<%= form_tag search_expenses_path, method: 'get' do %>
<%= select_tag 'category_name', options_from_collection_for_select(Category.order(:name), :name, :name) %>
<%= submit_tag 'search' %>
<% end %>
Furthermore, don't confuse 'query params' with 'url params'
http//www.host.com/profile/12?type='json'
In this example '12' is a url param and is expresed with :(nameofparam) in routes files but 'type' is a query param that are not expresed on rails routes.
It's supposed to work like that, since it's client-side.
You see, parameters are sent by the browser, that (in general) has no understanding of how your site routing works inside. Submitting a form, in general, requires an URL (to submit params to) and a set of parameters, which in case with GET typically* gets passed as a query string.
The browser will eventually hit the exact route that is specified in form's URL and supply all the form's parameters in a query string appended to the end in usual format:
...?category=stuff
You simply cannot expect the browser to hit a different route (which query string is not part of) with one form just because it has a different value in one of the <input>s.
* I've never actually seen this done differently, but I didn't find a firm requirement of this either.
Do you really want pretty search links that badly?
You could try to circumvent this by placing a "prettifying redirection" – direct search queries to that action, but do not perform search there: instead use the received parameters to construct a route and redirect your user to it.
def search_redirect
redirect_to whatever_search_path(category: params[:category])
end
That would trigger the route helper to build the pretty adress that conforms to the defined routes.
Too hacky?
Well, you could go with submitting a form through JavaScript and alter the parameters and URL request in any way you want. But this is still hacky and I wouldn't do either. Query string in search requests looks perfectly fine to me.

undefined method `pushes_path' for #<#<Class:0x007f85a15c6c90>

i'v been trying to resolve this error for the past 5 hours and I'm gonna burn my computer if I can't solve this.
undefined method `pushes_path' for #<#:0x007f859d605250> this is the error code I'm getting but i don't understand why.
this is my index.html.erb file inside of the interaction
<%= simple_form_for #push do |f| %>
<%= f.input :payload, as: :text %>
<%= f.input :segment, as: :radio_buttons %>
<%= submit_tag "start the campaign" %>
<% end %>
and this is my interaction controller
class InteractionController < ApplicationController
def index
#push =Push.new
end
end
Push is my table in the database and i'll get the inputs and write them in the database to use them for later one.
and this is my routes file
devise_for :partners
get 'home/index'
get 'segmentation/index'
get 'interaction/index'
root to: "home#index"
i really don't know why its looking for pushes_path, what am i doing wrong?
form_for
The problem you have is that your form_for method is going to try and generate a route based off your #path object. And as such, if you don't have a path created for it, you'll receive the error you're getting:
:url- The URL the form is to be submitted to. This may be represented
in the same way as values passed to url_for or link_to. So for example
you may use a named route directly. When the model is represented by a
string or symbol, as in the example above, if the :url option is not
specified, by default the form will be sent back to the current url
(We will describe below an alternative resource-oriented usage of
form_for in which the URL does not need to be specified explicitly).
The bottom line is that as Rails is object orientated, its built around the assumption that you'll have routes set up to handle the creation of individual objects.
Every time you use form_for, Rails will attempt to construct your routes from your object -- so if you're trying to do the following, it will treat the routes as photo_path etc:
#app/views/pushes/new.html.erb
<%= form_for #push do |f| %>
...
<% end %>
--
Fixes
As #mandeep suggested, there are several fixes you can employ to get this to work:
Firstly, you can just create a route for your push objects:
#config/routes.rb
resources :pushes
Secondly, as you're using a different controller, you'll want to do the following:
#config/routes.rb
resources :interactions
#app/views/pushes/new.html.erb
<%= form_for #push, url: interaction_path do |f| %>
...
<% end %>
This will route your form submission to the interactions controller, rather than the pushes controller that you'll get by default!
Objects
Something to consider when creating Rails-based backends is the object-orientated nature of the framework.
By virtue of being built on Ruby, Rails is centered on objects - a term for a variable, which basically encompasses much more than just a piece of data. Objects, in the case of Rails, are designed to give the application:
Once you understand this, the entire spectrum of Rails functionality becomes apparent. The trick is to realize that everything you do in Rails should be tied to an object. This goes for the controllers too:
--
Ever wondered why you call resources directive in your routes, for a controller? It's because you're creating a set of resourceful routes based for it:
Do you see how it's all object orientated?
This gives you the ability to define the routes for specific controllers etc. The most important thing to note is how this will give you the ability to determine which routes / controller actions your requests should go
--
There's nothing wrong in using the controller setup as you have - the most important thing is to ensure you're able to define the custom URL argument, as to accommodate the non-object based structure
In your index action you have
def index
#push =Push.new
end
and your form has
<%= simple_form_for #push do |f| %>
so your form is looking for /pushes with post verb or pushes_path and you don't have that route in your routes.rb file so to fix this you need to add this in routes.rb:
resources :pushes
Update:
when you add resources :push rails basically creates seven different routes for you. One of which is
POST /pushes pushes#create create a new push
and if you look at the html generated by your form it would be something like:
<form action="/pushes" class="new_push" id="new_push" method="post">
// your fields
</form>
notice the action and verb so when you submit your form your routes are checked for them and since you didn't define them in your routes you were getting this error
And how will i be able to use the params i m getting from this form with this new resource addition?
Your form will take you to pushes_controller create action so first of all you'll have to define them. You can access them simply by params[:pushes] in your controller action but since you want to create a new record so you'll have to permit those attributes, checkout strong parameters
If you are using rails >= 4 then you can do
class PushesController < ApplicationController
def create
#push =Push.new(push_params)
if #push.save
redirect_to #push
else
render 'interaction/index'
end
end
private
def push_params
params.require(:push).permit(:attributes)
end
end
If you are using rails < 4 then instead of permitting these attributes(because strong parameters feature came from rails 4) you'll have to tell rails that these attributes are accessible by writing this in your pushes.rb
attr_accessible :attribute_name
Why it is assuming that its pushes controller?Because of the Push.new creation?
That's because if you look at your index action #push = Push.new so #push contains a push object with nil values(as you have just initialized it) so this is where rails magic comes, rails automatically tries to figure out url of your form and since your #push is only an initialized variable so rails takes you to create action for it. For details you should checkout rails polymorphic urls If you want your form to go to interaction_controller or some other url then you'll have to specify the url option for it
<%= form_for #push, url: "your_url_for_custom_method" %>
// other fields
<% end %>
And in the end you should really read docs

Rails choosing wrong route when looping through a nested resource

I've got two resources, Clients and Projects.
In routes.rb:
resources :clients do
resources :projects
end
rake routes gives me this the route prefix client_project for the projects#show action
In my view, I call:
<% #client.projects.each do |project| %>
<%= link_to project.name, project %>
<% end %>
and Rails keeps throwing an error: undefined method project_path, which tells me for some reason the view is trying to call project_path and not client_project_path. I've tried rebooting my server (even rebooting my computer), and can't seem to find why it won't call the route properly. I recently refactored my routes (which caused this break). Am I missing something here? This exact pattern works in every other model in my app, only the refactored route doesn't.
I've never had success getting nested routes to work by simply passing the object in question as the url parameter of link_to... Plus (and maybe it's just me) I like my templates to be a bit more "explicit" than that.
A couple things you could do:
<%= link_to project.name, url_for([#client, project]) %>
or
<%= link_to project.name, client_project_path(#client, project) %>
If projects is a nested route it won't have a project_path route unless you explicitly define one. The Project URLs require a Client. Check this out http://guides.rubyonrails.org/routing.html#nested-resources

Resources