How to match rails route based on URL ending with an ID? - ruby-on-rails

It seems really easy to match routes using the '/' as a separator, but I'd like to match using a hyphen. Only problem is that there could be hyphens in the dynamic text that I want to appear before the ID:
Format: http://www.cities.com/-
Example: http://www.cities.com/los-angeles-california-123
match '/:description-:id' => 'cities#show' does not work because the :description text can contiain ids.
Is there any way for me to match this format? I've done this before in .NET using Regex, but loving my new life as a Ruby Dev.
Thanks!

I'd avoid using the routes for this: just pass the param :id and split it in the controller.
E.g.
City.find param[:id].split('-').last
More importantly make sure you are able to generate proper ulrs by overriding to_paramin the model:
def to_param
"#{self.name.split(' ').join('-')}-#{self.id}" # FIXME need extra sanity checks
end
One last trick, unless you do need the id as the last arg, you'd better put it at the beginning, e.g. 123-los-angeles, this way you do not need to "parse" the id in your controller, Model.find '123-los-angeles' works out of the box.
Cheers,

Related

Can I have the same URL, but different dynamic segments?

Is it possible to use the same URL, but with different dynamic segments?
My issue is: I want to be able add object A to objects B and C. So I want to have 2 Rails routes, A/new/:b_id AND A/new/:c_id. Which I tried.
In my routes.rb:
controller :A do
get 'A/new/:b_id', to: 'A#new', as: :new_b_a
get 'A/new/:c_id', to: 'A#new', as: :new_c_a
end
Problem is that the value being passed into the new page is always params[:b_id]! (I can print out the value from the URL using params[:b_id].)
So it seems like maybe I can't have 2 similar routes with different dynamic segments..? If so, how would I do this?
A better way to accomplish this would be using nested resources.
You always get :b_id because Rails matches routes in the order they appear in your file. Since a B ID is an integer indistinguishable from a C ID, there's no way for it to know if you want one or the other.
But, since you do have Bs andCs already, and perhaps those also need to be created, shown, etc., you can differentiate their paths RESTfully, which is what Rails wants you to do.
# config/routes.rb
resources :bs do
resources :as
end
resources :cs do
resources :as
end
This will build you the paths you're creating manually, but turned around a bit:
/bs/:b_id/as/new
/cs/:c_id/as/new
As you can see, the paths now start with the object type you want to add an A too, so Rails can tell them apart. The helper methods generated for this look the same as the ones you're currently defining manually:
new_b_a_path(b)
new_c_a_path(c)
Both paths will route you to the AsController, and then you'll need to look up the correct B or C based on the parameter present:
# AsController#new
#parent = B.find(params[:b_id]) if params[:b_id]
#parent = C.find(params[:c_id]) if params[:c_id]
#a = parent.build_a # Assuming has_one or has_many from B and C to A
Rails has spent a long time developing a particular way to do this sort of thing. You can always dive in and do it a different way, but at best you'll be wasting effort, and at worst you'll be fighting the framework. The framework is less compromising and usually wins.
The routing system works by trying to match the current path to each of the registered routes, from top to bottom. The dynamic :b_id part means "anything that goes here in the path will be passed as a parameter called :b_id to the controller". So making a request to "A/new/anything" will always match the first route, and since you renamed the parameter to :new_b_a, that's how it's called in the params hash.
If you really want to use the same route, you'll need to pass an extra argument specifying the class you want to create the relationship with, though I'd not recommend doing that. It could be something like get 'A/new/:klass/:id', so in the controller you could match the parameter to the desired classes:
def new
case params[:klass]
when 'B' then # do stuff
when 'C' then # do stuff
else raise "Invalid class: #{params[:klass]}"
end
end

ruby on rails iterating params

I have a client that is sending params such as age, gender, name and so on.
I need to retrieve data from the table based on the params, but first I need to check for the presence of the param(to avoid a null param and therefore an empty result). The params are working as filters, so they can be triggered or they can be left blanck.
What I am doing right now is
#retieve = Student.all
unless params[:age].nil?
#retrieve = #retrieve.where(age: params[:age])
end
unless params[:gender].nil?
#retrieve = #retrieve.where(gender: params[:gender])
end
and so on for every param I receive. This way I check if the filter has been selected, and if it has I use the selection as a parameter for the query
It works, but as Ruby is known for the DRY statement, I am pretty sure someone out there knows a better way for putting this and to make this flexible.
Thank you for whatever answer or suggestion you will provide!
This will work best if all of these filters were in a subhash of params that you can iterate over without including unwanted parameters (eg the :action and :controller parameters that rails adds)
Once you've done that you could do
(params[:filters] || {}).inject(Student.all) do |scope, (key, value)|
scope.where(key => value)
end
There's a few ways to do this sort of thing and you have options for how far you want to go at this stage.
Two big things I'd consider -
1) Make nice scopes that allow you to send a param and ignore it if it's nil. That way you can just append another scope for each param from the form and it will be ignored without using if or unless
2) Move the search into a separate class (a concern) to keep your controller clean.
Here's a blog post that talks about some of the concepts (too much to post in this answer). There is lots of info on the web about this, I searched on the web under "rails search filter params concern" to get an example for you.
http://www.justinweiss.com/blog/2014/02/17/search-and-filter-rails-models-without-bloating-your-controller/

Rails to_param - Without ID

I want to create param /users/will-smith, so here's my code:
def to_param
"#{full_name.parameterize}"
end
Parameterize will convert "Will Smith" to "will-smith"
So in the controller, the param won't match the find statement, thus return nil
# User with full_name "will-smith" not found
#user = User.find_by_full_name(params[:id])
Most of the solutions I found is by changing the param to #{id}-#{full_name.parameterize}. But I don't want the URL to contain the ID.
The other solutions is adding permalink column in the database, which I don't really like.
Any other solution?
Thanks
Here's a gem called FriendlyId. It will give you more options to play with.
The problem with your code is that you need to convert that parameter back to original, or use the same kind of transformation on your column during the search. FriendlyId, basically, helps you to achieve the same effect.
Also, I'm not sure, but you could miss that gist. It contaits lots of info on the topic.

Which characters in a search query does Google ignore (versus treating them as spaces?)

I want to give my pages human-readable slugs, but Rails' built-in parameterize method isn't SEO-optimized. For example, if I have a post called "Notorious B.I.G. is the best", parameterize will give me this path:
/posts/notorious-b-i-g-is-the-best
which is suboptimal since Google construes the query "Notorious B.I.G." as "Notorious BIG" instead of "Notorious B I G" (i.e., the dots are removed rather than treated as spaces)
Likewise, "Tom's fave pizza" is converted to "tom-s-fave-pizza", when it should be "toms-fave-pizza" (since Google ignores apostrophe's as well)
To create a better parameterize, I need to know which characters Google removes from queries (so I can remove them from my URLs) and which characters Google treats as spaces (so I can convert them to dashes in my URLs).
Better still, does such a parameterize method exist?
(Besides stringex, which I think tries to be too clever. 2 representative problem cases:
[Dev]> "Notorious B.I.G. is the best".to_url
=> "notorious-b-dot-i-g-is-the-best"
[Dev]> "No, Curren$y is the best".to_url
=> "no-curren$y-is-the-best"
I would try using a gem that has been designed for generating slugs. They often make good design decisions and they have a way of updating the code for changing best practices. This document represents Google's best practices on URL design.
Here is a list of the best gems for solving this problem. They are sorted by rank which is computed based on development activity and how many people "watch" changes to the gems source code.
The top one right now is frendly_id and it looks like it will generate good slugs for your use in SEO. Here is a link to the features of the gem. You can also configure it and it looks like it is perfect for your needs.
Google appears to have good results for both the "b-i-g" and "big" in the url slugs.
For the rails side of things, yes a parameterize method exists.
"Notorious B.I.G. is the best".parameterize
=> "notorious-b-i-g-is-the-best"
I think you can create the URLs yourself... something like
class Album
before_create :set_permalink
def set_permalink
self.permalink = name.parameterize
end
def to_params
"#{id}-#{permalink}"
end
end
This will create a url structure of:
/albums/3453-notorious-b-i-g-is-the-best
You can remove the id section in to_params if you want to.
Use the title tag and description meta tag to tell google what the page is called: these carry more weight than the url. So, leave your url as /posts/notorious-b-i-g-is-the-best but put "Notorious B.I.G. is the best" in your title tag.

Adding title to rails route

I have List objects which are shown like this:
www.mysite.com/lists/123
Where 123 is the id of the list. What I would like to do is add the title of the list the url so it it more informative(for google or whatever). So I would like it to look like:
www.mysite.com/lists/123/title-of-list-number-123
How do you go about adding to a url like this? If you just enter:
www.mysite.com/lists/123 w/o the title, should it find the title and then redirect to a new route?
If you want to keep your find-calls as they are (by id), you could do the opposite of what mplacona suggested:
def to_param
"#{id}-#{title.parameterize}"
end
With this, your find(params[:id]) will work because it'll convert the string to an integer (can only succeed if the number is in the beginning of the string). So this is will actually work:
List.find("123-my-title")
and will be the same as
List.find(123)
Read more about this and other ways to accomplish this here: http://gregmoreno.ca/how-to-create-google-friendly-urls-in-rails/
The parameterize will automatically convert the string to a "pretty" url. Read more here: http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html#M001367
If you want a bit more functionality, I'll suggest friendly_id aswell.
This article says exactly what you need to do accomplish this.
http://railscasts.com/episodes/63-model-name-in-url
UPDATE
Have a permalink added to your model, and save as follow to it:
def to_param
"#{permalink}-#{id}"
end
On your controller, instead of getting things by the id, get them by the pemalink:
#product = Product.find_by_permalink(params[:id])
And that's all you need.
The screen cast explains all the steps on how to do it.
You could also take a look at friendly id, if you're in the mood for a gem/plugin.

Resources