Keep track of reserved URLs (to enable vanity per-user URLs) - ruby-on-rails

On my rap lyrics explanation website, every user has an associated "favorites" page at http://rapgenius.com/USERNAME
Because these favorites pages occupy the root namespace, I have to make sure that no one takes a username that I'm already using for something else. E.g.:
/songs
/lyrics
/users
/posts
How can I look up all top-level paths that have higher precedence than the /username route (which is at the bottom of routes.rb) at the time of user creation so I can prevent users from taking these reserved names?

Why not make things easier for yourself and simply do:
def validate
reserved = %w(songs lyrics users posts)
errors.add(:username, 'is not allowed') if reserved.include?(username)
end

If you want to pull in a plugin to do this a useful one is friendlyid
From their site
FriendlyId is the “Swiss Army bulldozer” of slugging and permalink plugins for Ruby on Rails. It allows you to create pretty URLs and work with human-friendly strings as if they were numeric ids for ActiveRecord models.
More importantly for you it has support for making sure the urls generated don't match you controllers/paths

Maybe you could make use of this?
But it looks like it raises on no match, so you might have to write a wrapper for it to rescue false on those cases.
I also just realized you would have to also inspect any match to make sure it's not the user route you will want it to match...

Two counter-questions:
1) Why does the "favorites" page for each user need to live in the root of your URI tree?
2) How do you currently deal with the situation where two users choose the same username?

Related

How to redirect old url to new url in rails using just routes?

The old sitemap of my application has already been indexed to Google now. For some one visiting my rails app with old url shouldn't go to 404.
The old url looked like this
/search?sub_category=210
Now after making them friendly, it looks like this:
/search?sub_category=milling-and-drilling.
I tried redirecting it from controller but it causes too much issues on other things. Such as filters which are using the same params. Is there a way I can do it from routes file?
Instead of using redirect_to route_path(id) you would do redirect_to route_path(object.find_by_id(id).name)
How to redirect old url to new url in rails using just routes?
AFAIK (As far as I know), no. (if through params)
The main job of routes.rb is to determine what "code" will handle the request, particularly that which matches request.url and the request.method. It does not concern yet of the request parameters nor its values: these would be handled in the controllers itself. Although, you can route based on parameters (or any information about the "request") through a routes constraint
Alternative Solution:
Instead of finding by ID, now find by "Slug name or ID".
In your models, particularly in this specific example of yours, add something like:
class SubCategory < ApplicationRecord
# if you're using a gem for the "friendly" urls, you don't need this method, and just use theirs.
def self.friendly_find!(friendly_id)
# this may not necessarily be `name`, maybe `slug`? or depending on your case
find_by(name: friendly_id) || find(friendly_id)
end
end
And in your controllers, just replace wherever you're finding a record (maybe also to other models not just SubCategory as needed be), particularly in your search action:
def search
sub_category = SubCategory.friendly_find!(params[:sub_category])
end
NOTE: friendly_find! will raise an Error if no record is found.

User defined routes in Rails

There is a lot of good information on routing in Rails. I must be missing something, but I can't seem to find a good example of a Rails application that allows dynamically defined user specific routes.
For example, my application is hosted at:
www.thing.com
... and serves out user generated content.
I'd like to give the user an option to define a suffix that let's them share a somewhat customized URL to their content. For example, if a user 'joe' generates some car info they might want to make it avilable via joescars at:
www.thing.com/joescars
Maybe later they decide they want to serve it out under 'carsbyjoe' at:
www.thing.com/carsbyjoe
I can handle limiting what suffixs are valid. Is there a Rails way to codify this kind of dynamic routing?
There is a way to do this. In your config/routes file add a route that says get '/:user_route' => 'somecontroller#someaction'. You'll have to put it at the very bottom because routes are matched from top to bottom and this will match things like /users or other routes you'll likely want directed elsewhere.
Then, in your controller you can access params[:user_route] to show the appropriate content. There are a number of ways to store this custom content in your database, depending on your needs. You might have a model representing these custom routes like CustomRoute.find_by_route(params[:user_route]), or maybe each user will have a custom route so you could do User.find_by_route(params[:user_route]).custom_page and each User has one custom_page.

How to check if a route already exists , while generating a dynamic route in rails

In my application i need to generate vanity urls for all my users like:
profilename/joinme, where profile name will be unique.
There is a problem , if the profile name is an already existing route like if:
profile_name = users then route will be users/joinme and there is already a resource with the name users.
So i want to check upon creation of these vanity urls that they does not interfere with my existing routes.
Is there an easy way to do that ?
one way that i can think of is getting the formatted output of "rake routes" in a file and then checking for the existence of that on creation of every routes.
You should create a list of banned words and check that the username doesn't match any of this words using a validation. An easy way could be:
validate :check_banned_words
def check_banned_words
if %w( admin join login help register ).include? self.username
errors.add(:username, "this is a banned word!!")
end
end
If the list of banned words is too long, put it into a yaml file, or create a model just for banned words, in that way you can add new words without redeploy your app
Instead of generating dynamic route, you can use dynamic segments in routes.
For example:
match ":username/joinme", :to => "users#joinme"
Then you can customize behavior in controller as you want.
For the reference:
Rails Routes - Dynamic Segments
Also, if you want to control the process of creating new users to prohibit the use of names that are in conflict with the existing routes, you can use the approach described in this answer.

Friendly URLs in Github

How has Github managed to get friendly URLs for representing repos of users? For a project called abc by username foo, how do they work around with a URL like: http://github.com/foo/abc. Are they fetching the abc model for the DB from the title in the URL (which sounds unreasonable as they are modifying the titles). How are they transferring the unique ID of the abc repo which they can fetch and show in the view?
The reason I ask is that I am facing a similar problem of creating friendlier URLs to view a resource. MongoDB's object IDs are quite long and make the URL look horrific. Is there a workaround? All the tutorials that demonstrate CRUD (or REST) URLs for a resource always include the object's unique ID(e.g. http://mysite.org/post/1 or http://mysite.org/post/1/edit. Is there a better way to do it?
Not having seen their code, I couldn't tell you exactly how they do it, but if you're using Rails there are at least two Ruby gems that will give you similar results:
Take a look at Slugged and friendly_id
http://github.com/foo/abc is a unique repository identifier (for that repo's master branch). I'd assume that somewhere they have a table that looks like:
repository-id | user-id | project-id
and are just looking up based on user and project rather than repository-id.
You'd need to do some domain-specific mapping between internal and user-friendly ids, but you'd need to make sure that was a 1:1 mapping.
See this rails cast on methods, gems and solutions to common problems you might get while modifying the application to use friendly urls.
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid?view=asciicast
(although Ryan Bates deserves the rep+ for this)
I mocked a structure like this using FriendlyID and Nested Resources.
Essentially, use friendly ID to get the to_param-ish slugs in your routes, then set up nested resources. Using GitHub as an example:
routes.rb
resources :users do
resources :repositories
end
Then in your controller, say, for repositories, you can check the existence of params[:user_id] and use that to determine the user from the route. The reason I check for existence is because I did something like (roughly):
/myrepositories/:repository_id
/:user_id/:repository_id
So my controller does:
def show
#user = params[:user_id] ? User.find(params[:user_id]) : current_user
end
I followed this tutorial here to get started with this same project.
This is called URL rewriting if the web server does it (such as Apache), and routing when it happens in a web application framework (such as Ruby on Rails).
http://www.sinatrarb.com/intro#Routes
http://httpd.apache.org/docs/current/mod/mod_rewrite.html

Thoughts regarding model ids in rails routes and validation

I am new to RoR and started working on a typical 'has_many' association (ie. a user has many friends). I have everything working correctly, but I don't like having the ids exposed in the url. I find that I need to add extra validation in my controller to make sure the ids represent valid associations in case the user manually entered different ids.
Personally I would like to see the ids out of the url and passed via some other means but that is not always possible. Shallow nesting of resources will help reduce the number of ids I need to validate at least.
What is the RoR philosophy on this? I have not seen anything specific to this issue.
Thanks
the URL has parameters if it is a GET url.
Try using POST parameters, which means your url will no longer be cluttered. Note that a malicious user can still send a made-up POST request using curl.
My approach to this is implementing proper authorization. If the user requests information for an object he is not permitted to read, this should be handled by an authorization framework.
With CanCan or Declarative Authorization you can define rules that replace your "manual" (and error-prone) checks in controllers.
I like the IDs being in the URL. That is what REST is about. Getting information for specific Resources, which have to be identified with an ID.
You can use Friendly ID in order to replace the integer ID by a slug (e.g. users/tollbooth instead of users/42).
basically ror routes by default takes id as key to generate urls. If you are not fan of id based urls then you can always override urls by using to_param inside model.
def to_param
# make sure this field is always present & unique
username
end
then by default you will start seeing username instead of id inside urls
How to find object inside controller actions
User.find_by_username(params[:id])
If you dont want to do this manually make use of slug gems like friendly id

Resources