Basic Rails 3 Routing Question - ruby-on-rails

I am trying to make some clean URLs in a Rails3 application I am working on... but I am having a hard time understanding how to (or if I even should) customise my routes to make this work.
Here is the example:
I have a list of Stores. Each store is in a category (health, sports etc). Each store has a location.
I have 2 ways I'd like to present the data. One display is a list of all the stores in a directory type structure, the other is on a map.
Ideally I'd like my URLs to work something like this:
/stores/health/map (or /stores/map/health) to show just the health stores on a map (where essentially the map parameter is effecting which view is displayed, but still using the Index controller... which using a collection in my route doesn't seem to suit)
The other URL I'd like is /stores/sports/ to show just the sports stores in a directory view (the default) for example...
I am not entirely clear how I can manipulate the routes to handle this...
Here is my current Route which isn't really doing it for me:
resources :stores do
collection do
get 'map'
end
end
On top of that, I'd like to be able to add filters without using ?query=params... so:
/stores/sports/hockey , would essentially filter out only hockey stores...
I have no issues doing this with ?query, it's just putting my params into a nicer URL that I'm trying to achieve.
The documentation does not seem to outline what I am trying to do, so Im assuming what Im trying to do is wrong.
Is this breaking REST? Am I looking at it all backwards?
Thanks for your help, JD

You might be overthinking this. :-)
If you want to route HTTP Get of 'stores/health/map' to the StoresController with an action name of, say, health_map, what you need to do is:
get 'stores/health/map' => 'stores#health_map'
Anything that is a clean URL and doesn't modify data and uses HTTP GET is RESTful. (And that is coming from a co-author of a book on REST). It is when you wish to modify data that you need to be more careful on how you use methods.
To do filtering, try something like:
get '/stores/sports/:filter' => 'stores#sports'
The value of the filter will come into your method as params[:filter]

Related

How to create a custom route for show action in rails 5?

I have a model 'Item'. It all works fine, however am not completely satisfied with its show path. I want to use certain parameters from items table to construct a more SEO friendly url. So here is my question..
How can I change my Show action url
from
'mysite.com/items/1'
to
'mysite.com/items/item-name/item-city/item-id' where item-name, item-city, and item-id are dynamic for each specific item.
Is it possible to achieve this without a gem? if yes, how? If no, which gem would you suggest to achieve this in simplest way?
Thanks
One approach to this problem is to use route globbing:
http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
You should be able to do something like this:
get 'items/*names/:id', to: 'items#show', as: :item_names_path
And put whatever you want in *names. This would take a little experimentation to get it right. I might add a method to item to create the names array.
def names
[city.name, name].uniq.compact
end
Then I believe you would call item_names_path(#item.names, #item)
You can do something relatively simple and stays true to Rails by adding a to_param method to your model like so:
def to_param
"#{id}-#{name}-#{city.name}"
end
What this does is every time you use a method like the item_path, it will use the #item.to_param method (this is what it does now, and returns :id). Generating the normal route, but replacing the :id param with the SEO friendly one.
And, on the other end, when you go to find(params[:id]) in the controller in your show, edit, delete, or update actions, it will to a to_i on it and turn it back into an id. This is what it does now, but to_i on an int is still an int.
Your urls will look something like
/items/56-boston-itemname
The other benefit to this, if you happen to change the item name or the city name, it will change all the urls appropriately, but old urls that were sent in email will still work.

Search action in ReSTful rails model

So, I'm quite new to Rails and still working my way through the principles. I suppose like most people, I have started creating that basic CRUD. Okay. Done.
Now I want a new action: search. As it turns out, it is not one of the 7 rest sacred (!) actions (if got it right). While I know I could implement new custom actions and resource it and everything, I read in a few places to try my best to stick to the standard ones as long as possible. Okay. What would be the correct way?
Again a few sources like this guy suggest thinking of my scenarios in therms of nouns, case in which seems I'd need a "search" controller...? It just doesn't convince me that I'd have to create a whole class whereas I'd normally do def search just to keep it ResTful.
What did I get wrong? What would be the common solution here?
thanks.
REST is a concept, not a religion :-). But the core verbs are GET/POST/PUT/DELETE which map to their associated HTTP verbs. What's in the URL is typically a reflection of this, and (this is more the Rails philosophy) following convention can make everything much easier. The URLs you get with generic rails (e.g. scaffold) are not particularly ideal in several ways, but they work, and you can change them.
So, yeah, for search (assuming it's starting simple, e.g. finding records in a single model, say Product) then you could do a GET with a query string like this
def search
#results = Product.where("name ILIKE ?", params[:query])
...
end
Which would result in a URL like /product/search?query="foo" -- nothin' wrong with that.
It depends if your search is against ONE resource or many resources. For example if you have a ProductsController and you want to implement a search feature only for your products, you could create a collection action called "search" (the url would be /products/search)
If your search is for many resources, I'd create a SearchesController with a singleton resource :search in my routes file.
Then again, when you implement search functionality in your application, don't put all the logic in your controller but create models classes to handle your search. You can even create an abstract class to map to your search form and thus avoid using '*_tag' fields to create your search form.
See : https://github.com/slainer68/basic_active_model
If you want to adhere to REST (which is a guideline really, it has pros and cons), then the slideshare you link to is recommending the right way to do things.
So, for example if you have a comments_controller, and you want to be able to search comments, you could create a comments_search_controller. The search form would be at comments_search_controller#new, which would POST to comments_search_controller#create.
Yes, you are creating another class doing it this way, but that's not much different than creating another action in the comments_controller, and it does keep things consistent and separated. You wouldn't need a new CommentSearch model or anything, just that controller, which asks your Comment model for the relevant search results.

multiple views in a rails page

I have a very high level question that I cant find an answer to that makes sense to me. I understand it''s a terribly broad question but I'm only after some pointers in where to look for answers, not instructions on how to build my site.
So... If I want to render two different types of content in a single page using rails, how would I go about doing this? And how would I format the url? Say I create a gallery model and controller which has information about the gallery and perhaps a description, then I create a gallery-entry controller and model that belongs to the gallery which has an image and image name. If I want to create a url something like www.siteURL/galleryName/GalleryEntry that renders both the gallery info and description and all the associated gallery-entries but also a larger version of the gallery-entry that is named in the url where would i start and how would i structure this? How would i go about creating a url that has multiple attributes and how would i access them in the controller/view?
Thanks - and sorry for the vague question
There's several ways to go about it.
Your URL looks like a "vanity" URL that would exist in addition a normal RESTful route (galleries/:gallery_id/entries/:entry_id). The difference is that you don't want to show just the gallery entry.
If you want to specifically differentiate between different views of the same resource there are a number of ways it could be done, the two I'd consider first are adding another action, or adding a disambiguating query parameter. In this case, it's a hybrid, so I'd probably create a custom match and controller method.
The mapping might look like:
match ':galleryName/:entryName' => 'gallery#highlight_entry' # Or whatever
The action would be (more or less):
def highlight_entry
#gallery = Gallery.find_by_name(...)
#entries = #gallery.entries
#highlighted_entry = # pull from #entries, or retrieve
# Also, filter entries/etc. so the highlighted one doesn't show up
# etc
end

Non-CRUD Controller Actions

This might seem like a n00b question, but I am trying to break some of my bad practice that I may have adopted using MVC, so I hope you can help me out
So, imagine I want to do something like "Upload CSV And Parse It", it doesn't seem obvious to me to fit it into the CRUD pattern... I am not interacting with the DB, so i don't need add or update or delete, but I still want to be able to use the action in a meaningful way from different views. Thus, it is "ok" to just an action called "UploadCSV" and have it be accessible via a URL such as "/data/uploadcsv"
Your thoughts are much appreciated!
Tom
It sounds like you are talking about RESTful ideas (having actions called index, create, new, edit, update, destroy, show).
In MVC you can call an action largely whatever you want (so yes, you can call it uploadcsv if you want). If you want it fit RESTful principles you might want to think about what the action is doing (for example is a data upload essentially a create or an update function) and name it using one of the RESTful action names.
I believe I have the same point of view as you.
In my projects I try to be as restful as possible whenever I can. However as you said sometimes a special case just does not 'fit'
After all it is also a question of 'feeling'
If you provide a csv import function, I see it as perfectly correct to not create a full REST implementation for CSV.
Let's imagine in your application you have clients. And you wnat to give the option for clients to import data using csv. You can add a route for this action using:
map.resources :clients, :member => { :uploadcsv => :get }
The route is properly declared, Your 'clients' resource is completely restful and you have an additional action properly declared to manage data importation.
The only warning I have is: don't use a route like this one "/data/uploadcsv". From my point of view It lacks clarity. I like to be able to understand what my application is going to do just be looking at the url. And '/data' is too vague for me :)
The persistence of the resource is not crucial here. I suppose that what you are doing here is this - creating some kind of resource (although not persistent) out of the csv provided. The thing here is to think about what this csv file represents. What's inside? Is it something that will become a collection of resources in your system, or is it a representation of only one object in your system? If you think about it it has to be something concrete. Can you be more specific about your problem domain?

Why do I need to work harder to make my Rails application fit into a RESTful architecture?

I started a Rails project recently and decided to use RESTful controllers. I created controllers for my key entities (such as Country) and added index, new, edit, create, show, update and delete. I added my map.resources :country to my routes file and life was good.
After development progressed a little, I started to encounter problems. I sometimes needed extra actions in my controller. First there was the search action that returned the options for my fancy autocompleting search box. Then came the need to display the countries in two different ways in different places in the application (the data displayed was different too, so it wasn't just two views) - I added the index_full action. Then I wanted to show a country by name in the URL, not by id so I added the show_by_name action.
What do you do when you need actions beyond the standard index, new, edit, create, show, update, delete in a RESTful controller in Rails? Do I need to add (and maintain) manual routes in the routes.rb file (which is a pain), do they go in a different controller, do I become unRESTful or am I missing something fundamental?
I guess I am asking, do I need to work harder and add actions into my routes.rb file for the privilege of being RESTful? If I wasn't using map.resources to add the REST goodies, the standard :controller/:action, :controller/:action/:id routes would handle pretty much everything automatically.
I would treat search as a special case of index. Both actions return a collection of resources. The request parameters should specify things like page, limit, sort order, and search query.
For example:
/resources/index # normal index
/resources/index?query=foo # search for 'foo'
And in resources_controller:
before_filter :do_some_preprocessing_on_parameters
def index
#resources = Resource.find_by_param(#preprocessed_params)
end
As for index_full and search_by_name, you might look at splitting your current controller into two. There's a smell about what you've described.
Having said that, you're absolutely right that there's no point in forcing your app to user restful routes when it doesn't deliver anything over /:controller/:action/:id. To make the decision, look how frequently you're using the restful resource route helpers in forms and links. If you're not using them, I wouldn't bother with it.
If I go beyond the standard CRUD actions with my models, I normally just add the methods as required. Searching is something I add to many controllers, but not every one, so I add it and maintain the routes normally:
map.resources :events, :collection => { :search => :get }
Moving these actions to an entirely separate controller might keep some of your controllers RESTful, but I find that keeping them in context is far more useful.
REST does not specify that you can't have additional views. No real world application is going to be able use only the supplied actions; this is why you can add your own actions.
REST is about being able to make stateless calls to the server. Your search action is stateless each time as the data so far is supplied back, correct? Your alternate display action is also stateless, just a different view.
As to if they should be manual routes or a new controller, that depends on how distinct the activity is. Your alternate view, if it provides a full set of CRUD (create, read, update, delete) operations would do well to be in a new controller. If you only have an alternate view to the data, I would just add an alternate view action.
In other words, it doesn't sound like your application is failing to be RESTful, it is more an issue of realizing that the automatically generated feature set is a starting point, not a conclusion.
In my opinion they may have gone a bit off the rails here. What happened to DRY?
I'm just getting back into Rails not having done much development with it since beta and I'm still waiting for the light-bulb to come on here. I'm still giving it a chance but if it hasn't happened for me by the end of my current project I'll probably just drop-back to the old standard routes and define the methods as I actually need them for the next one.
I won't go on to explain more about REST since I think that has been answered in this question, however I will talk a little bit about the default route.
My main problem with the default route is that if you have multiple sites using the same Rails app it can look horrible.
For example there may be controllers that you don't want people to be able to see on one app:
http://example1.somesite.com/example_2/foo/bar/1
compare this to
/:controller/:action/:id
This would go to the controller example_2/foo, action bar and id 1
I consider this to be the main flaw of Rails' default route and this is something that RESTful routes (with subdomain extensions) or only named routes (map.connect 'foo' ... ) can fix.
To remain RESTful in your design, you need to rethink what you call a resource.
In your example a show action for a search controller, (search resource) is the direction to remain restful.
In mine, I have a dashboard controller (show) and controllers for single fields of in-place ecditors (show and update)

Resources