How to make case sensitive URLs with Rails / Friendly-Id? - ruby-on-rails

I am using the Friendly-Id gem to create pretty URL's. I would like to create case sensitive URLs and redirect all the other case versions of the same URL to the default one with a 301 status.
/profile/Jack-Wild # default URL
/profile/jack-wild # redirect to /profile/Jack-Wild
/profile/jaCk-Wild # redirect to /profile/Jack-Wild
Friendly_Id downcase's slugs and only matches and accepts downcase URLs, 404ing everything else.
The feasable solution would be to maintain a slug_case_sensitive ( Jack-Wild ) as well as the default downcase version of slug ( jack-wild ).
Then make Friendly_Id search by the slug column with params[:id].downcase and if params[:id] != slug_case_sensitive redirect to the preferred URL ( /profile/slug_case_sensitive ).
This is my idea on how to achieve it, with a few puzzles missing, hoping someone could help filling them in.
Modify the create and update slug methods, to create and update both versions of the slug. This would be only 1 method: set_slug:
# Sets the slug.
def set_slug(normalized_slug = nil)
if should_generate_new_friendly_id?
candidates = FriendlyId::Candidates.new(self, normalized_slug || send(friendly_id_config.base))
slug = slug_generator.generate(candidates) || resolve_friendly_id_conflict(candidates)
send "#{friendly_id_config.slug_column}=", slug
end
end
private :set_slug
Suppose slug is not downcased. Should changing:
send "#{friendly_id_config.slug_column}=", slug
with:
send "#{friendly_id_config.slug_column}=", slug.downcase
send "#{friendly_id_config.slug_column_case_sensitive}=", slug
be enough ? Also, how to add and set friendly_id_config.slug_column_case_sensitive option ?
And then, change user_path(#user) and <%= link_to "My profile", current_user %> to return the case sensitive URL. But no idea of a better way than user_path(id:#user.slug_case_sensitive) which is ugly and also I would have to change it everywhere and not sure if it can be done in 3rd party gems...
Any ideas on this would help and be much appreciated. Thanks!

Maybe different approach could be applied in this situation: most likely you'll use your Rails app behind Nginx as a proxy. Then it's possible to rewrite all requests for profiles with upcased letters in them to canonical variant. It will be faster than making additional DB requests and using whole Rails stack. Also it could be much simpler to implement.

You can override the method normalize_friendly_id in your model:
def normalize_friendly_id(value)
value.to_s.parameterize(preserve_case: true)
end
It relies on ActiveSupport parameterize which accepts a preserve_case option.

Related

Transform Rails params?

Is there a way to transform Rails params?
I have a URL like #some-user/posts/1
Users have the decorated # in the URL.
But, Users are saved without the # like.. some-user
So I need to look up the post via username without the #.
Currently, I have..
def url_params
params.permit(:username, :id)
end
Is there a best way to tranform the params for later use? In this case, remove the #?
One way to remove the # is like this:
‘#some-user’[1..-1]
As #jvillian has stated, this may not be the ‘best’ way, depending on your idea of ‘best’. For example, if you’re using Ruby 2.7, you can also use:
‘#some-user’.delete_prefix(‘#‘)
Maybe this is the ‘best’ way.
Ended up with this one.. I like this the best :)
def user_name
params[:username].gsub('#', '')
end

Case insensitive user names in Rails 3 routes

I am trying to allow users to access their accounts by their user name, regardless of how they capitalize it in the URL. So http://example.com/Username would go to the same page as http://example.com/username.
I am fine with the any other part of the URL being case sensitive (I know that's the standard), but I don't see a need to force a user to get the case of their user name correct in the URL.
Can this be done by adding certain settings in the routes.rb file alone?
I thought this would be straightforward but apparently it isn't.
I found a seemingly simple question here but there's only one answer that I think will help and it's too hacky (by the author's own admission)
I believe this isn't a routing issue and you can address it in your controller code:
User.where("lower(username) = lower(?)", params[:username]).first!
or
User.where("lower(username) = ?", params[:username].downcase).first!
or
User.find(:first, :conditions => [ "lower(username) = ?", params[:username].downcase ])
It should work fine to handle this behaviour in the controller, not in the routes.
# config/routes.rb
match ':username' => 'users#show'
# app/controllers/users_controller.rb
def show
username = params[:username]
user = User.find_by_username(username) || User.find_by_username(username.downcase)
# or something along these lines...
end
An even nicer solution might be to store some kind of slug identification for the user that is always downcased and ready to be used in URLs. You could have a look at the awesome friendly_id gem for that purpose.

Human readable URL causes a problem in Ruby on Rails

I have a basic CRUD with "Company" model. To make the company name show up, I did
def to_param
name.parameterize
end
Then I accessed http://localhost:3000/companies/american-express which runs show action in the companies controller.
Obviously this doesn't work because the show method is as following:
def show
#company = Company.find_by_id(params[:id])
end
The params[:id] is american-express. This string is not stored anywhere.
Do I need to store the short string (i.e., "american-express") in the database when I save the record? Or is there any way to retrieve the company data without saving the string in the database?
Send the ID with the parameterized value;
def to_param
new_record? ? super : "#{id}-#{name}"
end
And when you collect the data in the show method, you can use the whole parameter;
def show
#company = Company.find("12-american-express"); // equals to find(12)
end
There's also a plugin called permalink_fu, which you can read more about here.
I think friendly_id is more usable.
I do something similar with the Category model in my blog software. If you can guarantee that the only conversion the parameterize method is doing to your company names is replacing space characters with dashes then you can simply do the inverse:
def show
#company = Company.find_by_name(params[:id].gsub(/-/, ' '))
end
Try permalink_fu plugin, which creates SEO friendly URLs in rails
http://github.com/technoweenie/permalink_fu
cheers
sameera
I would suggest the friendly_id gem also.
It gives you the flexibility to use persited permalink slugs, also strip diacritics, convert to full ASCII etc.
Basically it makes your life a lot easier, and you get "true" permalinks (no to_param and the id workaround needed) with little effort.
Oh and did i mention that the permalinks are also versioned, so you can make old outdated permalinks to redirect to the current one? :)

How do you handle RESTful URL parameters in a Ruby on Rails application?

I am dealing with a very simple RESTful Rails application. There is a User model and I need to update it. Rails coders like to do:
if #user.update_attributes(params[:user])
...
And from what I understand about REST, this URL request should work:
curl -d "first_name=tony&last_name=something2&v=1.0&_method=put" http://localhost:3000/users/1.xml
However, it's quite obvious that will not work because each URL parameter will be parsed to the variable "params" and not "params[:user]"
I have a hackish fix for now, but I wanted to know how people usually handle this.
Thanks
It's just a matter of how Rails parses parameters. You can nest parameters in a hash using square brackets. Something like this should work:
curl -d "user[first_name]=tony&user[last_name]=something2&v=1.0&_method=put" http://localhost:3000/users/1.xml
This should turn into
{:user=>{:last_name=>"something", :first_name=>"tony"}}
in your params hash. This is how Rails form helpers build the params hash as well, they use the square brackets in the form input tag name attribute.
It's a tradeoff; You can have slightly ugly urls, but very simple controller/models. Or you can have nice urls but slightly ugly controller/models (for making custom parsing of parameters).
For example, you could add this method on your User model:
class User < ActiveRecord::Base
#class method
def self.new_from_params(params)
[:action, :method, :controller].each{|m| params.delete(m)}
# you might need to do more stuff nere - like removing additional params, etc
return new(params)
end
end
Now on your controller you can do this:
class UsersController < ApplicationController
def create
#handles nice and ugly urls
if(params[:user]) #user=User.new(params[:user])
else #user = User.new_from_params(params)
end
if(#user.valid?)
... etc
end
end
end
This will handle your post nicely, and also posts coming from forms.
I usually have this kind of behaviour when I need my clients to "copy and paste" urls around (i.e. on searches that they can send via email).

Friendly Form Validations (Rails)

I checked out both of these previously-asked questions, and they're a help but not a full solution for my case.
Essentially I need to validate a user-submitted URL from a form. I've started by validating that it begins with http://, https://, or ftp:// :
class Link < ActiveRecord::Base
validates_format_of [:link1, :link2, :link3,
:link4, :link5], :with => /^(http|https|ftp):\/\/.*/
end
That works great for what it's doing, but I need to go these two steps further:
Users should be allowed to leave the form fields blank if needed, and
If the URL provided by the user does not already start with http:// (say they enter google.com, for example), it should pass the validation but add the http:// prefix while being processed.
I'm having a hard time determining how to make this work cleanly and efficiently.
FYI, you don't have to pass an array to validates_format_of. Ruby will do arrays automagically (Rails parses the output of *args).
So, for your question, I'd go for something like this:
class Link < ActiveRecord::Base
validate :proper_link_format
private
def proper_link_format
[:link1, :link2, :link3, :link4, :link5].each do |attribute|
case self[attribute]
when nil, "", /^(http|https|ftp):\/\//
# Allow nil/blank. If it starts with http/https/ftp, pass it through also.
break
else
# Append http
self[attribute] = "http://#{self[attribute]}"
end
end
end
end
Just to add to the above, I use the Ruby URI module to parse URLs for validity.
http://www.ruby-doc.org/stdlib/libdoc/uri/rdoc/classes/URI.html
It works really well and it helps me to avoid regexes.

Resources