ITPub博客

首页 > 应用开发 > IT综合 > Rolling with Rails 2.0

Rolling with Rails 2.0

原创 IT综合 作者:333111444 时间:2008-02-22 10:04:42 0 删除 编辑

Let’s get started!

Recognizing the Environment

This tutorial is geared towards those who already have some knowledge of Rails 1.2. Please refer to the many great Rails tutorials around 1.2 available in the Internet world-wide.

The first think you have to do is update your gems:


sudo gem install rails --include-dependencies

You may probably need to update RubyGems as well:


sudo gem update --system

First things first. Let’s create a new Rails application:

[@more@]

rails blog

This will create our usual Rails folder structure. The first thing to notice is the environment: we now have this main structure:

  • config/environment.rb
  • config/initializers/inflections.rb
  • config/initializers/mime_types.rb

Everything that inside the config/initializers folder is loaded at the same time the original environment.rb is, and that’s because when you’re using several different plugins and gems in your project, the environment.rb file tends to become cluttered and difficult to maintain. Now we have an easy way to modularize our configuration.

Database

The second thing that we have to do is configure our databases. This is done the same way as before at config/database.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

development:
  adapter: mysql
  encoding: utf8
  database: blog_development
  username: root
  password: root
  socket: /opt/local/var/run/mysql5/mysqld.sock

test:
  adapter: mysql
  encoding: utf8
  database: blog_test
  username: root
  password: root
  socket: /opt/local/var/run/mysql5/mysqld.sock

production:
  adapter: mysql
  encoding: utf8
  database: blog_production
  username: root
  password: root
  socket: /opt/local/var/run/mysql5/mysqld.sock

Notice that now you have a ‘encoding’ options that’s set to UTF8 by default. The Rails app itself loads up with KCODE = true by default as well, meaning that it silently starts with Unicode support already, which is great. But that ‘encoding’ configuration has a new usage as well: everytime Rails connects to the database it will tell it to use this ‘encoding’ setting. Like issuing a ‘SET NAMES UTF8’.

One trick that we can do to DRY up our database.yml is this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

defaults: &defaults
  adapter: mysql
  encoding: utf8
  username: root
  password: root
  socket: /opt/local/var/run/mysql5/mysqld.sock

development:
  database: blog_development
  <<: *defaults

test:
  database: blog_test 
  <<: *defaults

production:
  database: blog_production
  <<: *defaults

Much better. We have new Rake tasks as well. And some of them are related to the database:

db:charset Retrieves the charset for the current environment’s database
db:collation Retrieves the collation for the current environment’s database
db:create Create the database defined in config/database.yml for the current RAILS_ENV
db:create:all Create all the local databases defined in config/database.yml
db:drop Drops the database for the current RAILS_ENV
db:drop:all Drops all the local databases defined in config/database.yml
db:reset Drops and recreates the database from db/schema.rb for the current environment.
db:rollback Rolls the schema back to the previous version. Specify the number of steps with STEP=n
db:version Retrieves the current schema version number

We have a far better database administration support. In the older Rails now we would log into our databases admin consoles and create the database manually. Now, we can do simply:


rake db:create:all

If we want to start from scratch, we can do db:drop:all. And in the middle of development we can do db:rollback to undo the latest migration file.

Sexyness

With database set and ready to go, we can create our first Resource. Remember now that Rails 2.0 is RESTful by default (for brazilians: I am writing a separated RESTful Tutorial as well).


./script/generate scaffold Post title:string body:text

The only difference here is that the ‘scaffold’ behaves like the ‘scaffold_resource’ we had before, and the old non-RESTful scaffold is gone. You also don’t have the ActionController class method ‘scaffold’ that dynamically populated your empty controller with default actions. So, everything scaffold we do is RESTful now.

It will create the usual suspects: Controller, Helper, Model, Migration, Unit Test, Functional Test.

The main difference is in the Migration file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# db/migrate/001_create_posts.rb
class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end

  def self.down
    drop_table :posts
  end
end

This is called Sexy Migrations, first devised by “Err the Blog” as a plugin and it found its way into the Core. The best way to understand the different is to take a look at what this migration would look like in Rails 1.2:


class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.column :title, :string
      t.column :body, :text
      t.column :created_at, :datetime
      t.column :updated_at, :datetime
    end
  end

  def self.down
    drop_table :posts
  end
end

It gets rid of the ‘t.column’ repetition and now uses the format ‘t.column_type’ and the automatic datetime columns are concentrated in a single ‘t.timestamps’ statement. It doesn’t chance any behavior, just makes the code ‘sexier’.

Now, we run the migration like before:


rake db:migrate

In the old days, if I wanted to rollback a migration change I had to do:


rake db:migrate VERSION=xxx

Where ‘xxx’ is the version where we wanted to go back to. Now we can simply issue:


rake db:rollback

Much nicer and more elegant, that’s for sure. All set, we can now start our server as before and take a look at the generated pages:


./script/server

It will load either Mongrel, Webrick or Lightpd at port 3000. We have the same root page as before, showing up the index.html page. One tidbit that I didn’t show at the screencast is this:

1
2
3
4
5
6

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.root :controller => 'posts'
  map.resources :posts
end

There is a new ‘map.root’ statement which have the same effect as “map.connect ’’, :controller => ‘posts’. Just a small nicety that doesn’t do anything big but tries to make the routes file feel more polished. Once you set that, don’t forget to delete the public/index.html file. Not the root URL will always be pointed to the Posts controller.

As you can see, everything feels the same as before. All the scaffold templates are the same. You can browse around, create new rows and so on.

Nested Routes

So, let’s create a companion Comment resource for the Post. That should complete our Blog’s resources:

1
2
3

./script/generate scaffold Comment post:references body:text
rake db:migrate

Same thing here: scaffold the resource, configure the column names and datatypes in the command line and the migration file will be already set. Notice another small addition: the keyword ‘references’. As my friend Arthur reminded me, this makes migrations even sexier. To compare, this is the old way of doing the same thing:

1
2

./script/generate scaffold Comment post:references body:text

Foreign keys are just implementation details that don’t matter. Take a look at the new migration file for this:

1
2
3
4
5
6
7
8
9

def self.up
  create_table :comments do |t|
    t.references :post
    t.text :body

    t.timestamps
  end
end

Take a look here for details on this new ‘references’ keyword. So, running db:migrate creates the table in the database. Then, we configure the ActiveRecord models so they relate to each other like this:

1
2
3
4
5
6
7
8
9
10

# app/models/post.rb
class Post < ActiveRecord::Base
  has_many :comments
end

# app/models/comment.rb
class Comment < ActiveRecord::Base
  belongs_to :post
end

Ok, nothing new here, we already know how to work with ActiveRecord associations. But we are also working with RESTful resources. In the new Rails way, we would like to have URLs like these:


http://localhost:3000/posts/1/comments
http://localhost:3000/posts/1/comments/new
http://localhost:3000/posts/1/comments/3

Meaning: ‘grab the comments from this particular post’ The scaffold generator only made it ready to do URLs like these:


http://localhost:3000/posts/1
http://localhost:3000/comments/new
http://localhost:3000/comments/3

That’s because in the config/routes.rb we have:

1
2
3
4
5
6
7
8

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :comments

  map.root :controller => 'posts'
  map.resources :posts
end

Let’s tweak it a bit. Just like in the models, we can create what’s called a Nested Route:

1
2
3
4
5
6

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.root :controller => 'posts'
  map.resources :posts, :has_many => :comments
end

Just like that! Now we can do the nested URLs as showed above. The first thing to understand is that when I type in this URL:


http://localhost:3000/posts/1/comments

Rails will parse it like this:

  • Load the CommentsController
  • Set params[:post_id] = 1
  • In this case, call the ‘index’ action

We have to make the CommentsController prepared to be nested. So that’s what we are going to change:

1
2
3
4
5
6
7
8

class CommentsController < ApplicationController
  before_filter :load_post
  ...
  def load_post
    @post = Post.find(params[:post_id])
  end
end

This makes the @post already set for all the actions within the Comments controller. Now we have to make these changes:

BeforeAfter
Comment.find@post.comments.find
Comment.new@post.comments.build
redirect_to(@comment)redirect_to([@post, @comment])
redirect_to(comments_url)redirect_to(post_comments_url(@post))

That should make the Comments controller ready. Now let’s change the 4 views at app/views/comments. If you open up either new.html.erb or edit.html.erb you will notice the following new feature:

1
2
3
4
5

# new edit.html.erb and new.html.erb
form_for(@comment) do |f|
  ...
end

That’s the new way of doing this old statement in Rails 1.2:

1
2
3
4
5

# old new.rhtml
form_for(:comment, :url => comments_url) do |f|
  ...
end
1
2
3
4
5
6

# old edit.rhtml
form_for(:comment, :url => comment_url(@comment), 
  :html => { :method => :put }) do |f|
  ...
end

Notice how the same form_for statement suits both ‘new’ and ‘edit’ situations. That’s because Rails can infer what to do based on the Class name of the @comment model instance. But now, for the Nested Route, comments is dependent on the Post, so that’s what we have to do:

1
2
3
4
5

# new edit.html.erb and new.html.erb
form_for([@post, @comment]) do |f|
 ...
end

Rails will try to be smart enough to understand that this array represents a Nested Route, will check routes.rb and figure out and this is the post_comment_url(@post, @comment) named route.

Let’s explain named routes first. When we set a Resource Route in the routes.rb. We gain these named routes:

routeHTTP verbController Action
commentsGETindex
commentsPOSTcreate
comment(:id)GETshow
comment(:id)PUTupdate
comment(:id)DELETEdestroy
new_commentGETnew
edit_comment(:id)GETedit

“7 Actions to Rule them all …” :-)

You can suffix them with both ‘path’ or ‘url’. The difference being:

comments_urlhttp://localhost:3000/comments
comments_path/comments

Finally, you can prefix them with ‘formatted’, giving you:

formatted_comments_url(:atom)http://localhost:3000/comments.atom
formatted_comment_path(@comment, :atom)/comments/1.atom

Now, as Comments is nested within Post, we are obligated to add the prefix ‘post’. In Rails 1.2 this prefix was optional, it was able to tell the difference by the number or parameters passed to the named route helper, but this could lead to many ambiguities so it is now mandatory to have the prefix, like this:

routeHTTP verbURL
post_comments(@post)GET/posts/:post_id/comments
post_comments(@post)POST/posts/:post_id/comments
post_comment(@post, :id)GET/posts/:post_id/comments/:id
post_comment(@post, :id)PUT/posts/:post_id/comments/:id
post_comment(@post, :id)DELETE/posts/:post_id/comments/:id
new_post_comment(@post)GET/posts/:post_id/comments/new
edit_post_comment(@post, :id)GET/posts/:post_id/comments/edit

So, to summarize, we have to make the Comments views to behave like they are nested within a Post. So we have to change the named routes within from the default scaffold generated code to the nested form:

1
2
3
4
5
6
7
8
9
10
11
12

<!-- app/views/comments/_comment.html.erb -->
<% form_for([@post, @comment]) do |f| %>
  

Body /> <%= f.text_area :body %>

<%= f.submit button_name %>

<% end %>
1
2
3
4
5
6
7
8
9
10
11

<!-- app/views/comments/edit.html.erb -->

Editing comment

<%= error_messages_for :comment %> <%= render :partial => @comment, :locals => { :button_name => "Update"} %> <%= link_to 'Show', [@post, @comment] %> | <%= link_to 'Back', post_comments_path(@post) %>
1
2
3
4
5
6
7
8
9
10

<!-- app/views/comments/new.html.erb -->

New comment

<%= error_messages_for :comment %> <%= render :partial => @comment, :locals => { :button_name => "Create"} %> <%= link_to 'Back', post_comments_path(@post) %>
1
2
3
4
5
6
7
8
9
10

<!-- app/views/comments/show.html.erb -->

Body: <%=h @comment.body %>

<%= link_to 'Edit', [:edit, @post, @comment] %> | <%= link_to 'Back', post_comments_path(@post) %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

<!-- app/views/comments/index.html.erb -->

Listing comments

<% for comment in @comments %> <% end %>
Post Body
<%=h comment.post_id %> <%=h comment.body %> <%= link_to 'Show', [@post, comment] %> <%= link_to 'Edit', [:edit, @post, comment] %> <%= link_to 'Destroy', [@post, comment], :confirm => 'Are you sure?', :method => :delete %>
/> <%= link_to 'New comment', new_post_comment_path(@post) %>

Some remarks:

  • Notice that I created a partial to DRY up the new and edit forms. But pay attention that instead of :partial => ‘comment’, I did :partial => @comment. Then again it can infer the name of partial from the class name. If we passed a collection it would do the equivalent of the old ’:partial, :collection’ statement.
  • I can use both post_comment_path(@post, @comment), or simply [@post, @comment]
  • Pay close attention to not forget any named route behind.

Finally, it would be good to link the comments list to the post view. So let’s do it:

1
2
3
4
5

<!-- app/views/posts/show.html.erb -->
<%= link_to 'Comments', post_comments_path(@post) %>
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>

So, I just added a link there. Let’s see how it looks like:

Completing the Views

Ok, looks good, but that’s not how a Blog should behave! The Post’s show view should already have the Comments listing and New Comment Form as well! So let’s make some small adaptations. There’s nothing new here, just traditional Rails. Let’s start at the view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

<!-- app/views/posts/show.html.erb -->

Title: <%=h @post.title %>

Body: <%=h @post.body %>

<!-- #1 --> <% unless @post.comments.empty? %>

Comments

<% @post.comments.each do |comment| %>

<%= h comment.body %>

<% end %> <% end %> <!-- #2 -->

New Comment

<%= render :partial => @comment = Comment.new, :locals => { :button_name => 'Create'}%> <%= link_to 'Comments', post_comments_path(@post) %> <%= link_to 'Edit', edit_post_path(@post) %> | <%= link_to 'Back', posts_path %>

More remarks

  1. There is nothing new in the iterator, just listing all comments
  2. Again, we pass in the @comment variable to the partial statement

One final adjustment: whenever we create a new post, we would like to return to the same Posts’ show view, so we change the CommentsController to behave like this:

1
2
3
4
5
6

# app/controllers/comments_controller.rb
# old redirect:
redirect_to([@post, @comment])
# new redirect:
redirect_to(@post)

Namespaced Routes

Ok, now we have a bare bone mini-blog that kind of mimics the classic blog from the ‘15 minute’ screencast David did in 2005. Now let’s go one step further: Posts should not be publicly available to anyone to edit, we need an Administration section in our website. Let’s create a new controller for that:


./script/generate controller Admin::Posts

Rails 2.0 now supports namespaces. This will create a sub-directory called app/controllers/admin.

What we want to do is:

  1. Create a new routes
  2. Copy all actions from the old Posts controller to the new Admin::Posts
  3. Copy the old posts views to app/views/admin* Leave the old Posts controller with only the ‘index’ and ‘show’ actions, this means deleting the new and edit views as well
  4. Adapt the actions and views we just copied so it understands it is within the admin controller

First things first, let’s edit config/routes.rb again:

1
2
3
4

map.namespace :admin do |admin|
  admin.resources :posts
end

In practice this means that we now have names routes for Posts with the prefix ‘admin’. This will disambiguate the old posts routes from the newest admin posts routes, like this:

posts_path/posts
post_path(@post)/posts/:post_id
admin_posts_path/admin/posts
admin_post_path(@post)/admin/posts/:post_id

Now let’s copy the actions from the old Post controller and adapt the routes to fit the new namespace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# app/controllers/admin/posts_controller.rb
...
def create
  # old:
  format.html { redirect_to(@post) }
  # new:
  format.html { redirect_to([:admin, @post]) }
end

def update
  # old:
  format.html { redirect_to(@post) }
  # new:
  format.html { redirect_to([:admin, @post]) }
end

def destroy
  # old:
  format.html { redirect_to(posts_url) }
  # new:
  format.html { redirect_to(admin_posts_url) }
end
...

Don’t forget to delete all the methods from the app/controllers/posts_controller.rb, leaving just the ‘index’ and ‘show’ methods.

Now, let’s copy the views (assuming your shell is already in the project’s root folder):


cp app/views/posts/*.erb app/views/admin/posts
rm app/views/posts/new.html.erb
rm app/views/posts/edit.html.erb

Now, let’s edit the views from app/views/admin/posts:

1
2
3
4
5
6
7
8
9
10
11
12

<!-- app/views/admin/posts/edit.html.erb -->

Editing post

<%= error_messages_for :post %> <% form_for([:admin, @post]) do |f| %> ... <% end %> <%= link_to 'Show', [:admin, @post] %> | <%= link_to 'Back', admin_posts_path %>
1
2
3
4
5
6
7
8
9
10
11

<!-- app/views/admin/posts/new.html.erb -->

New post

<%= error_messages_for :post %> <% form_for([:admin, @post]) do |f| %> ... <% end %> <%= link_to 'Back', admin_posts_path %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

<!-- app/views/admin/posts/show.html.erb -->

Title: <%=h @post.title %>

Body: <%=h @post.body %>

<%= link_to 'Edit', edit_admin_post_path(@post) %> | <%= link_to 'Back', admin_posts_path %>
<% end %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<!-- app/views/admin/posts/index.html.erb -->
...
<% for post in @posts %>
  
<%=h post.title %> <%=h post.body %> <%= link_to 'Show', [:admin, post] %> <%= link_to 'Edit', edit_admin_post_path(post) %> <%= link_to 'Destroy', [:admin, post], :confirm => 'Are you sure?', :method => :delete %>
/> <%= link_to 'New post', new_admin_post_path %>

Almost done: if you test http://localhost:3000/admin/posts it should work properly now. But, it will look ugly, and that’s because we don’t have a global app layout. When we did the first scaffolds, Rails generated specific layouts for Post and Comment alone. So let’s delete them and create one that’s generic:


cp app/views/layouts/posts.html.erb 
app/views/layouts/application.html.erb
rm app/views/layouts/posts.html.erb
rm app/views/layouts/comments.html.erb

Then let’s just change the title of it:

1
2
3
4
5

<!-- app/views/layouts/application.html.erb -->
...
<span class="co">My</span> <span class="co">Great</span> <span class="co">Blog</span><<span class="rx"><span class="dl">/</span><span class="k">title><tt>
</tt>...<tt>
</tt></span></span></pre></td></tr></tbody></table></p><p>It only remain the old ‘index’ and ‘show’ pages from the previous Posts controllers. They still have links to the methods we deleted, so let’s rip them off from those links:</p><p><table class="CodeRay"><tbody><tr><td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt><strong>5</strong><tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt><strong>15</strong><tt>
</tt>16<tt>
</tt>17<tt>
</tt>18<tt>
</tt></pre></td><td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><tt>
</tt><span class="c"><!-- app/views/posts/index.html.erb --></span><tt>
</tt><span class="ta"><h1></span>My Great Blog<span class="ta"></h1></span><tt>
</tt><tt>
</tt><span class="ta"><table></span><tt>
</tt>  <span class="ta"><tr></span><tt>
</tt>    <span class="ta"><th></span>Title<span class="ta"></th></span><tt>
</tt>    <span class="ta"><th></span>Body<span class="ta"></th></span><tt>
</tt>  <span class="ta"></tr></span><tt>
</tt><tt>
</tt><span class="c"><% for post in @posts %></span><tt>
</tt>  <span class="ta"><tr></span><tt>
</tt>    <span class="ta"><td></span><span class="c"><%=h post.title %></span><span class="ta"></td></span><tt>
</tt>    <span class="ta"><td></span><span class="c"><%=h post.body %></span><span class="ta"></td></span><tt>
</tt>    <span class="ta"><td></span><span class="c"><%= link_to 'Show', post %></span><span class="ta"></td></span><tt>
</tt>  <span class="ta"></tr></span><tt>
</tt><span class="c"><% end %></span><tt>
</tt><span class="ta"></table></span><tt>
</tt></pre></td></tr></tbody></table><table class="CodeRay"><tbody><tr><td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt><strong>5</strong><tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt><strong>15</strong><tt>
</tt>16<tt>
</tt>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<tt>
</tt>24<tt>
</tt><strong>25</strong><tt>
</tt></pre></td><td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><tt>
</tt><span class="c"><!-- app/views/posts/show.html.erb --></span><tt>
</tt><span class="ta"><p></span><tt>
</tt>  <span class="ta"><b></span>Title:<span class="ta"></b></span><tt>
</tt>  <span class="c"><%=h @post.title %></span><tt>
</tt><span class="ta"></p></span><tt>
</tt><tt>
</tt><span class="ta"><p></span><tt>
</tt>  <span class="ta"><b></span>Body:<span class="ta"></b></span><tt>
</tt>  <span class="c"><%=h @post.body %></span><tt>
</tt><span class="ta"></p></span><tt>
</tt><tt>
</tt><span class="c"><% unless @post.comments.empty? %></span><tt>
</tt>  <span class="ta"><h3></span>Comments<span class="ta"></h3></span><tt>
</tt>  <span class="c"><% @post.comments.each do |comment| %></span><tt>
</tt>  <span class="ta"><p></span><span class="c"><%= h comment.body %></span><span class="ta"></p></span><tt>
</tt>  <span class="c"><% end %></span><tt>
</tt><span class="c"><% end %></span><tt>
</tt><tt>
</tt><span class="ta"><h3></span>New Comment<span class="ta"></h3></span><tt>
</tt><tt>
</tt><span class="c"><%= render :partial => @comment = Comment.new, <tt>
</tt>   :locals => { :button_name => 'Create'}%></span><tt>
</tt><tt>
</tt><span class="c"><%= link_to 'Back', posts_path %></span><tt>
</tt></pre></td></tr></tbody></table></p><p>We can test everything from the browser, go in http://localhost:3000/admin/posts and see that everything is working properly now. But, we still have one thing missing: an administration section should not be publicly available. Right now you can just jump in and edit everything. We need authentication.</p><h2><span class="caps">HTTP</span> Basic Authentication</h2><p>There are several ways of implementing authentication and authorization. One plugin that’s widely used for this is <a href="http://agilewebdevelopment.com/plugins/restful_authentication">restful_authentication</a>.</p><p>But, we don’t want to make anything fancy here. And for that Rails 2.0 gives us a great way of authenticating. The idea is: let’s use what <span class="caps">HTTP</span> already gives us: <strong><span class="caps">HTTP</span> Basic Authentication</strong>. The drawback being: you will definitely want to use <span class="caps">SSL</span> when going into production. But, of course, you would do it anyway. <span class="caps">HTML</span> Form authentication is not protected without <span class="caps">SSL</span> either.</p><p>So, let’s edit our Admin::Posts controller to add authentication:</p><p><table class="CodeRay"><tbody><tr><td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt><strong>5</strong><tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt></pre></td><td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><tt>
</tt><span class="c"># app/controllers/admin/posts.rb</span><tt>
</tt><span class="r">class</span> <span class="cl">Admin::PostsController</span> < <span class="co">ApplicationController</span><tt>
</tt>  before_filter <span class="sy">:authenticate</span><tt>
</tt>  ...<tt>
</tt>  <span class="r">def</span> <span class="fu">authenticate</span><tt>
</tt>    authenticate_or_request_with_http_basic <span class="r">do</span> |name, pass|<tt>
</tt>      <span class="c">#User.authenticate(name, pass)</span><tt>
</tt>      name == <span class="s"><span class="dl">'</span><span class="k">akita</span><span class="dl">'</span></span> && pass == <span class="s"><span class="dl">'</span><span class="k">akita</span><span class="dl">'</span></span>      <tt>
</tt>    <span class="r">end</span><tt>
</tt>  <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td></tr></tbody></table></p><p>You already know what ‘before_filter’ does: it runs the configured method before any action in the controller. If you set it in the ApplicationController class, then it runs before any action from any other controller. But we only want to protect Admin::Posts here.</p><p>Then, we implement this method and the secret sauce is the ‘authenticate_or_request_with_http_basic’ method that let’s us configure a block. It gives us the username and password that the user typed in the browser. We would usually have a User model of some kind to authenticate this data, but for our very very simple example I am hard coding the checking, but you get the idea.</p><p style="TEXT-ALIGN: center"><img src="http://333111444.itpub.net/df_files/Picture_6_1.jpg" /></p>
                                                    </div>
                                        <p style="clear:both;"></p>
                    
                    <p class="translate">
                                                    来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12058779/viewspace-999805/
                            ,如需转载,请注明出处,否则将追究法律责任。
                                            </p>
<!--
                    
                    
                                      -->
                </div>
            </div>
                            <div class="preview-footer clearfix">
                    <!--已点赞、收藏-->
                    <div class="icon-praise" id="praise" onclick="blogObj.praise($(this),'/praise/')"><span
                                class="text"></span><span class="num">0</span></div>
                    <div class="icon-collect" id="collect" onclick="blogObj.praise($(this),'/favour/')"><span
                                class="text"></span><span class="num">0</span></div>
                    <div class="share-group fr">
                        <div class="bdsharebuttonbox bdshare-button-style0-16" data-bd-bind="1522216707615">
                            <span class="fl">分享到:</span>
                            <a href="javascript:;" class="bds_tsina" data-cmd="tsina" title="分享到新浪微博"></a>
                            <a href="javascript:;" class="bds_sqq" data-cmd="sqq" title="分享到QQ好友"></a>
                            <a href="javascript:;" class="bds_qzone" data-cmd="qzone" title="分享到QQ空间"></a>
                            <a href="javascript:;" class="bds_weixin" data-cmd="weixin" title="分享到微信"></a>
                        </div>

                        <script>
                            window._bd_share_config = {
                                "common": {
                                    "bdSnsKey": {},
                                    "bdText": "",
                                    "bdDesc": "",
                                    "bdMini": "",
                                    "bdMiniList": false,
                                    "bdPic": "",
                                    "bdStyle": "0",
                                    "bdSize": "32"
                                }, "share": {"bdSize": 16}
                            };
                            with (document)
                                0[(getElementsByTagName('head')[0] || body).appendChild(createElement('script')).src = '/static/api/js/share.js?v=89860593.js?cdnversion=' + ~(-new Date() / 36e5)];
                            if ($(window).width() < 1320) {
                                if ($(window).width() < 1200) {
                                    $(".sideslip").css({
                                        "left": "20px",
                                        "margin-left": 0
                                    })
                                } else {
                                    $(".sideslip").css({
                                        "margin-left": "-590px"
                                    })
                                }
                            }</script>
                    </div>

                </div>

                <div class="article-context">
                    <div class="fl">
                        <span class="color77">上一篇:</span>
                                                    <a href="https://blog.itpub.net/12058779/viewspace-999798/">Java 框架到底怎么了</a>
                        
                    </div>
                    <div class="fr">
                        <span class="color77">下一篇:</span>
                                                    <a href="https://blog.itpub.net/12058779/viewspace-1000307/">Hibernate关联关系</a>
                                            </div>
                </div>

                <input type="hidden" id="blogId"   name="blogId"  value="999805">


                <!--评论开始-->
                <div class="blog-comment">
                    <div class="new-comment">
                        <input type="hidden" id="hid" name="hid" value="999805">
                        <input type="hidden" name="_token" value="R1ATqYPaE2UGMCBiTRiK7uvhVqGpt71wWVkIflDa">
                        <a class="avatar" href="javascript:;">
                            <img src="/images/user_pic_default.png"></a>
                        <!--用户未登录-->

                        <div class="sign-container">
                            <span>请登录后发表评论</span>
                            <a class="btn btn-sign">
                                <a class="layui-btn ml30" onclick="blog.login(event)">登录</a>
                            </a>
                        </div>

                        <!--用户已登录-->
                        <textarea placeholder="请写下你的评论…" onfocus="blogObj.textareaFocus($(this),200)"
                                  maxlength=200></textarea>


                    </div>
                    <div class="comment-list">
                        <div class="top-title"><span class="all-comment">全部评论</span> <span
                                    class="colorbb ml20"></span>
                        </div>
                        <div class="comment-items" id="comment_items">
                            <script type="text/template" id='blog_template'>
                                <%for(var i=0;i
                                <items.length;i++){%>
                                <div class="comment-item">
                                    <div class="author">
                                        <a target="_blank" href="/<%=items[i].uid%>" class="avatar">
                                            <img src="<%=items[i].headimg%>" alt="">

                                        </a>
                                        <div class="info">
                                            <div class="name"><a target="_blank" href="/<%=items[i].uid%>"><%=items[i].username%></a>
                                                <span class="fr reply-btn"
                                                      onclick="blogObj.newComment($(this),'<%=items[i].username%>',<%=items[i].id%>)">回复</span>
                                            </div>
                                            <div class="time colorbb"><%=items[i].createtime%></div>
                                        </div>
                                    </div>
                                    <p class="comment-wrap"><%=items[i].content%></p>
                                    <%if(items[i].items.items.length) { %>

                                    <div class="sub-comment-list">
                                        <%for(var j=0;j
                                        <items
                                        [i].items.items.length;j++){%>
                                        <div class="sub-comment">
                                            <div class="color77 time-reply">
                                                <span class="time"><%=items[i].items.items[j].createtime%></span>
                                                <span class="reply-btn fr"
                                                      onclick="blogObj.newComment($(this),'<%=items[i].items.items[j].username%>',<%=items[i].items.items[j].id%>)">回复</span>
                                            </div>
                                            <p><a href="/<%=items[i].uid%>"><%=items[i].items.items[j].username%></a>   回复   <a
                                                        href="/<%=items[i].touid%>"><%=items[i].items.items[j].tousername%></a>:
                                                <span class="content"><%=items[i].items.items[j].content%></span></p>
                                        </div>
                                        <%}%>
                                        <%if(items[i].items.total > 5) { %>
                                        <div class="more-comment"><span class="sub-comment-count">还有<i class="count"><%=items[i].items.total-5%></i>条评论</span><span
                                                    class="more-comment-btn"
                                                    onclick=blogObj.loadSubComment($(this),<%=items[i].id%>) data-count=1 data-flag=true>点击查看</span>
                                        </div>
                                        <%}%>

                                    </div>
                                    <%}%>
                                </div>
                                <%}%>
                            </script>
                        </div>

                    </div>

                </div>
                <!--评论结束-->
                    </div>
        <script>
                    </script>
        <div class="fr w290">
            <!--作者信息开始-->
            <!--作者信息开始-->
<div class="author-info right-fixed ">
        <div class="head-img">
        <a href="https://blog.itpub.net/12058779/"><img src="http://account.itpub.net/api/avatar.php?uid=12058779" alt=""></a>
    </div>
    <div class="author-name"><a href="https://blog.itpub.net/12058779/">333111444</a></div>
            <div class="author-intro">
        
    </div>
            <ul class="tree-list clearfix">
        <li>
            <div class="item-tt">博文量</div>
            <a href="https://blog.itpub.net/12058779/"><span class="item-num blognum">47</span></a>
        </li>
        <li>
            <div class="item-tt">访问量</div>
            <div class="item-num blogviewnum">445381</div>
        </li>
    </ul>

</div>
<!--作者信息结束-->

<script>
    var data = {uid: 12058779};
    $._ajax({
        url: '/getAuthorInfo/',
        data: data,
        type: 'get',
        dataType: 'json',
        success: function (data) {
            if (data.code == 200) {
                var data = data.data;
                $(".blognum").text(data.blognum);
                $(".blogviewnum").text(data.visitednum);
            }
        }
    });
</script>            <!--作者信息结束-->

                            <!--博文推荐开始-->
                <div class="blog-choice right-fixed">
                    <h3 class="choice-title">最新文章</h3>
                    <ul class="newul">
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1019513/"
                                   class="clearfix">
                                    一个资深旅行社经理对旅行者的50句忠告
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1018018/"
                                   class="clearfix">
                                    表连接概念(转)
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1017896/"
                                   class="clearfix">
                                    关于oracle 密码文件
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1017534/"
                                   class="clearfix">
                                    Java的IO系统
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1017483/"
                                   class="clearfix">
                                    了解 JAVA classloader
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1016144/"
                                   class="clearfix">
                                    [转]Apache Commons工具集
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1016143/"
                                   class="clearfix">
                                    应用OSCache提升J2EE系统运行性能
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1016142/"
                                   class="clearfix">
                                    CSS属性与JavaScript 编码方法对照表
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1016141/"
                                   class="clearfix">
                                    IE和Firefox兼容的JS
                                </a>
                            </li>
                                                    <li class="new-item">
                                <a href="https://blog.itpub.net/12058779/viewspace-1015892/"
                                   class="clearfix">
                                    【转】java六种异常处理的陋习
                                </a>
                            </li>
                                                <ul>


                </div>
                <!--大牛精选结束-->
                    </div>
    </div>
    <div class="icon-back-top" onclick="$('html,body').animate({scrollTop:0},'slow');"></div>
    <!--main部分结束-->

    <!--百度推送-->
            <script>
            (function () {
                var bp = document.createElement('script');
                var curProtocol = window.location.protocol.split(':')[0];
                if (curProtocol === 'https') {
                    bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
                } else {
                    bp.src = 'http://push.zhanzhang.baidu.com/push.js';
                }
                var s = document.getElementsByTagName("script")[0];
                s.parentNode.insertBefore(bp, s);
            })();
        </script>
    
<!--footer部分开始-->
<div class="blog-footer">
    <div class="w1200 pr">
        <div class="footer-links">
            <a class="icon-blog icon-wx mr30">
                <div class="wx-qrcode">
                    <img src="/images/qrcode.jpg" alt="">
                </div>
            </a>
            <a href="https://weibo.com/itpub2001?from=myfollow_all" class="icon-blog icon-wb" target="_blank"></a>
        </div>
        <p class="footer-nav">
            <a href="http://www.it168.com/bottomfile/it168.shtml" target="_blank">支持我们</a>
            <a href="http://www.it168.com/bottomfile/tgzn.shtml" target="_blank">作者招募</a>
            <a href="http://www.it168.com/bottomfile/sytk.shtml" target="_blank">用户协议</a>
            <a href="//blog.itpub.net/31509949/viewspace-2157750/" target="_blank">FAQ</a>
            <a href="http://edu.itpub.net/contactus.html" target="_blank">Contact Us</a>
            <script src="https://s22.cnzz.com/z_stat.php?id=1274521965&web_id=1274521965" language="JavaScript"></script>
        </p>
        <p>北京盛拓优讯信息技术有限公司. 版权所有  <a style="color:#777777;" target="_blank" href="http://beian.miit.gov.cn">京ICP备16024965号-8</a>  北京市公安局海淀分局网监中心备案编号:11010802021510  niuxiaotong@pcpop.com  17352615567
            <a style="color:#777777;" target="_blank"  href="http://www.it168.com/jubao.html"> 未成年人举报专区</a></p>
        <p>广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员</p>
        <p>违法和不良信息举报电话:18101376593</p>
    </div>
</div>
<!--footer部分结束-->

<script>
    var _hmt = _hmt || [];
    (function() {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?5016281862f595e78ffa42f085ea0f49";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
    })();
</script>
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?540bee2c1a1b922df9e0a0b922440b73";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<!-- END STAT PV -->

</body>
</html>