Register

If this is your first visit, please click the Sign Up now button to begin the process of creating your account so you can begin posting on our forums! The Sign Up process will only take up about a minute of two of your time.

Results 1 to 10 of 10
  1. #1
    Senior Member
    Join Date
    Jun 2005
    Location
    Atlanta, GA
    Posts
    4,146
    Member #
    10263
    Liked
    1 times
    [h1]A Ruby on Rails Tutorial: Blogification, Part 2[/h1]
    If you don't know what this tutorial is, you should have a look at the first part of it, first. As a reminder, a working example of the latest iteration of this tutorial can be found at http://blogification.savedfastcool.com/ . If you feel like playing around, at least for now a username `savedfastcool' and password `test' will get you in.

    Now, what we'll be covering is this part is:

    * Authentication (with password hashing via MD5 -- nothing too complicated; go to the article to read about the potential security issues with MD5 hashing).
    * Updating the post interface to be in line with the authentication (i.e., no selecting which user posted anymore).

    [h2]Authentication: Giving Users Passwords[/h2]
    So, we need to develop some sort of authentication mechanism. This requires a couple of things, but the first one is definitely support in the database. Currently, the database's `users' table only has the username and actual name. We need to add a password field. Since we've decided to use MD5, we know the value stored in the database will be 128 characters long, so we'll go ahead and make the password field a 128-length varchar.

    Once, we've got that, we need to hook up access to it in our User model. Or... Do we? In fact, we don't. The User model automatically gets access to the new password field because we've just added it in the database. Well... That's made life easier. But we do run into an issue. If we set a user's password, we need to set the password itself to the MD5 of the password string, instead. This means that only the User model will know about the MD5ing of the password. Which is a good thing, right?

    So, we need to make the setter for a user's password convert it to MD5ed form. The first thing we need to do is to include the relevant library for MD5 checksuming. Put this at the top of [minicode]user.rb[/minicode]:
    Code:
    require 'digest/md5'
    Then, we need to do the actual conversion. Easy enough; we add this code to the User model:
    Code:
        def password=(password)
            write_attribute :password, Digest::MD5.hexdigest( password )
        end
    write_attribute sets the model attribute that is specified to the value that is specified. In Ruby, as we've mentioned before, everything is an object. Thus, we create a new instance of the MD5 object with the given password, and convert it into a string.

    Now, say we want to test this code, but we don't want to run the page. It'd be nice if we could do that, right? Well, guess what... Yep, Rails allows for this, too. If you go to the root directiorn ([minicode]blogification/[/minicode]) of your site, you can run [minicode]ruby script/console[/minicode]. That will load up a development environment where you can play with your models and controllers easily, just like the regular Ruby irb shell. So, let's do that, and check our password function:
    Code:
    >> user = User.new
    >> user.password = 'mypassword'
    => "power"
    >> puts user.password
    34819d7beeabb9260a5c854bc85b3e44
    => nil
    Yep, exactly what we wanted. Awesome! So now we have full support for passwords in our User model. The next step is to add support for it in the UserController, since that's where we create users.

    Our User class is still using scaffolding, but we'll get rid of that really quick and put in our own code. Because most of this is identical to the code we put in the Post controller, we'll just include it below:
    Code:
        def new
        end
    
        def edit
            @user = User::find_by_id params[:id]
        end
    
        def list
            @users = User::find_all
        end
    
        def destroy
            user = User::find_by_id( params[:id] )
            user.destroy if user != nil
    
            redirect_to :action => 'list'
        end
    
        def create
            user = User.new params[:user]
            user.save
    
            redirect_to :action => 'list'
        end
    
        def update
            @user = User::find_by_id params[:id]
    
            if @user.update_attributes( params[:user] )
                redirect_to :action => 'list'
            else
                render :action => 'edit'
            end
        end
    As you see, this code is essentially identical to the code in the PostsController. Note that we added no special handling for the password. This is because the `create' method will create the user with the parameters in the constructor, and the constructor automatically calls our mutator, so the MD5 is applied without any outside knowledge. Now that's encapsulation!

    When we create our form partial, however, we'll have to keep in mind the fact that we now need a password input field. Which means there will be some additional handling in the update method -- we need to check if the password field isn't blank, and only then should we set the password field. Otherwise, we might accidentally set the password to the hash of `'! So, we'll add some checking at the beginning of the update method, right after we fetch the user:
    Code:
            params[:user].delete :password if params[:user][:password] == ''
    Here, we ask the hash to delete the password entry in the hash if the password entry is blank. Pretty easy one-liner, right?

    Now, on to the views for the user. Since most of these are the same as those for the posts, I'll let you adapt those existing views for this. The only view of true interest as far as modifications are concerned is the _form partial. This one, we'll make look something like this:
    Code:
    <div class="formLine">
        <label for="user[username]">Username:</label>
            <%= text_field 'user', 'username' %>
    </div>
    
    <div class="formLine">
        <label for="user[name]">Name:</label> <%= text_field 'user', 'name' %>
    </div>
    
    <div class="formLine">
        <label for="post[password]">Password:</label>
            <%= password_field 'user', 'password' %>
    </div>
    
    <div class="formButtons">
        <%= submit_tag action %>
    </div>
    Pretty easy, right? The problem we run into is if we go to edit a user. If you notice, the password field is prepopulated with the existing value of the password. The problem is, that value is an MD5 hash. In order to not change the password, we have to blank out the field. To get the desired effect, we need to consider whether anyone really needs to have access to our password. Let's assume the answer is yes. Then we need to deal with this issue in the view. What we need, is to set an initial value of `' for the field. We can do that pretty easily:
    Code:
            <%= password_field 'user', 'password', { :value => '' } %>
    Once again, that's it. No more is needed.

  2.  

  3. #2
    Senior Member
    Join Date
    Jun 2005
    Location
    Atlanta, GA
    Posts
    4,146
    Member #
    10263
    Liked
    1 times
    [h2]Adding A Login Form[/h2]
    The next step is adding a login form to the system. Where should we put this form? You can associate this however you want; to me, however, it makes sense to associate the login form with the UserController -- specifically, make it a login action on the UserController. Logging in is a two-step process -- showing the form and then authenticating the user. So we're actually going to split this up into two different actions: login and authenticate. As per usual, the first thing we need to do is create the [minicode]login[/minicode] method in the UserController:
    Code:
        def login
        end
    Do we need anything from the login method? All it has to do is display the login form, so not really. This is just so Rails knows that there *is* a login method.

    Now, we create our login view in [minicode]app/views/user/login.rhtml[/minicode]:
    Code:
    <% form_for :user, nil, :url => { :action => 'authenticate' } do |frm| %>
        <div class="formLine"><label for="user_username">Username: </label>
            <%= frm.text_field :username %></div>
        <div class="formLine"><label for="user_password">Password: </label>
            <%= frm.password_field :password %></div>
    
        <div class="formButtons"><%= submit_tag 'Login' %></div>
    <% end %>
    Here, we see a different way of building forms. Instead of using the [minicode]*_field[/minicode] functions, we use the [minicode]form_for[/minicode] function and pass it a block. Inside this block, we can use the variable `frm' and ask it to create text fields, password fields, etc, for the various properties. Amongst other things, this means we don't have to repeat ourselves concerning the model name `user' everywhere. Notice that we passed `nil' in as the second argument; if we had a User model with initial values for these fields, we would put it there.

    Now, we can write our authenticate method:
    Code:
        def authenticate
            authedUser = User::check_credentials params[:user][:username],
                                                 params[:user][:password]
    
            if not authedUser
                flash[:error] = 'Error authenticating user.'
                redirect_to :action => 'login'
            else
                redirect_to :controller => 'post', :action => 'list'
            end
        end
    So here we're checking whether the user is authenticated by calling the User model's [minicode]check_credentials[/minicode] method. Now, while Ruby on Rails is awesome, it's not *that* awesome -- we do have to write this one. First, though, let's talk about `flash'. Flash provides a one-shot place to put a message -- for example, an error. It's used just like any hash, and can be accessed from the View. Anything inside that hash lasts only until the next view is displayed.

    Now, let's write the User model's [minicode]check_credentials[/minicode] method in the User model:
    Code:
        def self::check_credentials(username, password)
            user = find_by_username username
    
            return user if user != nil and user.password == MD5.new( password ).to_s
        end
    Fairly simply, we look up the user, and, if we find him or her, we return the user if the password matches.

    [h2]Tying Authentication In[/h2]
    So now we have a way to log in, and we have a login form. But the question is how we bridge the gap between the pages that require logins and the login form. Consider how nice it would be if, just as we can tell a user it has many posts, we could tell a controller that the `edit', `update', `create', `delete', and `new' actions all required the user to log in. Basically, imagine if we could add this line to the controller:
    Code:
        requires_auth :edit, :update, :create, :delete, :new
    And have everything taken care of, including redirects and everything. It'd be great, wouldn't it? Well, it turns out... You can.

    Every time you write a controller, you start it off with the fun snippet [minicode]< ApplicationController[/minicode] -- inehrits ApplicationController. Well, you might have noticed there's an [minicode]application.rb[/minicode] file sitting in your controllers directory. If you open that up, you'll see that ApplicationController class sitting right there. And we can add methods to it. And our controllers can get to them. Can you see where this is going?

    We'll start by writing the requires_auth method in our ApplicationController:
    Code:
        def self::requires_auth( *actions )
            before_filter :check_auth, :only => actions if actions.length > 0
            before_filter :check_auth if actions.length == 0
        end
    We define [minicode]requires_auth[/minicode] as a static method, since we'll need to access it outside an object instance. All requires_auth does is register a before_filter for the specified actions, or, if no actions are specified, it registers a before_filter for all actions. before_filters are methods that run before the action runs. In our case, we want to check if the user is authenticated; if they aren't, we want to redirect them to the login page. We also want the login page to be able to direct them back where they came from.

    So now that we know what we need, we can write the check_auth method in our ApplicationController:
    Code:
        def check_auth
            return if session[:user]
    
            session[:redirect_back] = { :controller => controller_name,
                                        :action => action_name }
    
            redirect_to :controller => 'user', :action => 'login'
        end
    Here, we return if the user variable is set in the session -- meaning the user has already been authenticated. Otherwise, we set the redirect_back variable in the session so we know where we came from, then redirect to the login action on the UserController.

    Now, we have to revise the succesful logging in in the User controller. In good OO style, we'll keep everything dealing with redirecting for logging in in one place -- the ApplicationController. Thus, when we succesfully log in, we'll redirect to the logged_in action (which the UserController will have because the ApplicationController will have it), and then write logged_in in the ApplicationController. Thus, our code changes from:
    Code:
                redirect_to :controller => 'post', :action => 'list'
    To:
    Code:
                redirect_to :action => 'logged_in'
    We also have to register the user in the session, so we add this line if the user was logged in succesfully (before the redirect_to, just to be consistent):
    Code:
                session[:user] = authedUser
    Now, we can write our logged_in method:
    Code:
        def logged_in
            redirect_to session[:redirect_back]
    
            session[:redirect_back] = nil
        end
    All it does is redirect to the redirect_back variable we'd set before, and then set that variable to nil. Notice that this means redirect_to doesn't instantly run the redirect. In fact, if you wanted to redirect instantly, you would have to write something like [minicode]redirect_to :action => 'myaction' and return[/minicode] to ensure that the method returns immediately afterwards.

    The final touch to this is to add the actual requires_auth specification to the PostController, which we do right after the controller declaration:
    Code:
        requires_auth :new, :edit, :update, :create

  4. #3
    Senior Member
    Join Date
    Jun 2005
    Location
    Atlanta, GA
    Posts
    4,146
    Member #
    10263
    Liked
    1 times
    [h2]Modifying the Post Form[/h2]
    Now we want to make sure that the form that users post through doesn't give them the option to pretend someone else posted it. So, we need to get rid of that awesome dropdown we developed in part I of the tutorial. I know, it sucks, but we'll be able to use similar techniques for other things later, so we'll live. So first things first, open the post [minicode]form[/minicode] partial and get rid of this bit of magic:
    HTML Code:
    <div class="formLine">
        <label for="post_user_id">User:</label>
            <%= select 'post', 'user_id',
                    @users.collect { |usr| [ usr.name, usr.id ] } %>
    </div>
    Now, we need to update our controller to deal with the renewed dearth of user information. The first place to look is the `create' method. Currently, the magic part is this one:
    Code:
            post = Post.new params[:post]
    The problem is, now params[ost] doesn't have user information with it. But it isn't horribly difficult to get it, right? If the user is logged in (and remember, we've specified they have to be via our awesome requires_auth function), then we know that the [minicode]session[:user][/minicode] variable has a reference to the current user. Thus, we can pull the id from there. So, we do our little magic, and change the above line to:
    Code:
            params[:post][:user_id] = session[:user].id
    
            post = Post.new params[:post]
    And voila! Now, we have another item we need to worry about; namely, updating. But will we ever update *who* created a post? Not really. So we actually don't have to modify the update method at all, since a call to update_attributes will only update the attributes that are present in the specified hash.

    The last bit we need to do, then, is eliminate the lookups of users for the `new' and `edit' actions; remember, we don't need to know all the users in the database, because the only relevant one is the one that's currently logged in. Nuke the lines that look like this:
    Code:
            @users = User::find_all
    [h2]Would that Lists Knew About Us[/h2]
    So now everything knows about what we're doing, right? Well... Sort of, but not quite. The `list' interface still shows all posts, and gives us the option to edit all posts. That's just no good. Moreover, the `edit' method still lets us magically edit posts that aren't ours! Scandalous!!

    First things first, being able to *see* all posts isn't a problem, it's giving all of the options for all posts that's a problem. In fact, it'd be nice if a simple listing of the posts just showed us the posts in a bloggish format, and there was an additional list that showed us only our posts and gave us the ability to edit them.

    In fact, what we would like is to be able to go to [minicode]/post/list[/minicode] and view all posts, and then go to [minicode]/post/mine[/minicode] and view ours. Since the interface to /post/mine is, essentially, the one that is currently under /post/list, we can rename our action and our view to `mine' and `mine.rhtml', respectively, and have that one working, more or less.

    We also want to only display our own posts, however, in the mine action. So, we change:
    Code:
            @posts = Post::find_all
    To what should now be the familiar:
    Code:
            @posts = Post::find_all_by_user_id @session[:user].id
    We'll also change our no-posts-found message a little bit, from `There are currently no posts in the database' to `You haven't posted anything yet!'

    Now, we'll redo the list display. The action can remain at a good ol' `@posts = Post::find_all'. The view will have to change a little, though. Here's a new list.rhtml:
    HTML Code:
    <% if @posts == nil || @posts.length == 0 %>
    
    <div class="information">There are no blogified posts yet!</div>
    
    <% else %>
    
    <% for post in @posts %>
    <div class="post">
        <h2 class="title"><%= post.title %></h2>
        <div class="author">Posted by <%= post.user.name %></div>
        <div class="content"><%= post.content %></div>
    </div>
    <% end %>
    
    <% end %>
    We'll tag a little bit onto our CSS file, too:
    Code:
    div.post .title
    {
        font-size: 16pt;
        font-weight: bold;
    
        margin-bottom: 0px;
        border-bottom: 1px dotted black;
    }
    
    div.post .author
    {
        color: rgb(100,100,100);
    
        font-size: 10pt;
    
        margin-bottom: 1em;
    }
    
    div.post .content
    {
        margin-bottom: 1em;
        border-bottom: 1px solid rgb(100,100,100);
    }
    Ok, fantastic. Now we have our two interfaces. We need to make sure that [minicode]/mine[/minicode] is only accessible when you're logged in, though; thus, we add :mine to the list of actions requiring authentication.

    [h2]Securing Edits[/h2]
    One last step is left, and that's securing edits so that a user can't do a submarine edit of a post that isn't theirs (for example, by pointing their browser to the appropriate ID without clicking on a link). This is fairly trivial, but we'll do it action by action. First, to the edit action, we add the following code at the end:
    Code:
            if session[:user] != @post[:user]
                flash[:error] = "Attempted to edit someone else's post."
                redirect_to :action => 'mine'
            end
    Now, you might have noticed that we accessed the user here as @post[:user] instead of @post.user. It turns out, Rails lets you do both (ActiveRecord overloads the [] operator to allow that to happen). In this case, I used this form so that the two ways of accessing the user appear similar. Mostly a cosmetic choice, really.

    Now, to the update action, we add the same code right after we fetch the post (and *before we update*). And, finally, the destroy action gets almost identical code right before the `destroy' method is run:
    Code:
            if post != nil and session[:user] != post[:user]
                flash[:error] = "Attempted to delete someone else's post."
    
                redirect_to :action => 'mine'
            end
    In fact, let's take this opportunity to add some error-checking, too; if we run into a case where we don't find a post specified as being updated, edited, or destroyed, we can do this:
    Code:
            if @post == nil
                flash[:error] = "Post not found."
    
                redirect_to :action => 'mine' and return
            end
    That way, we don't run into nil objects where we don't expect them.

  5. #4
    Senior Member
    Join Date
    Jun 2005
    Location
    Atlanta, GA
    Posts
    4,146
    Member #
    10263
    Liked
    1 times
    [h2]Last Step: Cosmetic Changes[/h2]
    Finally, we're going to make a few little cosmetic changes here and there. Cosmetic in terms of visuals as well as in terms of code.

    First of all, create, destroy, and update still redirect to /list. In our new structure, it makes more sense if they redirect to /mine, so we'll change that. I'm pretty sure you can figure this one out on your own.

    The second issue that needs to be fixed is that of logging in. Currently, if a user goes to, say, /post/new, they're redirected to the login page and, when they log in, back to their original page. But if they just go direction to /user/login, there's nowhere to go back to. We fix this by saying that, if there's nowhere to go, the newly logged in user gets sent to /post/mine. We can do this by adding this code at the beginning of the logged_in method in the ApplicationController:
    Code:
            redirect_to :controller => 'post', :action => 'mine' and return \
                    if session[:redirect_back] == nil
    Notice the \ at the end of the line. This line is perfectly legal without the `if' attached to the end. Thus, we need to tell Ruby explicitly that the line continues, which is what the \ does. Here, we redirect the user to /post/mine if the session variable with the redirection URI is not set.

    The third issue we need to fix is that, although we're flashing errors, they're not getting displayed anywhere! We can fix this by adding something to our `post' layout in /app/views/layouts/post.rhtml, right after the page title (h1):
    HTML Code:
    <div class="error">
        <%= flash[:error] %>
    </div>
    Coupled with some artful CSS:
    Code:
    div.error
    {
        color: rgb(221,74,74);
    }
    
    div.information
    {
        color: rgb(0,128,0);
    }
    We can add the above HTML to our user.rhtml layout, as well, so that layout can also display errors.

    Our final modification is a simple one: currently, /post/list displays all posts. Wouldn't it be nice if /post did that, too? So let's make it happen. It turns out, when Rails runs into just /post/, it's essentially an alias for /post/index. There are ways other than what we're about to do to make this work as we want it to, but we won't go into those for now. Right now, we're just going to implement an `index' action that simply redirects to the `list' action:
    Code:
        def index
            redirect_to :action => 'list'
        end
    [h2]Conclusion[/h2]
    So that's it! What's next? Well, next time we might talk about how we can view individual posts, and how we can make shiny URIs for them (made up of, say, the title and the id, for example). These URIs are all search-engine-friendly and stuff. We'll also, by consequence, talk about the magic that is `routes', and how to use them to play with the way our URIs work.

  6. #5
    ljm
    ljm is offline
    Senior Member ljm's Avatar
    Join Date
    Aug 2006
    Location
    Manchester, England
    Posts
    284
    Member #
    13684
    Liked
    1 times
    Just been going through this tutorial now (and it's been great so far! =D), and I'm getting some error with the password bit at the start. I'm not sure what's wrong or what to look for, but I'm getting the following when I test the thing out:

    Code:
    >> user = User.new
    => #<User:0x3356c24 @new_record=true, @attributes={"name"=>nil, "username"=>nil, "password"=>nil}>
    >> user.password = 'something'
    NameError: uninitialized constant User::MD5
            from /usr/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:477:in `const_missing'
            from ./script/../config/../config/../app/models/user.rb:5:in `password='
            from (irb):10
    Any ideas? The only thing I've not followed to the letter is using migrations to sort out the database, and that error is totally unrelated to all that. Is there something that needs to be defined elsewhere?

  7. #6
    Senior Member
    Join Date
    Jun 2005
    Location
    Atlanta, GA
    Posts
    4,146
    Member #
    10263
    Liked
    1 times
    As I recall, this changed in some version of Ruby 1.8.* Try putting this line at the top of user.rb:
    Code:
    require 'digest/md5'
    And change the [minicode]password=[/minicode] method to:
    Code:
    def password=(password)
      write_attribute(:password, Digest::MD5.hexdigest(password))
    end
    This just uses a different class from the other example. I believe the straight-up MD5 class got tossed out at some point, and replaced by Digest::MD5. Let me know if this works and I'll update the tutorial, though it's already a little outdated.

  8. #7
    ljm
    ljm is offline
    Senior Member ljm's Avatar
    Join Date
    Aug 2006
    Location
    Manchester, England
    Posts
    284
    Member #
    13684
    Liked
    1 times
    Oh that works wonderfully. Thanks a lot! I'm finding this tutorial a lot more helpful than others I've seen.

  9. #8
    Senior Member
    Join Date
    Jun 2005
    Location
    Atlanta, GA
    Posts
    4,146
    Member #
    10263
    Liked
    1 times
    Yeah, I feel like I should actually do the next part, which I never got to do last summer. Just too many other things going on. These first two parts also need some updating to match the REST stuff that happened in rails 1.2 and some of the shiny magic i've discovered in the meantime. I'm a fan of not succumbing to NIH syndrome, and I did a lot of that here (admittedly this was because I didn't know there were existing implementations). Then again, some of the stuff I implemented separately (like authentication) is a great way to describe things like filters.

  10. #9
    ljm
    ljm is offline
    Senior Member ljm's Avatar
    Join Date
    Aug 2006
    Location
    Manchester, England
    Posts
    284
    Member #
    13684
    Liked
    1 times
    I'd certainly love to see a third part, but I can see that it would take a fair bit of time to write, looking at the length of the two parts you've done already.

    I worried that it'd be out of date, such is the fast advancement of Rails, but the only thing I improvised on was database migrations to set things up. That in itself was confusing, since there's not much in the way of tutorials for that either!

    I've definitely found Ruby (and Rails) a lot more daunting to get into than, say, PHP, mind. These tutorials are very helpful.

  11. #10
    Senior Member
    Join Date
    Jun 2005
    Location
    Atlanta, GA
    Posts
    4,146
    Member #
    10263
    Liked
    1 times
    Actually, as it happens, I've already started rewriting it on my blog. I'm hoping to have Part II out the door tomorrow. This is based on the same concept (writing a blog), but is going into a lot of what I've learned about Rails over the past year, including the concept of Behavior-Driven Development and the awesomeness that is RSpec. I definitely totally dropped the ball on migrations, not sure what I was thinking there. It may be that at the time I wrote it, I didn't know about them (=-O) Anyway, yeah, this second iteration of it will definitely cover them. I'm also hoping that it will cover a ton of useful plugins that I've found over the past year that can greatly simplify life.


Remove Ads

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
All times are GMT -6. The time now is 10:17 PM.
Powered by vBulletin® Version 4.2.3
Copyright © 2019 vBulletin Solutions, Inc. All rights reserved.
vBulletin Skin By: PurevB.com