Rails rename model name in url dynamically - ruby-on-rails

In Rails, I have a model named item. Each item belongs_to category (another model), which has an attribute called name.
When seeking any item, I see the url as /item/:id
instead, I would like the url to show up as /'item.category.name'/:id
e.g. if the item belongs to category whose name is "footwear", then, I would like to see the url as /footwear/:id. And if there is another item that belongs to category whose name is "clothing", then I would like to see the URL as /clothing/:id
Is this possible?

If you setup a route like
# config/routes.rb
RouteTest::Application.routes.draw do
get ':category_name(/:id)', to: 'items#show', constraints: {id: /\d+/}
end
It will map to the show action in your ItemsController, and in there, you will have access to params[:category_name] and params[:id]. With this information, you should be able to get the data you want and render it.
Note that this route however will likely have the undesirable effect of masking any routes that follow. You could use rails advanced route constraints to further narrow down 'which values would be considered valid category_names' but this wouldn't be a very scalable or manageable approach.
For example, you could do something like
RouteTest::Application.routes.draw do
get ':brand_name(/:id)', to: 'items#show', constraints: lambda { |request| BrandList.include?(request.params[:brand_name]) }
# etc. ...
get ':category_name(/:id)', to: 'items#show'
end
but this only really works well when the BrandList is a finite list that you could setup during application initialization.
A better, more scalable approach might be to design your URLs like
/brand/adidas
/brand/teva
/shoes/1
/shoes/2
/jackets/45
IOW, prefix known namespaces like brand with an appropriate human friendly URL prefix and use category based route as a catch-all at the bottom.
Hope this helps.

Related

How to get an organized search URL result with slashes orders (/)?

I want a search section on the "index" from books_controller with some filter options from different authors, categories and other attributes. For example, I can search for a category "romance" and max pages = 200. The problem is that I'm getting this (with pg_search gem)
http://localhost:3000/books?utf8=%E2%9C%93&query%5Btitle%5D=et&button=
but I want this:
http://localhost:3000/books/[category_name]/[author]/[max_pages]/[other_options]
In order that if I want to disable the "max_pages" from the same form, I will get this clean url:
http://localhost:3000/books/[category_name]/[author]/[other_options]
It'll work like a block that I can add and remove.
What is the method I should use to get it?
Obs: this website, for example, has this kind of behavior on the url.
Thank you all.
You can make a route for your desired format and order. Path parameters are included in the params passed to the controller like URL parameters.
get "books/:category_name/:author/:max_pages/:other_options", to: "books#search"
class BooksController < ApplicationController
def search
params[:category_name] # etc.
end
end
If other options is anything including slashes, you can use globbing.
get "books/:category_name/:author/:max_pages/*other"
"/books/history/farias/100/example/other"
params[:other]# "example/other"
So that gets you the basic form, now for the other you showed it could just be another path since the parameter count changed.
get "books/:category_name/:author/*other_options", to: "books#search"
params[:max_pages] # nil
If you have multiple paths with the same number of parameters, you can add constraints to separate them.
get "books/:category_name/:author/:max_pages/*other", constraints: {max_pages: /\d+/}
get "books/:category_name/:author/*other"
The Rails guide has some furth information, from "Segment Contraints" and "Advanced Constraints": http://guides.rubyonrails.org/routing.html#segment-constraints
If the format you have in mind does not reasonably fit into the provided routing, you could also just glob the entire URL and parse it however you wish.
get "books/*search"
search_components = params[:search].split "/"
#...decide what you want each component to mean to build a query
Remember that Rails matches the first possible route, so you need to put your more specific ones (e.g. with :max_pages and a constraint) first else it might fall through (e.g. and match the *other).

Ruby on Rails page control

I'm sorry, my English is bad. Question: I have model Pages with columns title, description etc. I can create, change, destroy these pages. I can see the contents of the link mydomain/pages/1. I need for each page has been template and route, so I can see the content on the link, for example maydomain/contacts. How to do it? Help me please.
One way to implement your own solution is to add this to your routes file:
get '/mydomain/:slug, to: 'pages#show'
This is a pretty general matcher, so add it to the bottom of your routes so it doesn't override others.
Then your controller show action will look something like:
def show
#page = Page.find_by_slug(params[:slug])
end
This of course assumes you have a slug column on your Pages table.
I'm assuming by "mydomain" you mean the root url of your site (e.g. myapp.example.com)
I'd suggest that you separate the problem into two parts:
Use an attribute other than id to identify an item in the url
Reduce the route so that the controller does not need to be specified.
For 1, have a look at this: Rails routes with :name instead of :id url parameters Note, that as #spickermann suggests friendly_id could be a good solution for you.
For 2, you will need to create a route without the controller name, and then specify the controller in the route definition. (See the Rails Routing Guide):
get ':param', to: :show, controller: 'pages'
For that to work, you will need to put it after (lower in routes.rb) so that it doesn't intefer with other routes. I'd also recommend adding a constraint to the route - to limit the wrong urls that could be routed to that rout.

Self nested infinite routes

Is there a way to self nest a resource in the routes infinetelly?
Suppose the following scenario: I want to create several pages that may be self nested one to another, like a products page has many product pages and each product page has several sub-pages etc.
The resource would be a page in a tree structure , like with the awesome nested set gem.
In case an admin can create pages at unknown depth level how would I make the routes?
The above example has to produce a url like /:friendly_id_of_level_1/:friendly_id_of_level_2/.../:friendly_id_of_level_n
I've tried going with dynamic routes but it has many drawbacks this way.
Any suggestions?
I do a similar thing to this in one of my apps with this route:
map.connect "/c/*modules", :controller => "content", :action => "show"
(Note this is using rails2 routing syntax, you might need to update it).
This will resolve this url
/c/123-foo/456-bar/789-baz/653-qux
to the content#show action, with params set like
params = {"modules"=>[123-foo", "456-bar", "789-baz", "653-qux"]}
The modules are in a tree structure, so i can use the sequence of module ids in params[:modules] to make a breadcrumb chain and any other heirarchical data, and i use the last one in the array as the "current" one to actually show to the user.
Note: I put the "/c/" at the start of the url to separate these nested routes out from all my other routes: otherwise it's very greedy and will match pretty much any url on your site. That's not a problem if you want to always have it as your "catchall" route down at the bottom of your routes file, but if not then you'll need to add something to make it distinct. Obviously this doesn't need to be "/c/", you could have anything which will stop it colliding with your other routes.

Routing without the model name

Using this question and railscast 63 I've got my articles routed to articles/article_permalink.
I'd like them to be accessible without the model name in the url so my-domain.com/article_permalink routes directly to the article. I'd only want this to happen on the show action. Is this possible?
I think you need something like ...
(in routes.rb)
match '/:id' => 'articles#show', :via => 'get'
(needs to be last, or towards the end of the routes as it can match requests intended for other routes)
To change the article_path(...) helpers, "as" might help: http://guides.rubyonrails.org/routing.html#overriding-the-named-helpers
Or you can add a helper for that specific path.
If I understand your question, you want the model tied to the route "articles/article_permalink" to be dynamic based upon which is article is selected from a list?
Would you be open to appending a model ID to the end of the URL as a query string? A more complicated approach would be to have your links POST, with the model ID as a hidden input field. Your controller could determine if it was accessed via get/post, and handle it accordingly, but that doesn't feel right.
Regardless, when the controller action is fired up based upon a request to "articles/article_permalink", it has to know which model to fetch. With HTTP being stateless, something has to be passed in. You could get fancy and write JavaScript to fire one AJAX call, set a session var, and then fire the GET, but that's messy.
I hope I understood the question...

Rails Controller - getting the index action to display only certain records

By default the rails controller will load all associated objects in the index action. What I would like to do is display only certain objects.
For example
I have a model called Car(id, make, model, year). I want list only particular makes in the index, depending on a parameter.
There are a few ways to do this, I'm just not sure which is best.
I could:
pass a parameter to the link:
cars_path(make: 'Acura')
and would give me /cars/?make=Acura
set up routes: (this seems to get messy)
match "cars/:make" => "cars#index", constraints: {make: /[A-z]{1,20}/}
or I could make a separate controller action for this
Any suggestion about what is the most "rails-y" way to do this? RoR 3.1
Usually, when we are talking of filtering data, I prefer to keep the same index action and filtering parameters via plain old GET vars (no extra route definitions) url?key=val&key-val.
This has a number of benefits among them:
url is bookmark-able
no session tinkering
I can reuse the filtering params and pass them to pagination links and such to have the filter follow the user while search is in order
I prefer not to make extra routes as the complexity of the filter can easily go too high. If the filter params are few and you are sure of what you are doing, you may define extra nice routes url/param/param but I find that those cases are few to none.
If you just want to display the cars of one make, the best url imo would be: /makes/1-Acura/cars. So you would just get the cars of this make in the cars controller.
Do you have a table for makes or is it just a string in your car table? I think you should have one.
resources :makes do
resources :cars
end
With these routes, you would have to test if there is a params[:make_id] in the index action of the cars controller, and if it's the case you would get the cars like that:
#cars = Make.find(params[:make_id]).cars
Or you could set up your routes like that
resources :makes do
scope :module => "make_scope" do
resources :cars
end
end
This way, you can have your controllers setup like that:
controllers
- cars_controller.rb
- make_scope (folder)
- cars_controller.rb
The path make_cars_path(#make) would hit the index action in the make_scope/cars_controller, so you would not have to worry about the presence of a params[:make_id], you would just know you're working with the cars of a make.
Otherwise, the get params are fine. I don't think it's bad to define a new route to get prettier urls though, depending on the complexity of your filters.

Resources