Understanding Rails singular and plural paths - ruby-on-rails

I'm very new to using Rails and at the moment am building an Instagram clone as a project to help me understand Rails a bit better. I am following this very helpful tutorial on how to implement the likes/unlikes feature:
https://medium.com/full-taxx/how-to-add-likes-to-posts-in-rails-e81430101bc2
However, I don't fully understand the Rails paths - please could someone explain the difference between:
post_like_path and post_likes_path as mentioned in the tutorial. I cannot see why one is like and one is likes? :(
Really trying to get my head around this so would be so grateful for any insight!
Thanks :)

In Rails as per the REST -
If trying to refer to the single resource then use post_like_path.
If trying to refer to a collection of resources then use post_likes_path
When you want to show or delete a particular resource then you will have to provide an :id for the resource so that the target resource can be found.
[/posts/1/likes/1] - A single record of "like" is being referred here.
While in case of all records plural path is formed to refer to all like records -
[/posts/1/likes] - All records of "like" are being referred here.

post model can have multiple likes. But when we do undo like it will be singular right. so post_like_path will handle the single like and it's going to trigger "delete" action in controller.
post_likes_path will trigger the new action for for creating new like.
Please routes resources concept then you come to know more about it.

post_like_path is used for show page, update and destroy path. The post_likes_path will give you the path for the index and create actions.
This link is the ROR guide and has quite a simple explanation on it:
https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use

I'd suggest to always run this command from terminal:
rake routes
Or just for LikesController:
rake routes -c likes
This shows all the routes related to likes controllers. That's the output, it tells a lot of things. You can see where the plural and singular is used.
# Prefix Verb URI Pattern Controller#Action
# post_likes GET /posts/:post_id/likes(.:format) likes#index
# POST /posts/:post_id/likes(.:format) likes#create
# new_post_like GET /posts/:post_id/likes/new(.:format) likes#new
# edit_post_like GET /posts/:post_id/likes/:id/edit(.:format) likes#edit
# post_like GET /posts/:post_id/likes/:id(.:format) likes#show
# PATCH /posts/:post_id/likes/:id(.:format) likes#update
# PUT /posts/:post_id/likes/:id(.:format) likes#update
# DELETE /posts/:post_id/likes/:id(.:format) likes#destroy
First column shows you the path, the second the pattern (with required parameters) and the third the controller with action (related to the view).
So, for example, take
# new_post_like GET /posts/:post_id/likes/new(.:format) likes#new
This says that the form for a new like can be placed to a page linked by this URL:
new_post_like_path(post_id: #post) note the parameter required. The page is views/likes/new.html.erb.
the controller is LikesController and the action is def new; end where you need to instantiate the objects to be used in that page: #like = Like.new and #post = Post.find(params[:post_id]).
The form is then submitted by POST action, so the line to check is the following:
# post_likes POST /posts/:post_id/likes(.:format) likes#create
As before, the page where the form is located is views/likes/new.html.erb, the url to submit the form is post_likes_path(post.id). The POST action when submitting the form is processed by the controller LikesController and the action is def create; end
Other example:
# post_like GET /posts/:post_id/likes/:id(.:format) likes#show
It tells that to show the Like object with a certain id, you need to visit this link_to: post_like_path(post.id, like.id), the controller is LikesController, the action is def show; end and the view is in views/likes/show.html.erb.
And so on..

Related

Rails routing issue, can't figure out the link path

Let me fair from the outset, and tell you that I've 'solved' the problem I'm describing. But a solution that you don't understand is not really a solution, now is it?
I have a resource, Newsbites. I have an index page for Newsbites. All my CRUD actions work fine.
I created a separate index (frontindex.html.erb) that acts as the front page of my website to show the newest Newsbites. The formatting is different from my normal index so readers get a larger photo, more of the text of the article(more ads too:).
In my routing table, I have the following statements:
resources :newsbites
get 'newsbites/frontindex'
root 'newsbites#frontindex'
Rake routes show the following:
newsbites_frontindex GET /newsbites/frontindex(.:format) newsbites#frontindex
If I load my website from the root (localhost:3000), it works great. There is a separate menu page that is rendered at the top, and it loads fine. I can click on all links, except the 'Home' link, and they work fine.
The 'Home' link is:
<%= link_to 'Home', newsbites_frontindex_path %>
When I click on the linked, I get the following error:
Couldn't find Newsbite with 'id'=frontindex
The error points to the 'show' action of my Newbites controller. Here are the frontindex and show def from the controller. They appear exactly as I'm posting them:
def frontindex
#newsbites = Newsbite.all
end
def show
#newsbite = Newsbite.find(params[:id])
end
I don't get it. Why is the show action being called by newbites_frontindex_path when there is both a def and views that match? Now, I can get around this by simply pointing home to root_path. But that doesn't help me understand. What if this was not the root of the site?
Any help would be greatly appreciated.
Actually I'm very surprised your code worked at all. A route must define two things
Some sort of regex against which the URL of the user is matched (newsbites/frontindex is different than newsbites/backindex)
What do you want to do for a given URL ? You want to point to a controller action
Rails won't usually "guess" what that action is. Or maybe, he was still able to "guess" that you wanted to use the newsbites controller, but it didn't guess the action right this time :(.
You should declare the root like this, which is what you did
root 'controller#action'
For the rest, there are two ways you can declare it. I prefer the second one
resources :newsbites
get 'newsbites/frontindex', to: 'newsbites#frontindex'
resources :newsbites do
# stuff added here will have the context of the `newsbites` controller already
get 'frontindex', on: :collection # the name of the action is inferred to be `frontindex`
end
The on: :collection, means that 'frontindex' is an action that concerns ALL the newsbites, so the URL generated will be newsbites/frontindex.
On the other hand get 'custom_action', on: :member, means that the custom_action targets a specific item, the URL generated would look like newsbites/:id/custom_action
EDIT : Rails also generate path_helpers based on the route declaration
get 'test', to: 'newsbites#frontindex'
get 'test/something', to: 'newsbites#frontindex'
resources :newsbites do
get 'frontindex', on: :collection
get 'custom_action', on: :member
Will generate path helpers
test_path
test_something_path
# CRUD helpers : new/edit/show/delete, etc. helpers
frontindex_newsbites_path
custom_actions_newsbite_path(ID) # without s !
You can always override this by adding an as: option
get 'custom_action', on: :member, as: 'something_cool'
# => something_cool_newsbites_path
Rails routes thinks that frontindex is an id. That's what the error message says. So it goes to GET newsbite/:id which maps to show.
You need to find a way let Rails routes know that frontindex is not an id.
On a side note: The order in which you define routes matters. The first one matched will be used. If you have GET newsbite/:id and GET newsbite/frontindex then the one that appears first will be matched. In your case this is the first one.
Maybe try to change the order.

Creating Questions in Ruby on Rails

I have been following this guide to make a Ruby on Rails web app:
http://guides.rubyonrails.org/getting_started.html
It is supposed to be a quiz where people answer questions.
I have made :questions a resource. However, the guide mentions a page on its website to be able to create and delete its resource articles. Obviously this makes no sense with quiz questions as I just want to create them once and after that no more can be created or deleted. However, there is no mention of this in the guide?
If you don't want to be able to create/update/destroy your questions through controller, you can exclude these restful routes with :except, or specify the routes you need with :only which is more obvious(take a look at the routing guide):
resources :questions, only: [:index, :show]
This will create following routes:
GET /questions questions#index
GET /questions/:id questions#show
To fill your database with questions, use seeds(which are described in AR migrations guide). Put your questions creation related code into db/seeds.rb file and run rake db:seed afterwards.
You should specify in your routes to only :show the questions.
Do something like this in your routes:
resources :questions, only: [:show]
So as much I understand, you want to create resources and data on your own but don't want other users to be able to create or delete a resource, right?
let's start with the basics:
In MVC, the model/resource is the main representation of the entities.
The routes file indicate what routes are available at the first place
for each entity to which HTML requests can be sent. It also matches the route with a respective controller action. The controller
provides a gateway for users to interact with your application, takes
a request from them, take actions needed( issue SQL commands to create, show, delete from the database etc) and give a suitable response at the end.
So, suppose you want users to create a new resource on your server. First, you provide a route on which they can send a request. Then, you give them a create new resource button in your view through which they can interact. That button will use the route, match it with a controller action and send a request to that controller method. Depending on what is inside the controller action, the controller then creates the resource and redirect the user with a 302 with a notification.
Now, suppose you don't want a request to go through. What will you do?
You will firstly not create a route on which a request can be sent. You will also not create a controller action on which request can be received.
That's the use case in your scenario.
Since you don't want users to be able to send a request to the server to create the resource:
Don't provide the routes for them to send any such HTML request
Don't provide any controller which will receive such request
And obviously, don't provide any button in the view to take that action.
Now, the question would be: Then how will I create the resource?
You have three options:
Use a seed file: You can use a seed file where you add all the data you want. This tutorial and many other resources on the web can help you out. http://www.xyzpub.com/en/ruby-on-rails/3.2/seed_rb.html
Use rails console: Give create commands: Question.create(:name => "hello", :description => "How you doing!"). See the link here: https://apidock.com/rails/ActiveRecord/Base/create/class
If you are a beginner, you need to work a little more before trying this: Create authorized users and what actions they can take. You can also create an admin dashboard and provide all actions to admin users using activeadmin gem.
This blog explains the process: http://www.onceaday.today/subjects/1/posts/9 3
Finally, as you do need to show all the questions: create only show route, match it with a controller action, write the method in the controller action which issues a SQL command to get all the resource from the database and give the respective show view the data it can render.
Hope it helps you out!

Rails - working with scaffolding, error linking to the new resource that was made

I am learning Ruby on Rails and i did this scaffolding command:
rails g scaffold Alex
and it ran and created all the resources for me. So I tried to make a link from my index page to the alex page like this:
<%= link_to "Alex Link", alex_path(#alex) %>
(I am still not sure what that #alex part is, but it was in the other examples so I tried to have that there)
and in my routes.rb this code was created:
resources :alexes
get "home/index"
and when I tried to load the link, it gave me this error:
No route matches {:action=>"show", :controller=>"alexes"}
Just in case, here is the output from rake routes:
alexes GET /alexes(.:format) alexes#index
POST /alexes(.:format) alexes#create
new_alex GET /alexes/new(.:format) alexes#new
edit_alex GET /alexes/:id/edit(.:format) alexes#edit
alex GET /alexes/:id(.:format) alexes#show
PUT /alexes/:id(.:format) alexes#update
DELETE /alexes/:id(.:format) alexes#destroy
home_index GET /home/index(.:format) home#index
root / home#index
test POST /test(.:format) tests#create
new_test GET /test/new(.:format) tests#new
edit_test GET /test/edit(.:format) tests#edit
GET /test(.:format) tests#show
PUT /test(.:format) tests#update
DELETE /test(.:format) tests#destroy
What is wrong with the way that I made the link and how can I make it hit the controller before it goes to the view?
Thanks!
alex_path is for showing a specific Alex object. To use it, #alex needs to be an instance of an Alex object loaded by your controller.
You say you want to link to the "Alex page", which makes me think you want the listing of all Alex objects, or the index action of your AlexController. If this is the case, you should use alexes_path instead of alex_path(#alex).
If you actually do want to link to a single specific Alex, you'll need to load an instance from the database:
def my_action
# make a specific Alex object available to the view
#alex = Alex.find(...)
end
As an aside, you also ask:
how can I make it hit the controller before it goes to the view?
Your controller will always be hit before the views are rendered. It is impossible for a view to be rendered without an action being invoked.

rails_3_question :as => why is my /posts/new routing to posts/show after setting up a slug

I'm using Rails 3 and after setting up slugs, I found that posts/new no longer works.
posts/:id, posts/:id/edit and all the other CRUD operations work.
However /posts/new gives me a routing error
No route matches {:action=>"show", :controller=>"posts"}
Now for some reason posts/new is routing to posts#show. In my routes, its just
resources :posts
My theory is that since /posts/:slug now matches against things other than numbers ids, the show verb is being routed to first. However it doesn't make sense since posts/grr a nonexistent entry gives a different error than posts/new and posts/first comes out just fine with all its associated paths working fine as well.
Anyone know what might be going on?
I've uploaded the repo to https://github.com/cultofmetatron/cassowary/tree/photogallary
I know my code sucks, I'm still learning the ins and outs of the system and I'd appreciate any insight into whats going on.
In your comment the first part seems fine: add a column to the Post column called slug and so on, and the contents of that will become some or all of the URL used to display a specific post. (I'll assume the other CRUD operations should work as normal)
To find the URL, the router has to know how to know which controller and action will handle this URL (as compared to others). A normal resources :posts route will match all of the RESTful methods, e.g. mapping a GET request onto a path starting with the controller name, and if an id is specified (/posts/1) map to the posts#show controller method, if not, it will map to posts#index method. If the request is a PUT, or DELETE or POST, different actions around a standardized URL format will occur.
Two changes are needed:
URL with the post slug format needs to map to the posts#show method (which is modified accordingly), and
Any links to the show page that are generated on your site need to use the post slug instead of the id
I'll assume you're OK with URLs start with /posts (if not, you'll need to identify some other unique pattern).
The first change requires that you override the specific case of the show method using route globbing, my adding something like match 'posts/*slug before the standard resource route. Here's a link to the guide on route globbing: http://guides.rubyonrails.org/routing.html#route-globbing
The next change, modify the existing posts#show method so that it looks for slug instead of id, e.g.
def show
#post = Post.where("slug = ?", params[:slug])
...
end
Finally, change the way Rails handles the URL helper posts_path. Do this by overriding to_param in your Post model, e.g.
def to_param
"/posts/#{slug}"
end
And then you're done. Maybe.
After that, see how the friendly_id gem does the same thing :-) https://github.com/norman/friendly_id

Why is the scaffold generating routes like this? Why do they work?

The book "Agile development with Rails" shows in the second chapter, that you can say:
<%= link_to "Goodbye",say_goodbye_path %>
Instead of hardcoding the path to "/say/goodbye". Makes sense, I thought to myself. Probably Ruby is splitting the say_goodbye_path by _, assigns the first part as the controller name, the second part as the action name. But, afterwards, I generated the following scaffold:
rails generate scaffold User name:string mail:string
And I noticed in the index.html.erb view, that it had methods like: edit_user_path(user). I tried to rewrite it to user_edit_path(user), but of course, it didn't work. My question is, why are the scaffold links the other way around? How would I know if I should write them in the way the author uses them in link_to, or in the way they are generated by the scaffold. Can you shed some light on this?
The helper functions like user_edit_path are automatically generated by rails to map operations on resources to the matching routes and thus HTTP paths and HTTP verbs. You have to understand that you are dealing with resources here, not necessarily with simple controllers.
While most of the time your resources can map to a single controller, it doesn't have to be that way. You can have nested or combined resources which can result in rather complex routing definitions.
Resources are typically defined by giving it a name (userin this case) and defining some allowed operations on them. Rails encourages to follow the REST pattern there, so you can have shortcuts to have some operations pre-defined. One of them is edit, which by default matches to a GET request to users_controller#edit. Default operations on RAILS resources are:
HTTP verb path matching controller action
===================================================
GET /users #=> index
GET /users/1 #=> show
GET /users/new #=> new
GET /users/1/edit #=> edit
PUT /users/1 #=> update
POST /users #=> create
DELETE /users/1 #=> destroy
These mappings can be customized on your routes.rb (changing methods, adding or removing operations, ...) Generally you are encouraged to use the default mappings as these are supported by standard helpers and make your app easier to understand.
Scaffolds are code generated by a template which is not related to the routing.
Routing is based on the route.rb in your config folder. All resources are routed by default (when generated by scaffolds) but there's a default rule /:controller/:action/:id that you can enable. Think of a "catch all" case.
One way to see what routes to have is to edit route.rb and run rake routes and see how they change. There's an official guide here too: http://guides.rubyonrails.org/routing.html

Resources