URL Parameter Encoding and Rewriting in Rails - ruby-on-rails

As a learning experience for Ruby and Rails, I am creating a website for taking polls, stores the results, etc. As part of the polling process, a user has to go through a number of questions and provide answers to those questions. When they are done, they receive a list of recommendations based upon the answers they provided (of type Answer).
I have two parts to my question. One, I think I am heading down the right path. The other, I'm not even sure where to begin, and don't know if it is a good idea.
Here is my Answer model:
class Answer
attr_accessor :question_number, :description, :answer
end
Question 1
I am looking for a way that, when the user submits all the answers (I'm storing their responses in session storage), it goes to my search function - but it is encoded nicely.
Instead of:
http://localhost:3000/results/search?[biglongstringofdifferentanswers]
I would like something like:
http://localhost:3000/results/search/1-answer_2-answer_3-answer
After doing some searching, it seems that what I want to accomplish has to be done with the #parameterize method, but I'm not sure I understand how to do that exactly.
Question 2
The second part to my question is - can I encode my answers so that they aren't directly human readable. I want to do this to prevent people from browsing to each other's answers. For example, the first answer is always the person's unique ID and I don't want to someone to be able to just browse to any old set of results by switching around parameters.
So, I am hoping to get something along the lines of:
http://localhost:3000/results/search/798dh832rhhbe89rbfb289f9234972bdbdbbws3
For this second question, I'm not even sure if this is a good idea, so I'm open to suggestions for this one.
Appreciate any help and guidance on these questions as I continue to explore/learn Ruby and RoR.

If I get it right, there is not any login system and you want submitted answers that you store in your DB to be accessable via url for the user. You said you don't want users to navigate to other users' answers but the user getting the url can still share it.
What I would do is to submit answers via POST method, so you don't have to worry about encoding your params etc. It gets then real easy with Rails.
You can add a public_id column to your answer object that would be a generated big int. After the post methoded submit, once you save the answer in your DB, you could return a redirect to the answer public id url.
something like
def create
answer = Answer.new(params[:answer])
if answer.save
answer.generate_public_id # <= would be nice to add if in the answer model 'after_create' filter probably
return redirect_to public_id_answer_path
end
render :partial => 'error'
end
What do you think ?

Related

How does ruby on rails map http request to handler function?

I am new to RoR, so please forgive me if this is a stupid thing to ask.
I was looking into routes.rb file and found these two lines:
get "question/question"
get "question/answer"
But there was no mention of the functions they are mapped to.
I tried to look how they are mapped to the functions and in all the tutorials or reference docs I found on net, requests were hashed to function names.
So I was not able to understand the routing in this case. Can someone give names of some files to look into or some documents suitable for beginners which can explain routing clearly, removing the magical part?
Look at the QuestionsController and the question and answer methods.
See these routing docs for details on how routing works for these types of paths.
Allow defaults for values when possible.
These lines
get question/question
and
get question/answer
means respond to get requests that use a url with question/question or question/answer to be processed by the:
question controller and the question method
question controller and the answer method
You may be more used to working with constructs like:
get 'users/change_district/:district_id' => "users#set_district", :as => 'change_district'
which allow you to specify which controller (users) and which action (set_district)
If, however, you omit some parts then the router will use what you give and use defaults for anything not specified.

RESTful Quiz Representation

I'm building a quiz. A user can pick a subject and answer 5 questions. After each question they view the answer. I'm trying to stick to a strict RESTful representation of this workflow but cant really settle on a url scheme. For example:
User Joe picks the subject sport and is ready to see the first question. The url is
user/joe/subject/sport/question/1
When he submits his answer 'B' ( its a multiple choice quiz) , Joe is creating a new answer, we POST to
user/joe/subject/sport/question/1/answer/B
before viewing the correct answer at
user/joe/subject/sport/answer/1
we then view the next question at
user/joe/subject/sport/question/2
This is all obviously too complicated. How would you approach this problem in a RESTful manner?
Start with this presentation. It's is a great resource for RESTful API design. With that in mind, here are some starting suggestions:
RESTful URLs have an implicit hierarchy. Take the user information out of the URL. It belongs in the HTTP headers.
/subject/sport/question/1
/subject/sport/question/1/answer/B
/subject/sport/answer/1
/subject/sport/question/2
I don't see any useful information added by the subject part. The subject is identified by (in your example) sport.
/sport/question/1
/sport/question/1/answer/B
/sport/answer/1
/sport/question/2
Categories should be plural.
/sports/questions/1
/sports/questions/1/answers/B
/sports/answers/1
/sports/questions/2
When you POST to answer a question, you're not POSTing to add a new answer resource (that is, defining a new possible answer). Aren't you are POSTing to an existing resource?
You haven't mentioned anything about HATEOAS. If you're really going to implement REST, you should be doing things like providing "next" links in the hypermedia.
For me the basic idea in a REST service is the "resource". First you need to identify your resources.
So there is a user and she starts a new quiz.
POST /user/joe/quiz.
It returns: Location: /user/joe/quiz/1
Then the user selects a sports question, so you update your quiz to include a random (server selected) question.
POST /user/joe/quiz/1 -> Subject:sport
It returns: Location: /user/joe/quiz/1/question/1
The user answers:
PUT /user/joe/quiz/1/question/1 -> Answer B
Now rinse and repeat.
The resources we've got:
Users
Quiz for a user
Questions in a Quiz (The question is updated with an answer)
I would remove /user/joe from the routes entirely. You can get the current_user using Devise, Authlogic, or some other authentication framework.
Otherwise this looks okay to me, as it's only two nests which is readable enough. So you'd have:
GET subjects/sports/questions/1
POST subjects/sports/questions/1 # pass along params with {:answer => 'B'}
GET subjects/sports/answers/1

Prevent modification ("hacking") of hidden fields in form in rails3?

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.

How to stick with REST?

I'm basically putting together an app that does a super simple question => answer test in an attempt to learn more about rails.
ie:
Question: "What's your dog's name?"
Answer: "Doggington"
I have a Question model with two simple attributes:
question:string
correct_answer:string
My struggle here is how do i apply REST principals to this -- specifically, when i am checking a user's input(answer) to see if he got the question right or not.
I'm not sure if i should do something like modify the "show" method (or any other action) to accept values for answer posted to it... and it SEEMS like I should create a new method in my questions_controller called "verify_answer" or something a long those lines.
This breaks REST.
What do you think?
thanks!
AnswersController#create should accept the answers. Whether or not this controller actually has a related Answer model is irrelevant. One action should never perform two actions. For instance, if your QuestionsController#show both displays the question, and accepts a :put or :post with the answer to the question then you are breaking basic rails design principals.
Note that your routes file might very well look like this:
resources :questions do
resource :answer
end
Which will expose the /questions/8/answer route that you can :post to, which will go to AnswersController#create.
Off the top of my head I'm forgetting the exact name of the helper url method you can use to generate the url. Something like question_answer_path(#my_question).
This routing file is for rails3, which I assume is what you're using since there's no reason to use anything else if you're starting a new app in my opinion :p
If you do have an Answer model (maybe you want to store users' answers and look at them later or aggregate them and come up with statistics and such) then you should change the router to use resources :answer instead of the singular version.
For more information about routing and some RESTful tips you should visit the Ruby on Rails guide for routing found here: http://guides.rubyonrails.org/routing.html
I'm on an editing spree! There are times when you might need to add an additional method to your Questions controller that isn't strictly REST. This isn't necessarily considered bad practice but just make sure you look at your decisions and find out if you aren't actually just hiding the existence of another resource. I don't consider this to be one of those times as explained above :)

Referral program - cookies and more (Rails)

I'm building a referral program for my Ruby on Rails app, such that a user can share a link that contains their user ID (app.com/?r=ID). If a referrer ID is present when a visitor lands on app's homepage, the signup form on the homepage contains a hidden field that populates with the referrer's ID. The controller then detects the ID and creates a new referral in a referral table if the referred visitor signs up. It works, and here's that chunk of code:
#referrer = User.find(params[:r]) rescue nil
unless #referrer.nil?
#referral = Referral.new(:referrer_id=>#referrer.id)
end
Pretty simple stuff, but it's pretty easy to break (ex: if visitor navigates away from the homepage, referrer ID is lost). I feel like cookies could be a more robust method, where a cookie containing the referrer's ID is stored on the referred user's computer for x days. This is pretty commonplace, especially with affiliate programs like Groupon, but I have never worked with cookies and have no idea where to start.
Also, is there any good way to mask or change the URLs of the referral system? Instead of having app.com/?r=1842, I would prefer something like app.com/x39f3 <- a randomly generated sequence of numbers associated with a given user, without the ?r= portion.
Any help is greatly appreciated. Thanks!
To answer the cookie question, it's quite easy to set them:
cookies['app-referrer-id'] = params[:r]
And then it's the same format to read them back (but without the assignment). I would suggest putting this code in a before_filter in your application controller. This way, the cookie will be set irrespective of the page on which your visitor first lands on your site.
With regards to changing the structure of the urls to the suggested format, you would need to have the referral codes match a specific pattern, otherwise you are likely to run into routing problems. If, for example, they matched the format of 3 letters followed by three numbers, you could put the following your routes file:
match '/:referrer_id' => 'app#index', :constraints => {:referrer_id => /[a-zA-Z]{3}[0-9]{3}/}
The reference to app#index should be changed to the controller in which you handle referrals and you can access the referrer_id through params[:referrer_id].
Hope this is of some use.
Robin

Resources