Rails 3.x How to properly define inflections - ruby-on-rails

I have a need to define an inflection for the word 'chassis' where the same word defines both singular and plural and I am really struggling with this.
I thought I was there with the initialize/inflections.rb definition
ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
inflect.uncountable(/.*chassis.*/i)
end
Taking note of the example inflect.uncountable %w( fish sheep ) I tried to use inflect.uncountable %w( chassis ) when I first set up the scaffolding but that didn't work at all well as it didn't take into account leading parts in paths and caused problems with relationships and other tables like car_chassis and chassis_lookup.
Having looked at various solutions provided as answers to similar questions in Stack Overflow I eventually came up with inflect.uncountable(/.*chassis.*/i) Which seemed to take care of scaffolding but I'm having an issue with routes where <%= link_to "Chassis", admin_chassis_url%> gives me a no route for the show action error.
ActionController::RoutingError - No route matches {:action=>"show", :controller=>"admin/chassis"}
Which makes sense as I want the index action so I'm not passing an object to the path but Rails is obviously thinking I am requesting the show action
The other examples for regex's
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
are just complete gobbledygook to me and learning regular expressions needs a lifetime of learning that I neither have the inclination or the sanity to get my head round and the rails documentation http://api.rubyonrails.org/classes/ActiveSupport/Inflector/Inflections.html on inflections is quite frankly pathetic.
I obviously haven't got the inflection right. Can anyone give me a complete solution as to exactly how I should define an inflection for the word "chassis" for me and for others please as none of the previous answers I've found provide a complete and proper solution

Your inflection seem to be correct.
Check what 'rake routes' tell you. In my case it was smart enough to detect that plural and single form of chassis was the same, so it generated admin_chassis_index instead of just admin_chassis for the #index action. Probably, the same is true for you. This is what I did:
In config/routes.rb
namespace :admin do
resources :chassis
end
Running 'rake routes' gives (note the first path):
admin_chassis_index GET /admin/chassis(.:format) admin/chassis#index
POST /admin/chassis(.:format) admin/chassis#create
new_admin_chassis GET /admin/chassis/new(.:format) admin/chassis#new
edit_admin_chassis GET /admin/chassis/:id/edit(.:format) admin/chassis#edit
admin_chassis GET /admin/chassis/:id(.:format) admin/chassis#show
PUT /admin/chassis/:id(.:format) admin/chassis#update
DELETE /admin/chassis/:id(.:format) admin/chassis#destroy
So, for #index I'd need to call:
<%= link_to "Chassis", admin_chassis_index_url%>

Related

Rails routes: Wrong singular for resources

I have the following line in my routes.rb (Rails 4.1.4):
resources :request_caches
However, when I run rake routes I get the following output:
request_caches GET /request_caches(.:format) request_caches#index
POST /request_caches(.:format) request_caches#create
new_request_cach GET /request_caches/new(.:format) request_caches#new
edit_request_cach GET /request_caches/:id/edit(.:format) request_caches#edit
request_cach GET /request_caches/:id(.:format) request_caches#show
PATCH /request_caches/:id(.:format) request_caches#update
PUT /request_caches/:id(.:format) request_caches#update
DELETE /request_caches/:id(.:format) request_caches#destroy
As you can see, Rails somehow maps request_caches plural to request_cach singular. But it should be request_cache. Is this some kind of special case, because of the word caches? I've also played around with
resources :request_caches, as: :request_cache
But this results in wrong routes like request_cache_index. And furthermore, I think this is a standard task and should be solved clearly using Rails intern route helpers.
So, what am I doing wrong?
Rails guesses. It's not perfect. In config/initializers/inflections.rb add
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.irregular 'request_cache', 'request_caches'
end
You'll need to restart the server as it's in an initializer.
Have a look at config/initializers/inflections.rb. There should be some examples in the comments.
Something like this should do the trick:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.singular 'request_caches' 'request_cache'
end
Be sure to restart the server after making changes to an initializer.
As I said,you can achieve it by changing config/initializers/inflections.rb like below
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.irregular 'request_cache', 'request_caches'
end

Rails module names with acronym inflections

Looks like inflections don't work for module names with the nesting level more than one.
If you have the following in your config/initializers/inflections.rb:
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym 'VCloud'
end
Then when you create a directory under app/, say app/services/vcloud/ you will get two modules:
Vcloud #=> Vcloud
VCloud #=> VCloud
But if you create a directory with a higher nesting level, say app/services/vmware/vcloud/ you will get only one module:
Vmware::Vcloud #=> Vmware::Vcloud
Vmware::VCloud #=> NameError: uninitialized constant Vmware::VCloud
Is this a bug?
I would go with this is a bug. You can go around it with (within initializers):
module ActiveSupport::Inflector
def underscore_with_acronym_fix(string)
words = string.split('::')
return words.map(&method(:underscore)).join('/') unless words.one?
underscore_without_acronym_fix(string)
end
alias_method_chain :underscore, :acronym_fix
end
I'll make a pull request to fix this, however will need slightly more time to confirm it will not break anything. There are quite a lot of cases here.
I wonder if i could replicate this "issue".
Tried this running rails console.
> ActiveSupport::Inflector.camelize 'vcloud'
=> "Vcloud"
> ActiveSupport::Inflector.camelize 'v_cloud'
=> "VCloud"
There are test cases for all sorts of combinations below.
http://api.rubyonrails.org/classes/ActiveSupport/Inflector/Inflections.html#method-i-acronym
https://github.com/rails/rails/blob/master/activesupport/test/inflector_test_cases.rb

Rails3 Controllers Plural / Singular Routes

I have some issues with a controller in my rails3 application that is called nas
My ruby app is connected to an existing DB so the table name has to stay as nas.
In my models, I have previously been able to do this:
set_table_name
But I don't know how to do this in my controller / routes.
Right now, my routes contains this:
resources :nas
And the output is:
new_na GET /nas/new(.:format) {:action=>"new", :controller=>"nas"}
edit_na GET /nas/:id/edit(.:format) {:action=>"edit", :controller=>"nas"}
na GET /nas/:id(.:format) {:action=>"show", :controller=>"nas"}
PUT /nas/:id(.:format) {:action=>"update", :controller=>"nas"}
DELETE /nas/:id(.:format) {:action=>"destroy", :controller=>"nas"}
As you can see, rails drops the 's'
How can I resolve this?
Thanks
It's pretty confusing because I have no idea what a "na" or "nas" is. From your question I have the idea that you always want to refer to it as "nas", both plural and singular.
If that's the case, then the answer is to put this in config/initializers/inflections.rb:
ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable "nas"
end
This will also make your Nas model use the nas table by default, so no need for set_table_name.
However note that there is no reason to use Nas for your controllers if you don't want to! You can name them anything you like, as long as this is reflected in routes.rb and you use the correct model in your controller.
My ruby app is connected to an existing DB so the table name has to stay as nas.
Then why do your routes/controllers also have to be named nas? Once you fixed it on your model-level everything should be fine.
# model.rb
class WhateverILikeToCallMyModel
set_table_name "nas"
end
# controller.rb
class WaynesController << ApplicationController
# ...
def index
#items = WhateverILikeToCallMyModel.all
end
end
# routes.rb
resources :waynes
A guess, maybe you should try overriding the naming convention, because 'nas' is not plural? (assuming that's why the s dropped)
# Inflection rule
Inflector.inflections do |inflect|
inflect.irregular 'nas', 'nases'
end
in environment.rb
Edit: Instead of environment.rb use: config/initializers/inflections.rb (thanks Benoit Garret)
In your routes.rb, try,
match '/nas', :to => 'na'

Routes in Rails with controllers that end in 's'

This is just cosmetic but still driving me nuts. I've created a controller for my Address object and try to lay out routes for it. However, Rails seems to interpret the last 's' as plural and removes it from my paths, like this:
routes.rb:
resources :address
(note: this line is inside a namespace block called 'admin')
When I run rake routes I get this:
new_admin_addres
edit_admin_addres
... and so on. How do I get the extra 's' in my paths?
resources :addresses
which is the plural of address
Use the inflections to set address to be uncountable:
config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable %w( address )
end
This should now ignore any extra 's'. Not gramatically correct but should solve the issue.

Moving the :format attribute in routing from the end to beginning of route

In my application, I am trying to get my API to mimic GitHub's in how it has the (.:format) in the beginning of the route rather than appending it optionally at the end.
Here is my code that is "working" but can be ignored:
map.namespace :api do |api|
api.namespace :v1 do |v1|
v1.resource :company, :path_prefix => "api/v1/:format"
end
end
I can go to /api/v1/xml/company.json and it Rails will provide json as the params[:format] rather than xml.
When I run rake routes I am getting
/api/v1/:format/company(.:format)
Is there a way to get it to return:
/api/v1/:format/company
Thanks in advance!
This is going to require some serious monkeypatching. Also routing is one of the most complex areas of the Rails codebase, both connecting incoming HTTP requests to your code and generating URLS.
Forgive me if I'm being presumptuous, but as far as I can ascertain, your reason for going against Rails convention is to mimic another company. In other words you're willing to disregard the collective wisdom of Rails contributors in favour of following a decision made by a handful of developers.
I think you should ask yourself is your reason for wanting this compelling enough to be commensurate with the effort required?
The harder it is do something other than the Rails way, the more rigorously one should question their decision. The presence of significant hurdles is usually indicative that there is a better way of doing something.
EmFi is right. I didn't answer the question merely expressed my opinion.
Put the following code into an initializer file within the config initializers directory inside your Rails app. What you name the file is does not matter to the framework as all files in this directory are in the load path. I suggest that you call it actioncontroller_resource_monkeypatch.rb in order to make the intent clear.
ActionController::Resources.module_eval do
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
if resource.has_action?(action)
action_options = action_options_for(action, resource, method, resource_options)
formatted_route_path = route_path.match(/\/:format\//) ? route_path : "#{route_path}.:format"
if route_name && #set.named_routes[route_name.to_sym].nil?
map.named_route(route_name, formatted_route_path, action_options)
else
map.connect(formatted_route_path, action_options)
end
end
end
end
My answer uses the same method as EmFi's i.e. by monkeypatching ActionController::Resources#map_resource_routes. I decided to throw my hat into the ring because it did not offer a full implementation, that was left as an exercise for yourself. I also feel that a ternary assignment of formatted_route_path is much cleaner and more concise than an if-else/unless-else block. One additional line of code instead of five! That's the least I can do for a 200 bounty!
Now run rake routes
new_api_v1_company GET /api/v1/:format/company/new {:action=>"new", :controller=>"api/v1/companies"}
edit_api_v1_company GET /api/v1/:format/company/edit {:action=>"edit", :controller=>"api/v1/companies"}
api_v1_company GET /api/v1/:format/company {:action=>"show", :controller=>"api/v1/companies"}
PUT /api/v1/:format/company {:action=>"update", :controller=>"api/v1/companies"}
DELETE /api/v1/:format/company {:action=>"destroy", :controller=>"api/v1/companies"}
POST /api/v1/:format/company {:action=>"create", :controller=>"api/v1/companies"}
TADA!
I believe the (.format) is optional (that's what the parenthesis mean), so /api/v1/:format/company(.:format) == /api/v1/:format/company
If you want to change it any more than that, you'll need to hack/monkey patch rails.
I'm just taking a shot here (I'll check and update tomorrow when I can play with the code), but couldn't you avoid using :format and do something like this?
routes:
map.connect ':controller/:return_type/:action/:id'
controller:
#results = MyObject.all
case :return_type
when 'xml' render :text => #results.to_xml
when 'json' render :text => #results.to_json
end
If the case is too messy/ugly, you could easily create a helper method that mimics the behavior you're looking for.
Steve Graham says everything that needs to be said, but a question with 200 rep bounty deserves a proper answer. No matter how ill advised. Besides this does seem like it could be useful.
The monkey patch is surprisingly simple. You just need to override ActionController::Resource#map_resource_routes as follows.
def map_resource_routes(map, resource, action, route_path,
route_name = nil, method = nil, resource_options = {} )
if resource.has_action?(action)
action_options = action_options_for(action, resource, method, resource_options)
unless route_path.match(/\/:format\//) # new line of code
formatted_route_path = "#{route_path}.:format"
else # new line of code
formatted_route_path = route_path # new line of code
end # new line of code
if route_name && #set.named_routes[route_name.to_sym].nil?
map.named_route(route_name, formatted_route_path, action_options)
else
map.connect(formatted_route_path, action_options)
end
end
end
Incorporating this patch into your code involves either patching your rails gem or making a plugin, both of which are pretty simple and left as an exercise for the reader.
The code works by skipping the line that adds the option format to all routes if it the path already contains a parameter named format.
Edit: Linked to the Steve Graham answer that this solution refers to. There was only one at the original time of posting.
I used this code:
ActionController::Routing::Routes.draw do |map|
map.namespace(:v1, :path_prefix => "api/v1/:format") do |v1|
v1.resources :repositories
end
end
URLs become api/v1/[json/xml/whatever]/<restful url goes here>
I like the idea of namespacing the versions too (like in your question):
class V1::RepositoriesController < V1::ApplicationController
end
Whilst this method allows you to put the format in the URL twice: It is not up to you to ensure that users do not put the format in the URL twice, it is up to your users to ensure that they do not put the format in the URL twice.
Don't spend time solving PEBKACs.

Resources