Ruby on Rails custom routes or how to get request.request_url before the controller is initialized - ruby-on-rails

I am trying to do something for hours and I'm stuck with rails routes.
So.. the idea is to have some even more user-friendly urls like for example /Laptops for a category and /Laptops/Apple-MacBook-Air-and-so-on. I should also use such links for simple pages like /MyDummyPage etc.
So my idea was to get the request_url and check if i can find the page myself. But it seems rails is initialising this request class after defining routes and right before calling the controller.
As you can see I am stuck and can't see any possible solution for my problem.
I will be glad if someone can help me.
Thank you in advance.
All the best!

(Whole thing revised)
If you want to allow dynamic matches along with normal restful routes, there are a couple options- (put it at the end of your routes or it will match everything)
match '*raw' => 'dynamic#show'
And in dynamic_controller.rb
def show
parts = params[:raw].split '/'
# do logic here to set all variables used in views
render #resource_or_page
end
You could also use the input in a search function and redirect to the first result of that search. Or return a 404 if there are no results.
def show
results = search_method_here params[:raw].sub('/', ' ')
if results.any?
redirect_to results.first
else
raise ActionController::RoutingError.new 'Not Found'
end
end
Also, for freindlier urls within restful routes, try out this: https://github.com/norman/friendly_id
I think its important to realize that people generally do not manipulate URLs by hand, and its nice to have readable urls, but its more important for them to be clear on what/where they are doing/going.
In response to your comment, I think you are mislead about routing. If you make 2 routes :category and :page, they match the exact same url, except one of them stores it in params[:category] and the other in params[:page]. To differentiate it, you would need to have a different amount of arguments matched like :category/:product or a namespace, or, perhaps, a restful route which specifies the MVC the route routes to.

Related

Rails routes and querystring

I am having troubles trying to define the correct route for a query coming from ember. The request appears like this:
GET "/users?id=1011
No matter what I try I always have the request forwarded to the index action, while it is intended for the show action.
I have tried many things, like
get "/users?id=", to: redirect('/users')
but nothing seems to work.
Can anyone explain to me what can I do and most important the reason behind it?
Thank you very much for your time!
GET /users?id=1011 always goes to index because Rails just sees the route as GET /users. The question mark denotes a parameter, which isn't part of any of your defined routes.
You can modify your index method to interpret the param, something like:
def index
if params[:id]
# call show, do something, whatever
else
# regular index action
end
end
In the case of retrieving just one user, it would be more common to route this to your show action as you originally intended. To accomplish this, do not pass the id as a query param, but instead as a slug segment /users/1011, which you can accomplish by declaring the following in your routes.rb:
get '/users/:id', to: 'users#show'
If you want to go full tilt then you might as well just declare users as a resource like so:
resources :users
Which will automatically wire up your index, show, update, destroy, etc. Throw Active Model Serializers in the mix and you can have Ember Data start talking to your API nearly "out of the box".

Passing a list of same parameters with a different value

So basically I was creating some client methods that would create a path to hit an external service.
Also, I am using addressable gem.
Here is an example:
def get_member(ord_id, member_id)
path = '/organizations/{ord_id}/people/{member_id}'
hash = get(path, member_id: member_id, org_id: ord_id)
{ Member.custom_deserialize_method(hash) }
end
This works if the path is simple as above.
Now I want to write a method for a different path which does a bulk look up like this:
organizations/ab9176be/members/bulk?memberId=8e936891&memberId=b71c4f1e (This is not a web url. Its a service end point)
The above method can have multiple memberId params.
I know addressable has an expand method and ruby has a to_param method.
But I do not not know if that would help in my situation. I would appreciate some help here.
Route Globbing
I'm sure if this will help, but considering you've had no responses, I felt I'd be best posting
I learnt about route globbing a few weeks back. Bascially this allows you to define routes like this:
get 'books/*section/:title', to: 'books#show'
Would match books/some/section/last-words-a-memoir with
params[:section] equals 'some/section', and params[:title] equals
'last-words-a-memoir'.
Although not directly related to the solution for your question, it may help you appreciate how Rails processes some of the routes in the app. Essentially, it means you can add as many "wildcard" routes as you need, with the various elements of the route being compiled in the params hash

Rails route only if resource is present

I want to have a rails route that triggers if the given id is present, but falls back to further route matching if not.
Specifically, I want to have promo codes at the root level of my site. So, you can go to foo.com/save87 or foo.com/xmasspecial, and it'll treat it as a promo code, but if you go to a promo code that's not there (foo.com/badcode), that route will not match and rails will continue down the route list.
In an ideal world, I'd do something like this in my routes.rb file:
get '/:promo_id' => 'promos#show, constraints => lambda { Promo.exists?(promo_id) }
I know that the above code, without the constraints, would work as a catch-all for foo.com/*, and would sorta work if I put it as the last line in the routes file. Unfortunately, that would result in foo.com/badcode a 'promocode not found' error, rather than a normal 'route not found' error.
So, is there a way to accomplish what I'm trying to accomplish? This is in Rails 3, for reference.
Edit: To clarify a bit-
I want a wildcard url as described above so that our promocode urls are short and memorable (foo.com/save87 instead of foo.com/promo_codes/save87)
I'd prefer to have the option of having other routes after this one. I may, at some point, need another wildcard url at the root level- for example, if I want vanity urls for another resource in my system. For example, if I sell a dozen varieties of widgets, I might want foo.com/widget_deluxe, foo.com/widget_extreme, etc in addition to my promo code urls. I'd have to make sure that there's no collision between promo codes and widget varieties, but that's easily handled elsewhere.
In an ideal world, I'd do something like this in my routes.rb file:
No way. In Rails World, this functionality should go inside controller.
In controller you can do something like
def show
if Promo.exists?(promo_id)
#do something
else
raise ActionController::RoutingError.new('Not Found')
end
end
Update
With routes, you can do something like this
constraints(lambda { |req| Promo.exists?(req.params["promo_id"]) }) do
get '/:promo_id' => 'promos#show
end
Please keep in mind that this constraints will query the database for every request with a url matching the pattern /:promo_id (e.q. /users, /faq). To avoid unnecessary database queries that decrease your website performance, you should add this rule as far as possible to the end of your routes.rb.
Using this routing logic, every request to your application would do an extra search for a promo code before it moved on to the rest of the routes. I recommend looking at your business case and consider doing a Promo controller. If you must do routes, something like this would work but I would put it at the end so that it goes to your regular routes first.
get '*', to: 'promos#show'

Changing urls in ruby on rails depending on different conditions

I'm new to ruby on rails....I wanted to know if there is a way to change the URL displayed depending on the client's response. I mean... here's an example:
I'm making a project showing listings in various places...
Now in general I have a home page, a search page, and a detail page for listings. So, respective URLs are officespace/home, officespace/search?conditions, officespace/detailpage?id=(controller-officespace)[&Conditions eg.---price,size,place,type...]
So, every time the client makes a request for search, the same URL is shown, of course with the given conditions.
Now I want that if the client asks for only the place and mentions nothing about size, price, etc., the url should be /listing/location_name.
If he mentions other conditions, then it'll be listing/(office_type)/size(x sq feet)_office_for_rent_in_locationname)
B.t.w. (I already have a controller named listings and its purpose is something else.)
And so on ........... Actually, I want to change URLs for a number of things. Anyway, please help me. And please don't refer me to the manuals. I've already read them and they didn't give any direct help.
This is an interesting routing challenge. Essentially, your goal is to create a special expression that will match the kinds of URL's you want to display in the user's browser. These expressions will be used in match formulas in config/routes.rb. Then, you'll need to make sure the form actions and links on relevant search pages link to those specialized URL's and NOT the default pages. Here's an example to get started:
routes.rb
match "/listing/:officeType/size/:squarefeet/office_for/:saleOrRent/in/:locationName" => "searches#index"
match "/listing/*locationName" => "searches#index"
resources :searches
Since you explicitly mentioned that your listings controller is for something else, I just named our new controller searches. Inside the code for the index method for this controller, you have to decide how you want to collect the relevant data to pass along to your view. Everything marked with a : in the match expressions above will be passed to the controller in the params hash as if it were an HTTP GET query string parameter. Thus we can do the following:
searches_controller.rb
def index
if params[:squarefeet] && params[:officeType] && params[:locationName]
#listings = Listing.where("squarefeet >= ?", params[:squarefeet].to_i).
where(:officeType => params[:officeType],
:locationName => params[:locationName])
elsif params[:locationName]
#listings = Listing.where(:locationName => params[:locationName])
else
#listings = Listing.all
end
end
And to send the user to one of those links:
views/searches/index.html.erb
<%= link_to "Click here for a great office!", "/listing/corporate/size/3200/office_for/rent/in/Dallas" %>
The above example would only work if your Listing model is set up exactly the same way as my arbitrary guess, but hopefully you can work from there to figure out what your code needs to look like. Note that I wasn't able to get the underscores in there. The routes only match segments separated by slashes as far as I can tell. Keep working on it and you may find a way past that.

how do I make the URL's in Ruby on Rails SEO friendly knowing a #vendor.name?

My application is in RoR
I have an action/view called showsummary where the ID has been passed into the URL, and the controller has used that to instantiate #vendor where #vendor.name is the name of a company.
I would like the URL to be, rather than showsummary/1/ to have /vendor-name in the URL instead.
How do I do that?
All of these solutions use find_by_name, which would definitely require having an index on that column and require they are unique. A better solution that we have used, sacrificing a small amount of beauty, is to use prefix the vendor name with its ID. This means that you dont have to have an index on your name column and/or require uniqueness.
vendor.rb
def to_param
normalized_name = name.gsub(' ', '-').gsub(/[^a-zA-Z0-9\_\-\.]/, '')
"#{self.id}-#{normalized_name}"
end
So this would give you URLs like
/1-Acme
/19-Safeway
etc
Then in your show action you can still use
Vendor.find(params[:id])
as that method will implicitly call .to_i on its argument, and calling to_i on such a string will always return the numerical prefix and drop the remaining text- its all fluff at that point.
The above assumes you are using the default route of /:controller/:action/:id, which would make your URLs look like
/vendors/show/1-Acme
But if you want them to just look
/1-Acme
Then have a route like
map.show_vendor '/:id', :controller => 'vendors', :action => 'show'
This would imply that that it would pretty much swallow alot of URLs that you probably wouldnt want it too. Take warning.
I thought I'd mention String#parameterize, as a supplement to the tagged answer.
def to_param
"#{id}-#{name.parameterize}"
end
It'll filter out hyphenated characters, replace spaces with dashes etc.
Ryan Bates has a great screencast on this very subject.
Basically you overload the to_param method in the Vendor model.
def to_param
permalink
end
Then when you look up the resource in your controller you do something like this:
#vender = Vender.find_by_name(params[:id])
But the problem with this is that you'll have to make sure that the vendors' names are unique. If they can't be then do the other solution that Ryan suggests where he prepends the the id to the name and then parses the resulting uri to find the item id.
You do this by modifying the routes that are used to access those URL's and changing them to use :name, rather than :id. This will probably mean that you have to write the routes yourself rather than relying on resources.
For instance add this to the routes.rb file:
map.with_options :controller => "vendor" do |vendor|
vendor.connect "/vendor/:name", :action => "show"
# more routes here for update, delete, new, etc as required
end
The other change that will be required is that now you'll have to find the vendor object in the database by the name not the id, so:
#vendor = Vendor.find_by_name(params[:name])
Internally (at least to my knowledge/experimentation) whatever parameter name is not specified in the URL part of the route (i.e. not within the "/Controller/Action/:id" part of it) is tacked on to the end as a parameter.
Friendly ID
http://github.com/norman/friendly_id/blob/26b373414eba639a773e61ac595bb9c1424f6c0b/README.rdoc
I'd have to experiment a bit to get it right, but there's two primary parts to the solution.
1) Add a route.
in config/routes, add a line that sends requests of the form baseurl/controller/:vendor-name to the action showsummary, (or maybe a new action, show_summary_by_vendor_name)
[also, if you planned on using baseurl/:vendorname, that's fine too]
For convenience, make sure the parameter is something like :vendor-name, not the default :id
2) Write the controller action.
In the controller file, either edit your showsummary action to differentiate based on whether it's called with an id or with a vendorname, or just write a show_summary_by_vendor_name. (depending on best practices, and what route you wrote in 1. I don't know off the top of my head which is preferable)
You can then do
#vendor = Vendors.find_by_name(params[:vendor_name])
or something like that, and then render it the way you would in regular showsummary.
3) Use that as the link.
Once you confirm that baseurl[/controller?]/vendor-name works, and shows the summary, make sure all the links in your application, and elsewhere, use that link. Off the top of my head, I can't remember how difficult it is to integrate a custom route into link_to, but I think it's doable. Most search engines [google] rely heavily on links, so good SEO will have you using those named links, not the numbered ones. I think. I don't know much about SEO.
Take a look also at this quck start to SEO for Rails

Resources