Programmatically get list of all controllers in a given namespace - ruby-on-rails

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

Related

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

How are Model, View and Controller connected?

I understand the different roles the Model, View and Controller play in an application but how are they linked to each other, for example a simple comparison is when linking a JavaScript file with an HTML file a tag is required with a 'src' reference.
Since learning Rails I have been told they are linked, but not how.
How are the Model, View and Controller actually linked and why is a 'src' or something similar not required with MVC and specifically Rails?
Basically it's all linked through routes controllers and actions.
The routes file (routes.rb file in your_app_root/config) effectively intercepts an incoming url and maps that url to a controller/action (actually that's done by a web server like Nginx which then passes the request on to your rails app via something like Unicorn but that's another question entirely).
So for a standard HTML site set up you might have a folder called home and in that folder you might have an index.html
So when you navigate to some_url/home/index then you will get the contents of the index.html file rendered in the browser.
In RoR for this to work you will need a route defining a get request for a collection (plural or no params passed in)
It could look something like this
YourApp::Application.routes.draw do
get 'home', to: 'home#index', as: :home
# etc...
That route would connect your browser to the index action on the home controller if you navigate to some_url/home
The index action in the home controller can just be an empty action
class HomeController < ApplicationController
def index
end
end
And providing you have an index.html.erb in the app/views/home folder then you will get that file rendered automatically as ActionController translates whatever you have told it to render in to HTML, css and javascript or json or XML if you are using resource routes and the request it receives is an XML or JSON request, and sends the resulting data back to the browser
If you wanted to get some data displayed from your database then the controller action has the responsibility of obtaining that data and stuffing it into an object (an instance variable which is denoted by an # symbol) that your view can use in an erb tag
e.g.
class HomeController < ApplicationController
def index
#some_records = SomeModel.all
end
end
This can then be used in the index.html.erb file like so
<ul>
<% #some_records.each do |rec| %>
<li> A record: <%=rec.some_method%> </li>
<% end %>
</ul>
Because you have a route you can use the routes name as a path for links and buttons to post data from the browser back to the server where the whole process starts all over again.
That's not a strict description of exactly how it all hangs together but it shou close enough to give you an idea of how it all happens
Model–view–controller is a software design pattern that organizes Rails applications. From the viewpoint of a software architect, it is an abstraction that simply provides a way to organize code. You can see the MVC design pattern in the hierarchy of classes in the Rails API, notably ActionController (controller), ActionView (view), and ActiveRecord (model), which are objects that can be subclassed to be used as components of your web application. You can also see the MVC design pattern reflected in the file structure of a Rails application, where you will find folders for models, controllers, and views. You won't see the actual code that wires together models, controllers, and views unless you dig deep into the Rails source code.
For a deeply technical introduction to the Rack middleware that ties together models, controllers, and views, you can read the RailsGuide: Rails on Rack which describes the Action Dispatcher.
The Action Dispatcher does the work of integrating the model, controller, and view objects. When you follow the Rails convention of organizing code according to the MVC design pattern, Rails does all the work of "linking" the code for you. You never actually see the code that "links" the MVC pieces together.
For a more complete explanation of MVC (and everything else that is Rails), see my in-depth article:
What is Ruby on Rails?
To understand Rails, you need to look at it from several perspectives, including software architecture.

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.

Creating links using link_to for hierarchical model

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.

How to handle cases in which the generated content depends on the current user authorization?

I am using Ruby on Rails 3.2.2 and, since my system implementation, I would like to generate different outputs (in views) and / or to retrieve different records (in controllers) depending on the "access-er" user authorization (for instance, the authorization could depend on if the "access-ed" user is or not is the current "access-er" user).
How can I handle the situation? That is, for example in order to handle if the user is or not the current user and so to display different content, should I implement two view files and / or controller actions for each case, or should I use if else statements directly in the view file and / or in the controller action?
A cleaner approach I would suggest is to create different roles for users and group them. So for that particular group you can have a separate view files and controllers. One advantage of this approach is it will be very easy to read the code and we can easily understand the code. We get more control on the page easily without having to worry about other users. We can even avoid the need for many filters. But if there is only two type of users then it could be managed easily with if else statement, so choosing the right method will depends on the problem too.
I usually use if else statements right in the views, and render partials if there is a lot of markup between the statements to keep things clean.
That is a style choice though so if you are finding you are making massive conditionals on every page, it could be that you need to rethink your organization in the controller or even make a new resource. For simple things like adding an 'edit' or 'delete' button if the user is an admin, I would use conditionals in the view.
For .html.erb markup, you can do normal if/else blocks like this:
<% if <condition> %>
<%= render 'partial_name' %>
<% else %>
<p>Some other content</p>
<% end %>
Where condition is your condition, like for example current_user? #user

Resources