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
Related
I've seen strong parameters used for write HTTP verbs,
for example in rails
User.new( params.require(:user).permit(:name) )
but usually you wont see this kind of param whitelisting for HTTTP GET, why? i imagine it must be because of writes are more sensitive, but can't think of specific explanation.
Strong parameters were added to make sure that no model attributes would be updated which you did not specifically allow. If a user is able to change his/her website URL, you probably don't want him/her to change the role_id attribute, which would be trivial if you did not sanitize the input.
GET requests are typically not used to create or update models.
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.
So, I'm implementing a pretty/SEO-friendly URL scheme for my rails app. I have a model called Artist, and I would like the Rails artist_path helper to always generate the friendly version of the path.
In my routes.rb file, I have the following line:
get 'artists/:id(/:slug)', :to => 'artists#show', :as => 'artist'
If the slug is left out, or is incorrect (it's calculated by the artist name), the controller 301 redirects to the correct URL. However, for SEO reasons, I want to ensure that all links internal to my site have the correct URL to start with.
The Artist model has the two following (very simple) functions to allow this to work:
def slug
name.parameterize
end
def to_param
"#{id}/#{slug}"
end
If I call artist_path with an artist object, this works as intended:
> app.artist_path(Artist.find 1234)
=> "/artists/1234/artist-name"
However, when I use call it with just the ID, it does not seem to use to_param at all:
> app.artist_path(id: 1234)
=> "/artists/1234"
tl;dr: How can I force Rails to always instantiate the object and call to_param on it when artist_path is called, even when only the ID is specified?
As far as I'm aware, the reason why what you're asking won't work is because when you pass in values to the built-in/automatic URL helpers (like an ID, per your example), those values just get used to "fill in the blanks" in the route URL.
If you pass an array, the values from the array will get used in order until the URL is filled in. If you pass a hash, those properties will get replaced into the URL. An object, like your model, will use it's to_param method to fill in the values... etc.
I understand your concern regarding "having knowledge of the limitations of that model," however, this behavior is standard in Rails and I don't believe it would really throw anyone. When you pass in an ID, as you do in your example, you're not telling the URL helper to "lookup a record via the model using this ID," you're simply telling it to "replace ':id' in the URL string with the ID you're providing." I'm fairly certain the built-in URL helpers wouldn't even know how to lookup the record - what model to use, etc. - other than maybe inferring from the route/controller name.
My best suggestion is to always use the instantiated model/record. If you were hoping the URL Helper would look that up for you, then there's no extra overhead as far as querying the database goes. If there's some additional reason you want to avoid instantiating the record yourself, I'd be glad to hear it and possibly provide other suggestions.
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
So lets say I have a form for submitting a new post.
The form has a hidden field which specify's the category_id. We are also on the show view for that very category.
What I'm worried about, is that someone using something like firebug, might just edit the category id in the code, and then submit the form - creating a post for a different category.
Obviously my form is more complicated and a different scenario - but the idea is the same. I also cannot define the category in the post's create controller, as the category will be different on each show view...
Any solutions?
EDIT:
Here is a better question - is it possible to grab the Category id in the create controller for the post, if its not in a hidden field?
Does your site have the concept of permissions / access control lists on the categories themselves? If the user would have access to the other category, then I'd say there's no worry here since there's nothing stopping them from going to that other category and doing the same.
If your categories are restricted in some manner, then I'd suggest nesting your Post under a category (nested resource routes) and do a before_filter to ensure you're granted access to the appropriate category.
config/routes.rb
resources :categories do
resources :posts
end
app/controllers/posts_controller
before_filter :ensure_category_access
def create
#post = #category.posts.new(params[:post])
...
end
private
def ensure_category_access
#category = Category.find(params[:category_id])
# do whatever you need to do. if you don't have to validate access, then I'm not sure I'd worry about this.
# If the user wants to change their category in their post instead of
# going to the other category and posting there, I don't think I see a concern?
end
URL would look like
GET
/categories/1/posts/new
POST
/categories/1/posts
pst is right- never trust the user. Double-check the value sent via the view in your controller and, if it does't match something valid, kick the user out (auto-logout) and send the admin an email. You may also want to lock the user's account if it keeps happening.
Never, ever trust the user, of course ;-)
Now, that being said, it is possible to with a very high degree of confidence rely on hidden fields for temporal storage/staging (although this can generally also be handled entirely on the server with the session as well): ASP.NET follows this model and it has proven to be very secure against tampering if used correctly -- so what's the secret?
Hash validation aka MAC (Message Authentication Code). The ASP.NET MAC and usage is discussed briefly this article. In short the MAC is a hash of the form data (built using a server -- and perhaps session -- secret key) which is embedded in the form as a hidden field. When the form submission occurs this MAC is re-calculated from the data and then compared with the original MAC. Because the secrets are known only to the server it is not (realistically) possible for a client to generate a valid MAC from the data itself.
However, I do not use RoR or know what modules, if any, may implement security like this. I do hope that someone can provide more insight (in their own answer ;-) if such solutions exist, because it is a very powerful construct and easily allows safe per-form data association and validation.
Happy coding.