How to access model-specific constant in routes.rb - ruby-on-rails

I'd like to generate some dynamic routes based on a constant that's stored in one of my models, but I'm getting an error indicating that I don't have access to that constant from the router.
Here's my router snippet:
MyShowroom::CATEGORIES.each do |category|
match "#{category}", :controller => :my_showrooms, :action => :index, :type => category, :as => category
end
Here's the error I'm getting:
NameError: uninitialized constant MyShowroom::CATEGORIES
So basically, I just need to know the proper way to access a model constant in routes.rb
Any help would be much appreciated. Thanks!

Arup gave me the answer I needed to figure out how to implement this (thanks Arup!), but I just wanted to give my own answer to show how this was fully implemented to hopefully help others out.
I left my enum constant defined in my model, because I want to keep things very logically organized (i.e. the enum is directly related to the model so that's where it belongs). To make the constant available across the application, I created a constants.rb file in app/config/initializers and then I created a new constant in that file that simply pointed to the constant I already had defined in my model - effectively making the constant that's defined within my model available across the application.
Here's the full implementation:
my_showroom.rb (Model)
CATEGORIES = {
1 => 'Opinions Please',
2 => 'Budget Minded',
3 => 'Getting Started',
4 => 'Ever Evolving',
5 => 'Done for Now',
6 => 'All Out Assault',
7 => 'Home Theater',
8 => 'Secondary Rigs'
}
constants.rb
MY_SHOWROOM_CATEGORIES = MyShowroom::CATEGORIES
routes.rb
MY_SHOWROOM_CATEGORIES.each do |key, value|
action_name = value.tr(' ', '_').downcase
get "my_showrooms/#{action_name}", to: "my_showrooms##{action_name}"
end
Generated Routes

You can create a file called constants.rb inside the app/config/initializers/ directory. And then you are done to use them anywhere.Then put the module, class or any constants, inside constants that you want to use any places inside your app.

Related

Rails Routing can't read constraints when it has more than a word

I have here a scope and constraints for the Rails routing
scope constraints: { category: /#{ CATEGORY_NAMES }/,
location: /#{ LOCATION_NAMES }/,
id: /[0-9]+/ } do
get ':category/:label-:location-:id', to: 'categories#show', as: :my_page
end
the :label can be any parameterized string (e.g. need-something-here)
while the :location must be any matched from the LOCATION_NAMES
LOCATION_NAMES = "california|new-york|japan|china"
I have a problem for location with 2 words like New York / new-york
The routing keeps getting new as included on the :label constraint.
Parameters: {"category"=>"music", "title"=>"need-something-here-new", "location"=>"york", "id"=>"111"}
and only york is considered as the location...
I'm new to Rails and I'm not really that familiar with the routing yet. Hope you could help me.
I've tried... adding a title: /.*(?=\.)/ to constraints but it's not working... the issue is still there.
It seems like your routes.rb is initialized prior to the initializer, where you have defined your constants in.
Try to manually require your own initializer with defined constants in the routes file.
require Rails.root.join('config', 'initializers', 'constants.rb')
The original quetion/answer was found here:
Can routes.rb Access Initializers' code?

Can routes.rb Access Initializers' code?

I've been trying to write a routing expression that would only forward a request to a specific controller (call it sample_controller) if the URL suffix (namely, www.example.com/suffix) corresponds to a hash key in one of my initializers. Otherwise, I want Rails to route the request to a different controller (say, the one root refers to). For example, given a hash RELEVANT_HASH = {"a" => "yada", "b" => "bla"}, www.example.com/a would route to sample_controller, while www.example.com/c would not, loading root's route instead.
I've been trying to do that by putting
match '/:page_name' => 'sample_controller#action', :as => :something, :constraints => lambda { |r| MyInitializer::RELEVANT_HASH.has_key?(r.params[:page_name]) }
in my routes.rb file. However, MyInitializer isn't available from inside routes.rb's do/end block since it is in the context of ActionDispatch::Routing::Mapper, causing Rails to return a 'uninitialized constant MyInitializer' error.
Is there a workaround that would make MyInitializer accessible from routes.rb (say, referring to it from application.rb)? Would placing my code outside the do/end block solve the scoping issue? Or is there an alternative way to achieve my goal without exposing MyInitializer to routes.rb?
Is there a workaround that would make MyInitializer accessible from routes.rb?
Yes, there is. Add this line to routes.rb:
require Rails.root.join('config', 'initializers', 'my_initializer.rb')

Implicit creation of helpers - routes.rb and 'match' statements

I am reading Obie Fernandez' "The Rails 3 Way", and there is a bit of it that I am not sure I understand correctly. I am new to rails, and want to make sure I understand it correctly. I have some experience with vanilla Ruby. Not much, but some.
The text in question is as follows: (regarding routing and the config/routes.rb file)
"...
By creating a route like
match 'auctions/:id' => "auction#show", :as => 'auction'
you gain the ability to use nice helper methods in situations like
link_to item.description, auction_path(item.auction)
..."
My question is, specifically what part of match 'auctions/:id' => "auction#show", :as => 'auction' creates the helper functions? (such as link_to auction and auction_path() ) Is it the :as => 'auction' part? Would any helpers be created without appending :as => 'auction'?
My confusion stems from other guides I have seen where this is omitted, and yet helpers seem to be created regardless. What specifically does rails use in match statements in the routes.rb file to create helpers? If it isn't the :as => 'auction' part, then what is the specific purpose of appending this to the match statement?
I know this seems like a super basic question, but this detail seems to get glossed over in the texts I have read thus far. Thanks in advance for any light you can shed on this.
I just tried this:
match "alfa/beta", to: 'users#new'
In this case, even without an :as => 'named_route', I got for free the following helper
alfa_beta_path
which, as expected, points to users#new.
So, it seems that helpers are also automagically generated by parsing the route's string, in case there is no :as specification.
Yes, it is the :as => 'named_route' part that creates the named route (which in turn creates the helpers). As for leaving it off, are you referring to instances of resources :something in routes.rb? The resources method generates a set of URL helpers based on the name of the resource automagically.

Help with rails routes

I seriously cant understand why this is so hard... I have some experience with other mvc frameworks but always heard rails was the easiest to code in.... right now I cant even get to my controller methods if i want to.
I used scaffold to creat 'student' which automatically created for me the controller, model and views for basic CRUD.. but now I just want to add a method "helloworld" to my controller and when i go to
http://localhost:3000/students/helloworld
I get a
Couldn't find Student with ID=helloworld
error.
what am I missing?.. I know its got to do with routes and the REST thing but I still cant figure out then how else am I supposed to use my own methods... do I have to edit my routes.rb file everytime I create a new method?.. please help
Routes for models in Rails are divided into 2 groups. Ones that act on a single objects (think edit, update, delete) and ones that don't have a single object to act on (new, index). If you want to create your own method that doesn't take an object ID you need to add a route config for that method in your routes file. The methods are either member or collection methods. Member methods URLs look like /model/id/method_name. Collection methods look like what you want (/model/method_name). Here is an example for your students model (routes.rb)
map.resources :students, :member => {:some_member_function_example => :get },
:collection => { :helloworld => :get }
Note: You can just remove the :member => ... from the config and only have collection if you have no member methods to define.
Link /students/foo will not call the foo method of the students_controller. That's because REST mappings in Rails includes /:controller/:id route for GET. And your link matches this pattern.
In order to override that path (for methods with no parameters, like yours) use the following snippet:
map.resources :students, :collection => {:method_name => :get}

Rails - RESTful Routing - Add a POST for Member i.e(tips/6)

I'm trying to create some nice RESTful structure for my app in rails but now I'm stuck on a conception that unfortunately I'm not sure if its correct, but if someone could help me on this it would be very well appreciated.
If noticed that for RESTful routes we have (the uncommented ones)
collection
:index => 'GET'
:create => 'POST'
#:? => 'PUT'
#:? => 'DELETE'
member
:show => 'GET'
#:? => 'POST'
:update => 'PUT'
:destroy => 'DELETE'
in this case I'm only talking about base level action or the ones that occur directly inside i.e http://domain.com/screename/tips or http://domain.com/screename/tips/16
but at the same time I notice that there's no POST possibility for the members, anybody knows why?
What if I'm trying to create a self contained item that clones itself with another onwer?
I'm almost sure that this would be nicely generated by a POST method inside the member action, but unfortunately it looks like that there's no default methods on the map.resources on rails for this.
I tried something using :member, or :new but it doesn't work like this
map.resources :tips, :path_prefix => ':user', :member => {:add => :post}
so this would be accessed inside http://domain.com/screename/tips/16/add and not http://domain.com/screename/tips/16.
So how would it be possible to create a "default" POST method for the member in a RESTful route?
I was thinking that maybe this isn't in there because it's not part of REST declaration, but as a quick search over it I found:
POST
for collections :
Create a new entry in the collection where the ID is assigned automatically by the collection. The ID created is usually included as part of the data returned by this operation.
for members :
Treats the addressed member as a collection in its own right and creates a new subordinate of it.
So this concept still the same if you think about the DELETE method or PUT for the collection. What if I want to delete all the collection instead just one member? or even replace them(PUT)?
So how could I create this specific methods that seems to be missing on map.resources?
That's it, I hope its easy to understand.
Cheers
The reason they aren't included by is that they're dangerous unless until secured. Member POST not so much as collection PUT/DELETE. The missing member POST is more a case of being made redundant by the default collection POST action.
If you still really want to add these extra default actions, the only way you're going to be able to do it, is it to rewrite bits of ActionController::Resources.
However this is not hard to do. Really you only need to rewrite two methods. Even then you don't have to rewrite them fully. The methods bits that you'll need to add to those methods don't really on complex processing of the arguments to achieve your goal. So you can get by with a simple pair of alias_method_chain.
Assuming I haven't made any errors, including the following will create your extra default routes as described below. But do so at your own risk.
module ActionController
module Resources
def map_member_actions_with_extra_restfulness(map, resource)
map_member_actions_without_extra_restfulness(map, resource)
route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
map_resource_routes(map, resource, :clone, resource.member_path, route_path, :post, :force_id => true)
end
alias_method_chain :map_member_actions, :extra_restfulness
def map_default_collection_actions_with_extra_restfullness(map, resource)
map_default_collection_actions_without_extra_restfullness(map,resource
index_route_name = "#{resource.name_prefix}#{resource.plural}"
if resource.uncountable?
index_route_name << "_index"
end
map_resource_routes(map, resource, :rip, resource.path, index_route_name, :put)
map_resource_routes(map, resource, :genocide, resource.path, index_route_name, :delete)
end
alias_method_chain :map_default_collection_actions, :extra_restfulness
end
end
You'll have to mess around with generators to ensure that script/generate resource x will create meaningful methods for these new actions.
Now that we've covered the practical part, lets talk about the theory. Part of the problem is coming up with words to describe the missing actions:
The member action described for POST in the question, although technically correct does not hold up when applied to ActionController and the underlying ActiveRecord. At best it is ambiguous, at, worst it's not possible. It makes sense for resources with a recursive nature (like trees,) or resources that have many of a different kind of resource. however this second case is ambiguous and already covered by Rails. Instead I chose clone for the collection POST. It made the most sense for default post on an existing record. Here are the rest of the default actions I decided on:
collection
:index => 'GET'
:create => 'POST'
:rip => 'PUT'
:genocide => 'DELETE'
member
:show => 'GET'
:clone => 'POST'
:update => 'PUT'
:destroy => 'DELETE'
I chose genocide for collection DELETE because it just sounded right. I chose rip for the collection PUT because that was the term a company I used to work for would describe the act of a customer replacing all of one vendor's gear with another's.
I'm not quite following, but to answer your last question there, you can add collection routes for update_multiple or destroy_multiple if you want to update or delete multiple records, rather than a single record one at a time.
I answered that question earlier today actually, you can find that here.
The reason that there's no POST to a particular member is because that member record already exists in the database, so the only thing you can do to it is GET (look at), PUT (update), or DELETE (destroy). POST is designed only for creating new records.
If you were trying to duplicate an existing member, you would want to GET the original member in a "duplicate" member action and POST to the resource root with its contents.
Please let me know if I'm missing what you're asking.

Resources