Friendly URLs in Github - ruby-on-rails

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

Related

Generating the URL of a nested resource without its parent (without using shallow)

In our application, many resources are nested under a common resource representing an organization. Most URLs include an organization ID the following pattern: /:organization_id/notifications/:id.
My problem is that I always have to give the current organization to generate the URL to any model. For example, the link to an existing notification would be link_to [#organization, #notification].
Since a notification already belongs to an organization, I was wondering if it was possible to generate my URL using link_to #notification and it would actually generate a URL including the organization ID of the notification. I was hoping that a configuration in the model would be able to achieve this but I could not find anything in the guides, the docs or the source code of Rails.
I would like to keep the organization ID visible in the URL as this is an information that is used by our customers. So I do not want to use shallow nested resources for this problem.
We are using Rails 5.2.0.
You want the resolve route definition method.
It is designed to do exactly what you want: configure a different behaviour when a single model instance is passed to url_for (as link_to does, for example).
Specifically, in your config/routes.rb, something like:
resolve("Notification") do |note|
[:notification_organization, note.organization, note]
end
It sounds like you were on the right track -- it's just a routing concern rather than a model one.

Things to change when using username slug/short URL in rails

I'm pretty new at rails, so forgive me if I'm overlooking simple things or the rails way. My objective is to completely replace URLs of the form
/users/1
with
/username
for all purposes. (I think exposing IDs scaffolding publicly is like walking around with a bone sticking out of your arm.) But implementing seems a little more complicated than I expected. This seems to change the entire way rails indexes and looks up users, rather than just substituting a lookup method.
Although I've kind of gotten this to function using the to_param override in my user.rb file, I've read this means I'll have indexing problems down the road when using params([:username]), and I'm not sure how it will impact my
(a) session model at new user creation, and
(b) #User usage in the user/show.html.erb file.
So, I've either consulted the following pages (or asked the questions):
Ruby on rails routing matching username
customize rails url with username
Routing in Rails making the Username an URL:
routing error with :username in url
Correct routing for short url by username in Rails
rails3, clean url, friendly_id, sessions
The major issues I'd like to understand from this question:
What functionality do I lose by transitioning to this? That is, what things currently "just work" in rails that I'll have to address and rewrite if I pursue this replacement?
As a practice, is this something better to replace with friendly_id? My concern here is that creating a slug column in my DB identical to the username seems a little non-DRY and makes me uncomfortable, and I'd rather avoid dependencies on external gems where possible.
What does my users#show need to look like?
You should check out Friendly ID. Makes doing what you're trying to do incredibly easy.
https://github.com/norman/friendly_id
There's a Railscast for it, too.
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid?view=asciicast
If your username contains a special characters like #, -, . and got an error that says "No route matches" then you need to filter its route. See below:
match "/user/:username" => 'users#show', :as => :profile, :username => /[\.a-zA-Z0-9_#-]+/
After working around this for a couple weeks, I'd say the best answer as of Aug 2, 2012 is that if you do this, you violate many rails conventions and rip apart the very fabric of time and space itself.
Ugly scaffolding in the URLs is a necessary part of rails' approach to RESTfulness.

Rails 3.0 Frontend URL's with slugs, backend URL's without

Is there any way to use the slugs generated by overriding "to_param" in the model for only the frontend?
Eg, In my model I have:
def to_param
"#{id}-#{name.parameterize}"
end
And it works great on the frontend. But the problem seems to be, on the backend adminstrative side, you can edit the name/title. This ends up with a bunch of weird stuff when I start editing things.
Is there any easy way to simply use the #{id} only if I'm in the namespaced admin backend?
Please check the gem Friendly_ID. It deals with slugs in a very nice way.
If the name changes (which is your identifier for the edited object), the id does not change.
So when you edit or update an object, you could use the id for identification, rather than the slug.

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

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

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?

Resources