Jump to content

The ultimate community for Ruby on Rails developers.


Photo

Is it okay to use non-REST actions in Controllers?


  • Please log in to reply
12 replies to this topic

#1 Rowel

Rowel

    Controller

  • Members
  • 109 posts

Posted 09 September 2013 - 08:57 AM

On the tutorials I've seen, I always see the Controllers always having the following methods/actions.  Not all may be present, but it's always the same gang of characters: 

list
show
new
create
edit
update
delete

Is it okay to use my own methods/actions in the controller?

 

Here's the scenario....

1. User registers on my site, I send an email message to him to validate if his user registration/email address is real or bogus.

2. User clicks on a URL on his email message, with a security token.

3. My app services this request, validates the security token, and updates User record.  Display welcome screen.

4. If security token is bad, or user does not exist, then display error screen. 

class ValidationController < ApplicationController
  def update
     # get validation code from URL, and validate against the database
     # if matched, set validated_at date and validated to TRUE
     @user = User.find_by(validation: params[:c])

     if @user 
       # user found, then update_attribute (instead of update_attributes) so no model validation performed, i.e. no need to fill-out password and password_confirm fields
       @user.update_attribute(:validated, true)
       @user.update_attribute(:validated_at, Time.now) 
       send_welcome_email

       # display welcome webpage to user 
       render :success
     else
       # user not found, probably invalid code or user deleted from database
       render :fail
     end
  end

  def success
  end

  def fail
  end 

  #------------------ P R I V A T E ---------------------------
  private
    def send_welcome_email
      UserMailer.welcome_email(@user).deliver
    end 
end 

In my routes.rb, I have

  # Validation Pages
  match '/validate',              to: 'validation#update',    via: 'get'
  match '/validation_success',    to: 'validation#success',   via: 'get'
  match '/validation_fail',       to: 'validation#fail',      via: 'get'

Then of course, I have /views/validation/success.html.erb  and  fail.html.erb  for my display pages. 

 

Everything seems to work fine but I just want to make sure I'm not breaking any convention by not using the typical --

list

show
new
create
edit
update
delete methods. 

 

Now, if I'm understanding the REST concept correctly, I shouldn't be updating my Model because I used 'GET'... and that one should use POST if you want to update or make changes to the Model record.  

 

But you can't send a POST from clicking on a URL link in an email message.  So I definitely broke that rule. 

 

Is there another way of doing this whole scheme? 

 

 



#2 Jamie

Jamie

    Controller

  • Moderators
  • 114 posts
  • LocationThe UK

Posted 09 September 2013 - 12:40 PM

I often get caught up asking myself questions like this and most of the time the response is along the lines of.. "If it works, then leave it how it is".

 

I would say the same about your question. I would probably try sticking to convention, for example a create method, I would cal it create and now /create_a_new_record in the controller but in the Routes change it to that. I guess it's easier for a 3rd party developer to look at your code and go to the routes to understand what the flow is..


Rails developer based in Newcastle, UK.
Web app owner - Twitter lover

#3 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 09 September 2013 - 07:08 PM

But you can't send a POST from clicking on a URL link in an email message.  So I definitely broke that rule.

 

 

You should NEVER use anything other than a form (button_to, submit_tag, form_for etc) to send data back to a web server that will update the state of the server. This is a rule of the web not a rule of Rails.

Many people do it, and there are many sites out there that suffer as a result of doing it, often without the developer even knowing or caring.

But you will be pretty annoyed when all the bots start crawling your pages and clicking links that send nothing back to your server and you start seeing hundreds of errors in your log files and you wonder why you are not getting the best SEO experience, I could go on and on and on and still not even touch on the security vulnerabilites, but I won't. Just don't do it!


  • Kelli Shaver likes this

Programming is just about problem solving!


#4 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 09 September 2013 - 07:14 PM

Now, if I'm understanding the REST concept correctly, I shouldn't be updating my Model because I used 'GET'... and that one should use POST if you want to update or make changes to the Model record.

 

 

I think the latest thing in Rails 4 is to use a PATCH request.

 

http://weblog.rubyon...od-for-updates/


Programming is just about problem solving!


#5 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 09 September 2013 - 07:19 PM

If I find yourself updating a scaffolded controller I usually stop and think twice.

Before filters and respond_to blocks and things like forcing SSL are fine but I usually even then find myself removing these things to a base controller of some description.

 

But not always!

 

There are rare occasions where it is perfectly reasonable to have a non CRUD controller, such as a reports controller.

 

The caveat with rails is, if it's not easy, you are probably doing something wrong.

 

I prefer to work with Rails rather than fight it. It's much more fun and productive that way.


Programming is just about problem solving!


#6 Rowel

Rowel

    Controller

  • Members
  • 109 posts

Posted 09 September 2013 - 07:56 PM

You should NEVER use anything other than a form (button_to, submit_tag, form_for etc) to send data back to a web server that will update the state of the server. This is a rule of the web not a rule of Rails.

 

 

Here's the email I send to the registered user, using the email address he submitted. 

 

Please click on this link to complete your user registration. 
http://my-domain-goe...bd7f5c77f879cf8 
You may also copy & paste the above link to your browser.

 

So user receives the above email, clicks the above link.   (which will be a GET operation) 

On the website, validate will look for this code in the User database, and if found, update validated to true, etc...  via def update

def update
...
...
end 

You can't send a PATCH command from a clicked URL linked, right?  So naturally, I have to update the record via GET because the submission didn't happen on a web browser. 

 

also this....

def success
end

def fail
end 

Okay to have these functions in my controller?  They're stubs so I can have a success.html and fail.html  views.   



#7 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 09 September 2013 - 09:36 PM

You can't send a PATCH command from a clicked URL linked, right?

 

 

Right!

 

http://railscasts.co...-reset-password

 

Should give you a good example of the work flow


Programming is just about problem solving!


#8 Rowel

Rowel

    Controller

  • Members
  • 109 posts

Posted 09 September 2013 - 09:58 PM

http://railscasts.co...-reset-password

Should give you a good example of the work flow

 

Thanks for the link.  

 

So he did something similar, but he called the edit function instead because he has to prompt for a password in a web form, and then he called update from that form (via POST).  

def edit
@user = User.find_by_password_reset_token!(params[:id])
end

I'll just leave my update as a :get since I don't need/require any user interaction (web form) and I won't have any chance to call an update as a post.  



#9 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 09 September 2013 - 11:25 PM

What's to stop someone from getting in between the requests, finding out the token and wreaking havoc on the site?

Just make sure the url expires and you use https with a valid ssl cert and you should be fine so long as you send the url as an https url e.g.

https://everybodypray.com/validate?c=918121a0519c6b3117c41ac1cbd7f5c77f879cf8

Programming is just about problem solving!


#10 Kelli Shaver

Kelli Shaver

    Inspector

  • Administrators
  • 75 posts
  • LocationKentucky

Posted 09 September 2013 - 11:28 PM

I think the latest thing in Rails 4 is to use a PATCH request.

 

PATCH is the RESTful way to do it, though a lot of people use PUT as well and Rails 4 will accept PUT requests for updates (as it should). Technically, PUT is equivalent to "update or create."

 

The HTTP verbs that REST uses don't always translate well to interactions with a rendered HTML page, largely due to the lack of browser support for them, but for the most part, they work. That said, you often need to do things in the browser that don't translate well to a RESTful design - like resetting a password. 

 

So, my take is this: respect HTTP verbs as much as possible when performing controller actions, but the default RESTful controller layout will almost always need to be extended. Basically, don't confuse the verbs and their proper usage with the design pattern.

 

(Building REST APIs and the clients that use them is how I spend about 80% of my time these days - a surprising amount of logic that you'd think would belong in the API ends up actually being client-specific.)



#11 Rowel

Rowel

    Controller

  • Members
  • 109 posts

Posted 09 September 2013 - 11:51 PM

 

What's to stop someone from getting in between the requests, finding out the token and wreaking havoc on the site?

Just make sure the url expires and you use https with a valid ssl cert and you should be fine so long as you send the url as an https url e.g.

https://everybodypray.com/validate?c=918121a0519c6b3117c41ac1cbd7f5c77f879cf8

 

The token above doesn't log them in the site.  The intent was if somebody clicked on this link, they presumably got the email - which means the email address he entered was valid and not junk. 

 

If a man in the middle attack occurs and they somehow got a copy of the above link, they'll just be confirming the validity of the email address. But the man in the middle still wouldn't know the password and doesn't gain login authorization to the site.  


  • Kelli Shaver likes this

#12 Rowel

Rowel

    Controller

  • Members
  • 109 posts

Posted 09 September 2013 - 11:55 PM

So, my take is this: respect HTTP verbs as much as possible when performing controller actions, but the default RESTful controller layout will almost always need to be extended. Basically, don't confuse the verbs and their proper usage with the design pattern.

 

 

Can you expand more on this concept?  "the RESTful controller will almost always need to be extended"....  Extended as in what?  Size? or additional verbs?  

 

Thanks all for the discussion, James, Kelli! 



#13 Kelli Shaver

Kelli Shaver

    Inspector

  • Administrators
  • 75 posts
  • LocationKentucky

Posted 11 September 2013 - 03:03 PM

As in, you'll almost always need more actions in your controller than the default ones mapped to the HTTP verbs. Two good examples:

users/reset_password
users/verify

Neither of those map well to a show/edit/update action. You could make it work with routing and before filters, but it would get very messy. The same with, for instance, a PUT to a bulk update action, where you're not passing IDs in the URL because you need to update a lot of records at once.

 

Better, I think, to have separate controller actions for them.


  • Rowel likes this




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users