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.
Related
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
Our Rails 3.2 application has admin panel. Admin panel is divided into modules. For instance there is Admin::Searching. I want to refactor some repetitive and hardcoded partials. For instance:
<ul class='searching_nav'>
<% %w(tests fuzzy stats terms hits).each do |tab| %>
<li class="<%= css_nav(tab) %>"><%= link_to tab.humanize, [:admin, :searching, tab] %></li>
<% end %>
</ul>
It is searching nav section, after adding another controller you have to change it manually.
What I want is, getting list of all controllers in Admin::Searching (controllers in this namespace are subclasses of Admin::AdminController). I believe it is possible to do it in elegant way from Rails api. Then it can be generalized for all modules tabs.
I have tried checking Admin::AdminController.subclasses, but classes in that array are lazy loaded so after hitting Terms for example, after server restart, there is only one element [Admin::Searching::TermsController], and after visiting other modules there are also other controllers. I could iterate over them and base on controller_path results, filter only admin/search controllers. Maybe it is possible from the routes end? I mean, could I get all controllers in given namespace, from Rails.application.routes.routes?
I do not want to get to filesystem. I know that I could scan admin/searching directory with Dir but it is inefficient.
I had a whole nice answer describing the use of Module#constants and Module#const_get but then I read through to the latter part of your question and realized that this technique probably won't work for the same reason that Admin::AdminController.subclasses doesn`t—and that is because Rails lazily loads the controller classes as needed.
In your case, I would probably just end up doing a directory scan. You think it's inefficient, but Rails and lots of Ruby gems end up going to the file system anyway when doing dynamic loading and stuff.
Now, you want to find all (relevant) controllers before they're loaded anyway. Couple this with the insight that in production, the number of controllers doesn't change.
What I'm trying to say is, go ahead and do a Dir scan, but do it in an initializer (a file under config/initializers) so it only gets executed once, at the start of your app.
So, conceivably,
# config/initializers/admin.rb
module Admin
CONTROLLERS = begin
# Do a directory scan of app/controllers/admin looking for relevant files
end
end
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.
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.
I am using the "in_place_editing" plugin for rails to render a form with in-place edits. Thing work fine as long as the default template is chosen by rails (no 'render' method is invoked inside the controller), but they break down when I try to render a partial using "render :partial => 'partial_name'" call. Is this a known issue (in_place_edit does not work with partials?) or am I missing something? I am getting the following error while rendering the partial:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
.../vendor/plugins/in_place_editing/lib/in_place_macros_helper.rb:74:in `in_place_editor_field'
You don't provide anywhere near enough information in your question, giving only two lines of the backtrace and no fragments of the view which does work, or the partial which does not. This means that any attempts to answer you must be based largely on guesswork. That said, the in-place editor helper is just a helper method like any other, nothing special. You can call it from just about any view component. It is highly likely that the way in which that view is included by the controller, or indeed a parent view, is not the reason it is failing.
The helper method is complaining about a nil value. This means that most likely, your partial is invoking in_place_editor_field and passing it values which are not defined in the partial. Check to make sure it isn't using local variables which are not defined, compared to those used in the view where your in_place_editor_field call works; check to make sure that it isn't asking for different instance variables too. In all probability you'll find the views which work are using one variable name while the partial you've tried to render is using another.
The render :partial => ... mechanism supports different ways of explicitly passing in values to the partial; you may choose to use these to clarify your code. See the :locals and :object options for the "Rendering partials" section of the render documentation in the Rails API at:
http://api.rubyonrails.org/classes/ActionController/Base.html#M000658
I am working on a maintenance project which is in rails 2.3.8. And this issue ate a lot of my time
In the view, Change the view to have an instance variable:
#batch = batch
in_place_editor_field :batch, 'priority'