First module gives "unknown action"


#1

Hi, I’m using “Agile Web Dev. w/Rails”, which is great but has a minimum
on grouping controllers into modules. I’ve successfully generated a
module scaffold, so I have:

app/controllers/admin_controller.rb
app/controllers/admin/things_controller.rb

(And the rest of the scaffold output, which seems normal.) However,
browsing to http://localhost:3000/admin/things/ gives me “Unknown action
No action responded to things”.

I’m not sure what or how much code I should post to illustrate the
situation, or if this is some sort of configuration issue in
Radrails/InstantRails. Thanks in advance for any help!


#2

I think the problem here has to do with your routes (config/routes.rb).
By default the following route is used to handle incoming requests:

map.connect ‘/:controller/:action/:id’

In your case, your admin_controller.rb perks up its ears because the
first part of the URL (:controller) is ‘admin’, in reference to it. The
controller is now expecting to get and action and optionally and id
passed to it but, your URL implies something a bit different that it is
unaware of. You are trying to reference a controller inside a module of
the same name ‘admin’. At this point your admin_controller.rb throws up
its hands because it doesn’t have a method named ‘things’ and doesn’t
know what to do.

How is this fixed? I am no expert but here is what I would do:

  1. Rename your admin_controller.rb

  2. Create a custom route in routes.rb for your ‘admin’ module

    map.create ‘/admin/:controller/:action/:id’


#3

Jeremy M. wrote:

How is this fixed? I am no expert but here is what I would do:

  1. Rename your admin_controller.rb

  2. Create a custom route in routes.rb for your ‘admin’ module

    map.create ‘/admin/:controller/:action/:id’

Thank you so much for taking the time to reply. I tried suggestion #2
first, since that’s so easy; it didn’t have any effect.

I’m not sure I understand #1
If I rename the top-level controller then my module won’t be inside it
any more, which I thought was the point of modules. As I understand it,
when an incoming request has a controller named “admin/things”, Rails
looks for a controller called “things_controller” in the directory
app/controllers/admin. Doesn’t this also mean that I want to have a
top-level controller called “admin”?

Thanks again,


#4

Thanks again, Jeremy. I appreciate your help with this.

I get what you’re saying about routes, and how Rails interprets
‘/:controller/:action/:id’ by default. The rest of my app is working
well according to those rules.

As it happens, I am using “Agile Web D. w/Rails” –
specifically I’m looking at page 191, which says:

"So far, all our controllers have lived in the app/controllers
directory. It is sometimes convenient to add more structure to this
arrangement. For example, our store might end up with a number of
controllers performing related but disjoint [sic] administration
functions. Rather than pollute the top-level namespace with each of
these, we might choose to group them into a single admin namespace.

Rails does this by using a simple convention. If an incoming request has
a controller named (say) admin/book, Rails will look for a controller
called book_controller in the directory app/controllers/admin. …"

There’s not much more; it sounds like you have a copy to refer to.

So, I apologize if I’m being difficult, but can you help me understand
page 191, versus your explanation of routes here? Thanks again,


#5

So, I apologize if I’m being difficult, but can you help me understand
page 191, versus your explanation of routes here? Thanks again,

Certainly. It appears I have a bit more reading to do to understand what
is being said myself. I’ll try and answer if no one else does in the
mean time. :slight_smile:


#6

If I rename the top-level controller then my module won’t be inside it
any more, which I thought was the point of modules.

By ‘top-level controller’ do you mean
app/controllers/admin_controller.rb? This controller is not in a module.
The only time a controller is in a module is when it is in a
sub-directory of the base controller directory
(app/controllers//some_controller.rb). Conversely, any
controller NOT in a sub-directory is NOT in a module. I hope I am not
confusing it more.

As I understand it, when an incoming request has a controller named
“admin/things”, Rails looks for a controller called “things_controller”
in the directory app/controllers/admin.

By default no, this is not what Rails does. When an incoming request has
a controller named “admin/things” how does rails know which part of the
URL is referencing the ‘controller’ and which part is referencing the
‘action’?

Short answer.

It looks at config/routes.rb.

Long answer.

Remember we are talking about default Rails behavior here, not a
modified route as was suggested in my previous post. Ok, so the default
route that Rails uses is:

map.connect ‘/:controller/:action/:id’

This in basically says, for each incoming request, I understand the URL
to be implying the following:

o The string (if there is one) in the spot labeled ‘controller’ is what
I should use as the controller.
o The string (if there is one) in the spot labeled ‘action’ is what I
should use as the action.
o The string (if there is one) in the spot labeled ‘id’ is what I should
use as the id.

That means, if you pass it a URL that is in the form of ‘admin/things’
it will do the following:

o Assume the controller is ‘admin’ since the string in the controller
spot
is ‘admin’.
o Assume the action is ‘things’ since the string in the action spot is
‘things’.
o Assume there is no id since one is not specified.

At this point, Rails will try to find a controller named
admin_controller.rb in app/controllers/admin_controller.rb and execute
the action (method) ‘things’. However, in your setup, it finds the
controller it is looking for but there is no method named ‘things’. This
is why you get ‘Unknown action No action responded to things’.

Doesn’t this also mean that I want to have a top-level controller
called “admin”?

No.

In fact, you probably don’t even need
app/controllers/admin_controller.rb if I understand what you are trying
to do.

If I were you, I would get the ‘Agile Web D. with Rails’. There
are entire chapters that explain this stuff. It really helped me to
understand it more clearly.

http://www.pragmaticprogrammer.com/titles/rails/index.html


#7

I would say yes, that is the way to program module controllers like
that. I have mine setup up the same way for the admin modules and have
never and any problems with it.

I would say keep going the way you are.

-Nick


#8

hi,
Another idea:
create your module with a controller inside it named ‘BaseController’.

admin/base_controller.rb
class Admin::BaseController < ApplicationController

Then all of the other admin module controllers should be children of
‘BaseController’.

admin/thing_controller.rb
class Admin::ThingController < Admin::BaseController

I think this is the kind of inheritance you were after. All controllers
in the module inherit all the functionality of BaseController.


#9

I did some experimenting, and it seems to work like this. I was trying
to “mix” a top-level controller with a module, like this:

app/controllers/admin_controller.rb
app/controllers/admin/things_controller.rb

…where admin_controller contains some methods like “index”, “login”,
etc. My impression was that for each incoming request, Rails would look
in the top-level controller, not find “things”, and then look for a
module. This is what didn’t work.

After experimenting, it looks like maybe you’re not supposed to “mix”
controllers like that. I removed admin_controller, and moved all of the
methods in it to a second module – so now it looks like this:

app/controllers/admin/login_controller.rb
app/controllers/admin/things_controller.rb

So now these URLs work (with default routes):
http://localhost/admin/login/login
http://localhost/admin/things/list

The remaining issue is that this URL doesn’t work:
http://localhost/admin/

…because there can’t be a top-level controller for that. I am getting
around this by installing a custom route, like so:

map.connect ‘admin/’, :controller => ‘admin/login’, :action => ‘login’

So my app is working in a coherent manner, which is good. I’m not
confident, because this is really not covered in the Agile book. Can
anyone confirm that this is how modules in Rails are supposed to work?