Creating links using link_to for hierarchical model - ruby-on-rails

Okay, so I am new to Rails and am creating a project management system with the framework. For a couple of my models, views, and controllers I used scaffolding and had no problems. For other parts, I coded all of the parts myself.
So as an overview of my project, at the root of it all you can have many projects. Within all of these projects you can create multiple to-do lists. Within each to-do list, you can have multiple tasks. This is what I meant by "hierarchical" in the title.
I just created my lists page, and when I go to the URL directly in my browser (ex: http://localhost:3000/projects/3/lists/20/tasks/1) the task is displayed correctly. However, I do not know how to format my link that is in one of my to-do list views (the tasks are usually shown below the to-do list but now I want them to show on their own view).
Here is the code I currently have:
<%= link_to "#{task.description}", project_list_tasks_url(#list.id,task.id) %>
I know that the link_to "#{task.description}" is correct because I tried using it with a static URL (Google or something), but project_list_tasks_url(#list.id,task.id) is where I'm having trouble.
Can anybody help me out? I can provide as much code as you'd like from my to-do list or task controllers and views.

A couple of tips to help make routing less confusing. It can be a bit unnerving to get used to.
Routing Rule #1
Always check the output of rake routes to be sure how to call your various routing methods. You might think you know how your routes will play out by looking at routes.rb but you won't know until you look at the compiled routes table.
In your case you're expecting a route with the format:
/projects/:project_id/lists/:list_id/tasks/:id
Be sure that's the case. If it is, your call should look like:
project_list_task_path(#project, #list, task)
Note that the arguments here are :project_id, :list_id and :id, so all three are required in this case. Any in brackets in the path specification can be ignored, like :format usually is.
Routing Rule #2
Use the _path methods unless you strictly require the full URL. They're shorter and the output is easier to read and debug. They also don't inadvertently flip the URL in the browser and cause session problems if you don't properly differentiate between www.mysite.com and site.com.
Routing Rule #3
Don't forget there's a huge difference between #project and #project.id when it's supplied to a routing path method.
The router will always call the to_param method if it's available and this can be over-ridden in your model to produce pretty or friendly URLs. id is for your database and your database alone. to_param is for routing but you shouldn't be calling it manually unless you're doing something exceptionally irregular.

You generally should not nest resources more than one level deep, but putting that aside, the link_to format should be:
link_to task.description, project_list_task_path(#project, #list, task)
i.e. project_link_tasks_url should be project_link_task_url, and you have to pass the #project as the first argument (I'm assuming that your project is named #project). I've switched _url to _path so you can just pass the objects themselves as arguments rather than their ids.
See the documentation on creating paths and URLs from objects for details.

Related

link_to 'new' action in a different controller?

I want to have a link at the bottom of my show.html.erb that links to the new action in a different controller.
class Sample < ActiveRecord::Base
belongs_to :song
end
class Song < ActiveRecord::Base
has_many :samples
end
So, at the bottom of the show action for songs, I want to link to new for samples. This seems pretty easy, but I'm struggling to figure this out. I would also like to pass the id from the song to the form as :song_id
Fiddy, because you're new, let me explain how this works...
Routes
Your problem is that you don't understand the Rails routing structure - I'll hopefully explain it for you.
Rails, since it's an MVC framework, builds a series of "routes" for you. These "routes" are stored in the file available at config/routes.rb.
Routes, as described by the Rails documentation are as follows:
The Rails router recognizes URLs and dispatches them to a controller's
action. It can also generate paths and URLs, avoiding the need to
hardcode strings in your views.
The most important thing you should consider here is the way the routes generate paths for you. These paths are simply Rails "helper" methods, which you can call from your views. The reason these exist is two-fold -
They provide you with a DRY (don't repeat yourself) way of accessing / manipulating data
They are constructed around objects, helping maintain the object-orientated nature of Rails
These will likely mean nothing to you. However, what you need to realize that if set up your routes correctly, it seriously helps your app's infrastructure immensely.
--
Rails
This leads us quite nicely onto appreciating the way in which Rails works
Rails is an MVC (model view controller) framework. This might seem somewhat trivial, but in reality, it's one of the most important aspects to learn about Rails development, and here's why:
The Rails software system works by taking "requests" (user input) and then routing them to specific controller#actions. Those controllers then build model data from the database, and will translate that into either variables or objects, which you can use in your view.
The reason I mention this is that this type of development takes a lot of getting used-to, in that your program's flow is not about logic / functionality, but the accessibility of data. Therefore, when you ask about the routes or other parts of your app, you need to firstly remember what data you wish to show, and also how you want that data to be shown - this will give you the ability to construct & use the routes / controller actions which will get it to work properly
--
Fix
In terms of what you're saying, the way you'd go about achieving the result you want will be to use a nested route:
#config/routes.rb
resources :songs do
resources :samples #-> domain.com/songs/:song_id/samples/new
end
This will create a new route for you (which you can check by firing rake routes in your rails c (console). This will give you a path to use for your samples#new action:
#app/views/songs/show.html.erb
<%= link_to #song.name, new_song_sample_path(#song) %>
The above link will take you to the samples#show action, which you'll be able to populate with as much data as you require from the samples controller. The important thing to note is this action will have params[:song_id] available for you to either build an object from, or otherwise
<%= link_to "New Sample", new_sample_path(:song_id => #song_id) %>
Where #song_id is the variable that has that id in it.
Set paths in link_to tag which you can get by running rake_routes in terminal.
Ex
link_to "New song", new_sample_path(#song)
In the example given above #song is the instance variable of your current page.
You can also get some idea from here:
link_to Base URL Randomly Changed
Song Model:
accepts_nested_attributes_for :sample, allow_destroy: true
Route:
resources :songs do
resources :samples
end
Song's Show file:
<%= link_to "New Sample", new_song_sample_path(#song) %>
in url it will be:
/songs/:song_id/sample/new
Try this and let me know it works or not... I hope this helps you

What is the "_path" method in ruby on rails?

I'm learning RoR, and I'm getting very confused by the "_path" method as it's used in Controllers and Routes. To be more specific, I'm referring to the many different calls that take the syntax "(something)_path". So far as I know, they all seem to either encode or manipulate a URL or link. I'm having a hard time mastering the use of this type of method because I can't figure out what it's core functionality is supposed to be.
For example, I could use the following code to redirect an old URL structure to a page of listed Tweet instances in my config/routes.rb file:
get '/all' => 'tweets#index', as: 'all_tweets'
Only now can I use the following in an .erb file. Notice the "_path" code at the end of the line.
<%= link_to "All Tweets", all_tweets_path %>
I could also use the following code to create a link to an edit page (and another action) in a different .erb file:
<p><%= link_to tweet.user.name, edit_tweet_path(#tweet) %></p>
I've tried reading through my study materials as well as the RoR documentation, but I always end up more lost than when I began. Does anybody know the low-level definition of this "_path" method?
Helper
It's called a route helper, which means that Rails will generate them to help provide you with resource-based routing structures. I'll explain more in a second
--
To explain properly - Rails is just a framework.
Like all software, it is a series of files loaded in a particular order. As such, Rails creates a series of helper methods in the booting process. These "helper" methods can then be used throughout your application to call functionality / information as you require:
The Rails framework provides a large number of helpers for working
with assets, dates, forms, numbers and model objects, to name a few.
These helpers are available to all templates by default.
In addition to using the standard template helpers provided, creating
custom helpers to extract complicated logic or reusable functionality
is strongly encouraged. By default, each controller will include all
helpers. These helpers are only accessible on the controller through
.helpers
The route helpers (which are generated from your config/routes.rb file give you the ability to call routes which are resourceful. These might seem strange to begin with, but once you understand them, will help you inexorably.
--
Resourceful
To give you more clarity - Rails routes are known as resourceful
This means they are constructed around resources. To give you a brief definition of this, you need to appreciate that the resources of your application are the pools of data you can add to, and pull
from.
To explain further, because Rails is object orientated. If you're new, this won't mean very much, but keep it in mind, as when you progress through the language / work, you'll begin to see why this is important.
Object orientated programming puts OBJECTS at the center of the flow. Typically, you'd put logic at the center, but with OOP, it's the objects. This is very important for us, as it means that everything you do in Rails is based around the objects you can create.
As per the MVC principle (which, again, is what Rails is built on), you'll create / invoke your objects from your Models:
This means that if you want to create a series of routes to "CRUD" (Create Read Update Destroy) your objects, Rails is able to create the routes necessary to do that. This is where the resources directives come from inside the routes file:
Hope this helps!
Actually, these paths are generated based on your routes.rb. If you run this command at your project, you would be able to see all available on your app
rake routes
For example, if I declare my resources in routes.rb like this
resources :posts
then I would automatically have following available paths
posts_path
post_path
new_post_path
edit_post_path
If you use some strange abc_path which has not been declared in routes.rb, then you will get errors.
Hope this is helpful, you will definitely need to work more with Rails and then eventually you will understand all of these things :)
you could find definition for these methods in rails repository:
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route_set.rb#L127

Rails routes. How does the process explicitly work?

I've created a few apps using Ruby on Rails now, but there's a few concepts that I haven't quite been able to get my head around.
One of these is how does the 'routing' process work? By that I mean, where a user enters a URL string and Rails serves the relevant assets in response to the URL.
Here's what I think is happening:
A user browses to the server using their browser:
http://0.0.0.0:3000
They then prepend their URL with a string:
http://0.0.0.0:3000/entries/view_all
Rails' 'routes.rb' file specifies what the string should actually relate to via directives:
match "/entries/view_all" => "entries#view_all"
This above directive says that when the string "/entries/view_all" is prepended to a URL, execute the method view_all, found in the file 'entries_controller.rb'.
The view_all method is executed:
def view_all
#entries = Entry.all(:order => 'created_at DESC')
end
It assigns all the entries from the table 'Entry' to the constant #entries, in descending order.
Rails then magically knows to serve the user the 'view_all.html.erb'.
An each loop inside the 'view_all.html.erb' displays relevant information from the 'Entry' table:
<% #entries.each do |entry| %>
<h1><%= entry.title %></h1>
<p><%= entry.content %></p>
<p><em>Posted at <%= entry.created_at %></em></p>
<% end %>
My questions are as follows:
How wrong is my concept of how things work?
In step #3 above, how does Rails actually know the view_all method is found inside 'entries_controller.rb'? The directive was entries#view_all, not entries#view_all. Does Rails automatically match the start of the controller file names inside the 'controllers' directory, and ignore the '_controller.rb'?
In step #6 above, how does Rails 'magically' know to serve the 'view_all.html.erb' view? Is it similar to how I think it works in question #2? Does Rails match the 'view_all' part of the file name with the name of the method found in 'entries_controller.rb'?
How is the object/constant #entries, and all its methods, "passed on" to 'view_all.html.erb' from 'entries_controller.rb'?
In response to (2) and (3) - Rails emphasizes convention over configuration, which results in seemingly magical coupling between the router, controllers, and view templates. For example, the router knows that entries refers to an EntriesController class because there's a line in ActionDispatch::Routing::RouteSet:
def controller_reference(controller_param)
controller_name = "#{controller_param.camelize}Controller"
...
It's not magic - the "Controller" word is hard coded. It's just what Rails was programmed to expect given your inputs. And it's like this all over the place, which can be a bit daunting to comprehend when you're starting out with it (have a look at Ember.js for even more daunting magic like this).
In response to (4): Rails copies your individual instance variables into an ActionView instance. There is fairly significant contention in the community over whether it should really be doing this, but for now that's how it works, and you should keep that in mind when you are writing your controller actions. You don't want a lot of overhead in copying numerous or bloated objects that you don't need in the view.
You got it, actually. It's all name matched. The Entry model matches up with the entries_controller which matches up with the entries view. The name of the specific view correlates with the controllers action.
#entries is an instance variable (called such as it's an instance of the whole model), as is any variable that starts with an #. Those variables in the controller are the ones that are available to the corresponding view.
A neat trick to use is the _enrty partial.
_entry.html.erb
<h1><%= entry.title %></h1>
<p><%= entry.content %></p>
<p><em>Posted at <%= entry.created_at %></em></p>
Then in places that you want to call the index (your view_all is typically labeled as index), or parts there of, you can <%= render #entries %>
But yeah, a lot of the magic of rails is in the matchy matchy naming conventions. There are ways around that, as everything is customizable, but that sums it up. Cheers!
1) How wrong is my concept of how things work?
Thats a pretty good way of thinking about how the process works
2) In step (3), how does rails actually know the 'view_all' method is found inside 'entries_controller.rb'? The directive was 'entries#view_all', not 'entries#view_all'. Does rails automatically match the start of the controller file names inside the 'controllers' directory, and ignore the '_controller.rb'?
Rails abides by a directive called "Convention over configuration", this means Rails will behave a certain way, as long as you give it instructions in the way it expects. So in your query above, because you specify the "entries" part of your controller it knows to go look in "entries_controller" for the "view_all" action
3) In step (6) how does rails 'magically' know to serve the 'view_all.html.erb' view? Is it similar to how I think it works in question 2? Does rails match the 'view_all' part of the file name with the name of the method found in 'entries_controller.rb'?
Convention over configuration aka "magic". Once Rails executes an action, it will look for the matching template to execute based on your request. if you made a request for an json page (by altering your request headers for example), it would look for view.json.erb. If you left out that template it would throw an error unless at the end of the action you used a render call to tell it to do something else
4) How is the object/constant '#entries', and all its methods, 'passed on' to 'view_all.html.erb' from 'entries_controller.rb'?
It just is :D, or are you asking to see the rails source code that is responsible for that?
ActionView (Views) and ActionController(Controllers) both inherit from Actionpack, so I imagine its not that hard to share variables between the two.

Show action in polymorphic associations

I have a polymorphic resource Discussion that can belong to either the Project, Task, or Subtask.
At one point I wish to reroute to show action of discussions controller. To do that, I need discussion id (which I get from params) and I need to know what the parent is (which I get from params also).
So, to route to show action, I'd have to have 3 cases:
project_discusison_path(#project, #discussion)
task_discussion_path(#task, #discussion)
subtask_discussion_path(#subtask, #discussion)
How to write this 3 cases in one path helper? (looking something like below)
parent_discussion_path(#parent, #discussion)
Remember, I can find and have all the variables. Only writing the path is the problem.
If you're generating a link in a--for instance--link_to, you can use this array syntax:
link_to "Show Discussion", [#parent, #discussion]
Under the hood, this achieves the same thing as the polymorphic_path and polymorphic_url methods:
polymorphic_url([#parent, #discussion])

How to use Rails named route helpers with parameters?

given this route
match 'posts/hello/:name/:title' => 'post#show', :as => :hello
what are the ways that I can call hello_path ?
if i call hello_path(#post), what does it try to do?
I was hoping that the :name and :title files will bind to the path automatically but it seems that rails only know how to get the :id out of the model object.
instead, it only works if I call it like
<%= link_to "link2", hello_url(:name=> #post.name, :title=>#post.title) %>
(lack of proper documentation is really killing me)
To answer your two questions:
At the command line, runrake routes to see what routes there are in
your app. It will show you all the ways you can use the named routes,
just add "_path" or "_url" to the name of the route which is shown on
the left.
Calling hello_path(#post) will generate a URL to the
show page for that hello instance.
The way you are calling it is the norm:
<%= link_to "link2", hello_url(:name=> #post.name, :title=>#post.title) %>
However, this may work too:
<%= link_to "link2", hello_url(#post.name, #post.title) %>
Here is some documentation (other than the Rails API) which should help.
http://guides.rubyonrails.org/routing.html
To answer your question of "what does hello_path try to do?"
hello_path knows how many parameters it's supposed to get. This is from counting the named parameters in config/routes. It will accept either a hash or a list of arguments. If you give it a hash, the keys must match the names of the URL parameters. If you give it a list of arguments, it'll just match them up by position - the first argument with the first named parameter.
Then, it will call to_param on each parameter individually before joining them all together (see code here, 4.0 branch).
If you pass in an object when it's expecting 2 or more params, it won't even get around to calling to_param on the object. That's when you get errors with no stack trace that say something like
No route matches {:controller=>"posts", :action=>"show", :id=>#<Post ...>}
Working with 1 named parameter
If you've only got one named parameter, things are pretty straightforward. If you need to look up your posts by name instead of id, you can just redefine to_param
class Post < ActiveRecord::Base
...
def to_param
name
end
end
Working with multiple named parameters
But if the URL has more than one named parameter in it, then redefining to_param isn't enough. Let's say you tried this:
# app/models/post.rb
class Post < ActiveRecord::Base
...
def to_param
{name: name, title: title}
end
end
# app/views/posts/index.html.erb
<%= post_path(post) %>
In this case, you'll get a routing error because you're not passing in enough arguments to post_path (see above). To get around this, I just call to_param explicitly:
# app/views/posts/index.html.erb
<%= post_path(post.to_param) %>
This is a little less slick than most Rails routing magic, but works perfectly well. If you later change the way you're looking up Posts, all you have to do is redefine to_param. No need to worry about all the places you've called post_path
Under the hood
The relevant code to look at is actionpack/lib/action_dispatch/routing
The other answers (at the time of this writing) are fine, but your reply to GregT's answer shows a lack of understand about Rails, which is fine -- we've all been there.
Specifically, three of the key principles behind Rails: convention over configuration, the model-view-controller architecture (MVC), and REST. It's stuff at the beginning of every beginning Rails book. Beginners often think they can just jump to the first chapter with code, but Rails differs from many other topics in that the first chapters explain important concepts and aren't just intro chapter filler. Because Rails isn't "code", it's a "framework of code".
"Convention over configuration" means if you follow certain conventions then you benefit from behaviors baked into Rails. Routing is one of those areas, if not the biggest, where convention benefits the developer although it is entirely configurable.
Paths following a specific routing format, are parsed into the controller, action and possibly an id, format, and additional parameters. By default and at minimum, a Rails (and also a Sinatra) path takes the following format and order:
/controller_name/action_name
It's a little more complicated than that, with more options, and it looks more like this in actuality:
/controller_name/action_name/(id)(.format)(?param=value)(&...)
...but it's more detail than is necessary for this answer.
The controller is the C in MVC, or the class that handles the request. The action is one of the seven RESTful actions (index, show, new, create, edit, update, and destroy) within that controller. Not all actions require an id (index, new and create) and not all of them are get requests (requests that generate a view, for instance destroy, create and update don't have views).
Putting it all together we see this example:
/articles/edit/1
...will route the request to the 'edit' action in the ArticlesController controller passing along id 1. It's expected that the controller will do some housekeeping, like authentication and authorization, then retrieve Article model (MCV) with ID 1 and pass it along to the "edit" view (MCV).
It's best, especially for new Rails developers, to stick to these conventions and perhaps add a few additional actions beyond those provided by REST.
You can step outside this convention by configuring in your routes.rb file an alternative routing scheme, not following REST, etc., but you'll be like a salmon swimming upstream -- it's a lot harder than going with the flow. If you go down that path (pun) you'll make a lot of additional work for yourself, probably reinvent the wheel somewhat, and lose advantages provided by the Rails framework. If you find yourself in that situation for whatever reason, perhaps Rails isn't the right tool for your job.
You can also call it like this
hello_path(#post.name, #post.title)
Hope it helps.

Resources