Ruby on Rails 的学习曲线还算是有一点陡的, 作为一个初学者, 建议先看一下Ruby的语法书, 再看一下Rails的入门教材和示例 , 但真正做项目的时候, 可能就要和各种各样Gems打交道了, 因为自己走了很多弯路, 所以想把一些笔记分享出来, 希望对新生有点帮助.
我准备用一个Blog的示例把Scaffolding MongoDB Devise Omniauth CanCan串起来.
MongoDB  
1.1 Help  
1.2. CRUD  
1.3. Security  
1.4. Port  
准备工作  
Git  
Scaffold  
4.1. 添加字段  
4.2. 验证输入  
4.3. 表关联  
4.4. 引用关联  
 
首先讲 MongoDB , Mongo 取自 humongo us, 意思是大得无比的.
在Ubuntu上安装MongoDB相当简单. 其网站上有详细资料 , 我仅copy一下几条命令:
1 
2 
3 
4 
5 
6 
$  sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
$  sudo vi /etc/apt/sources.list.d/10gen.list
deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen
 
 $  sudo apt-get update
$  sudo apt-get install mongodb-10gen
 
配置文件, 可以改变数据库存储目录等, 默认是 /var/lib/mongodb
1 
$  sudo vi /etc/mongodb.conf
 
数据库启动停用重启命令和其它service一样:
1 
$  sudo service mongodb start | stop | restart
 
命令行输入mongo可以打开数据库操作台:
1 
2 
3 
4 
>  help                           //  --top level help 
>  db . help ()                      //  --help on db-specific methods 
>  db . mycollection . help ()         //  --help on collection methods 
>  db . mycollection . find (). help ()  //  --cursor help 
 
1 
2 
3 
>  show  dbs  //  --displays all the databases on the server you are connected to 
>  use  db_name  //  --switches to db_name on the same server 
>  show  collections  //  --displays a list of all the collections in the current database 
 
不能免俗, 可以在默认的test数据库来个hello world!:
1 
2 
3 
>  db . test . save (  {  mongo :  "Hello World!"  }  ) 
>  db . test . find () 
{  "_id"  :  ObjectId ( "4fb947a9e7ffc7b413ce9c54" ),  "mongo"  :  "Hello World!"  } 
 
C: create 
1 
2 
>  db . test . save ( {  website  :  "blog.neten.de" } ); 
>  db . test . save ( {  website  :  "bbs.neten.de" } ); 
 
R: read 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
>  db . test . find (); 
{  "_id"  :  ObjectId ( "4fb947a9e7ffc7b413ce9c54" ),  "mongo"  :  "Hello World!"  } 
{  "_id"  :  ObjectId ( "4fb94971e7ffc7b413ce9c55" ),  "website"  :  "blog.neten.de"  } 
{  "_id"  :  ObjectId ( "4fb94978e7ffc7b413ce9c56" ),  "website"  :  "bbs.neten.de"  } 
>  db . test . findOne (  {  mongo :  "Hello World!"  }  ); 
{ 
      "_id"  :  ObjectId ( "4fb947a9e7ffc7b413ce9c54" ), 
       "mongo"  :  "Hello World!" , 
       "website"  :  "www.neten.de" 
 } 
 
U: update 
1 
2 
3 
4 
5 
6 
7 
>  person  =  db . test . findOne (  {  mongo :  "Hello World!"  }  ); 
>  person . website  =  "www.neten.de" ; 
>  db . test . save (  person  ); 
>  db . test . find (); 
{  "_id"  :  ObjectId ( "4fb94971e7ffc7b413ce9c55" ),  "website"  :  "blog.neten.de"  } 
{  "_id"  :  ObjectId ( "4fb94978e7ffc7b413ce9c56" ),  "website"  :  "bbs.neten.de"  } 
{  "_id"  :  ObjectId ( "4fb947a9e7ffc7b413ce9c54" ),  "mongo"  :  "Hello World!" ,  "website"  :  "www.neten.de"  } 
 
D: delete 
1 
2 
3 
4 
5 
>  db . test . drop ()  //  --drop the entire test collection 
>  db . test . remove ()  //  --remove all objects from the collection 
>  db . test . remove (  {  mongo  :  "Hello World!"  }  )  //  --remove objects from the collection where name is mongo 
>  use  [ database ]; 
>  db . dropDatabase (); 
 
如果删除Collection之后, 可能要重启才能看到Collection不见了.
还有一个重要工作就是安全工作 :
添加管理员用户laoda(老大), 管理员的名字最好不要用admin, root之类的
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
>  use  admin 
switched  to  db  admin 
>  db . addUser ( "laoda" , "neten" ) 
{  "n"  :  0 ,  "connectionId"  :  2 ,  "err"  :  null ,  "ok"  :  1  } 
{ 
  "user"  :  "laoda" , 
   "readOnly"  :  false , 
   "pwd"  :  "3d075670621dfa6f25d9b6b9caa1d987" , 
   "_id"  :  ObjectId ( "4fbb5c2c124f3221f45162b4" ) 
 } 
 
验证函数:
1 
2 
3 
4 
>  db . auth ( "laoda" , "neten" ) 
1 
>  db . auth ( "laoda" , "neten.de" ) 
0 
 
添加普通用户xiaodi(小弟)和只读用户(龙套):
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
>  use  test 
switched  to  db  test 
>  db . addUser ( "xiaodi" , "neten" ) 
{  "n"  :  0 ,  "connectionId"  :  2 ,  "err"  :  null ,  "ok"  :  1  } 
{ 
  "user"  :  "xiaodi" , 
   "readOnly"  :  false , 
   "pwd"  :  "8cb78b1a918c6c5cca2b9cea00673180" , 
   "_id"  :  ObjectId ( "4fbb5c96124f3221f45162b5" ) 
 } 
>  db . addUser ( "longtao" , "neten" , true ) 
{  "n"  :  0 ,  "connectionId"  :  2 ,  "err"  :  null ,  "ok"  :  1  } 
{ 
  "user"  :  "longtao" , 
   "readOnly"  :  true , 
   "pwd"  :  "fe130c893dd5c69a5a1cb96feba00f7d" , 
   "_id"  :  ObjectId ( "4fbb5cc7124f3221f45162b6" ) 
 } 
 
虽然我取的密码都是neten, 但hash过后的字符串都不一样, 比md5靠谱.
查找当前数据库用户:
1 
2 
3 
4 
>  db . system . users . find () 
{  "_id"  :  ObjectId ( "4fbb5c96124f3221f45162b5" ),  "user"  :  "xiaodi" ,  "readOnly"  :  false ,  "pwd"  :  "8cb78b1a918c6c5cca2b9cea00673180"  } 
{  "_id"  :  ObjectId ( "4fbb5cc7124f3221f45162b6" ),  "user"  :  "longtao" ,  "readOnly"  :  true ,  "pwd"  :  "fe130c893dd5c69a5a1cb96feba00f7d"  } 
>  db . removeUser (  username  ) 
 
删除用户, 以下两种方法效果一样:
1 
2 
db . removeUser ( "longtao" ) 
db . system . users . remove ( { user :  "longtao" } ) 
 
数据库默认端口:
1 
2 
3 
4 
Standalone mongod : 27017
 mongos : 27017
 shard server (mongod --shardsvr) : 27018
 config server (mongod --configsvr) : 27019 
 
第一步, 创建一个Rails App:
1 
$  rails new neten -T -O
 
用-T -O 是为了不产生Test::Unit 和 Active Record 文件. 由rails产生的Gemfile修改后的内容如下:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
source  'https://rubygems.org' 
 gem  'rails' ,  '3.2.3' 
 group  :assets  do 
  gem  'sass-rails' ,    '~> 3.2.3' 
   gem  'coffee-rails' ,  '~> 3.2.1' 
   gem  'uglifier' ,  '>= 1.0.3' 
 end 
 gem  'jquery-rails' ,  '2.0.2' 
gem  "mongoid" ,  "~> 2.4" 
gem  "bson_ext" ,  "~> 1.5" 
 
最后两行是mongoid相关的.
为项目管理方便, 可以创建gemset, 并设置它作为默认的gemset
1 
2 
3 
$  rvm --create 1.9.3@neten
$  rvm --default use 1.9.3@neten
$  echo  "rvm 1.9.3@neten"  > .rvmrc
 
安装gems, 并检查本机安装的gems, 最好把Gemfile里面的gems都按gem list所显示的标上版本号, 这样在远程部署的时候就不会出现版本问题.
1 
2 
$  bundle install
$  gem list --local
 
为了简化过程, 在此就不涉及RSpec了, 但无用的test模块可以删除.
1 
2 
3 
4 
5 
$  rm -rf test /
$  vi config/application.rb
# ... 
# require 'rails/test_unit/railtie' 
# ... 
 
使用Mongoid, 执行下面的命令后, 会生成配置文件config/mongoid.yml
1 
$  rails generate mongoid:config
 
Mongoid会自动处理config/application.rb文件, 禁用ActiveRecord, require 'rails/all'会被以下代码替代:
1 
2 
3 
require  "action_controller/railtie" 
require  "action_mailer/railtie" 
require  "active_resource/railtie" 
 
原来的数据库配置文件config/database.yml可以删除了.
做了这么多工作, git是时候要登场了:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
$  vi .gitignore
.bundle
 db/*.sqlite3*
 log/*.log
 *.log
 tmp/**/*
 tmp/*
 doc/api
 doc/app
 *.swp
 *~
 .*~
 
 $  git init
Initialized empty Git repository in /home/user/neten/.git/
 $  git add .
$  git commit -am "Initial commit" 
 
用 scaffold 生成文章系统, 主要copy于railscasts 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
$  rails g scaffold article name:string content:text
      invoke  mongoid
       create    app/models/article.rb
        route  resources :articles
       invoke  scaffold_controller
       create    app/controllers/articles_controller.rb
       invoke    erb
       create      app/views/articles
       create      app/views/articles/index.html.erb
       create      app/views/articles/edit.html.erb
       create      app/views/articles/show.html.erb
       create      app/views/articles/new.html.erb
       create      app/views/articles/_form.html.erb
       invoke    helper
       create      app/helpers/articles_helper.rb
       invoke  assets
       invoke    coffee
       create      app/assets/javascripts/articles.js.coffee
       invoke    scss
       create      app/assets/stylesheets/articles.css.scss
       invoke  scss
       create    app/assets/stylesheets/scaffolds.css.scss
 
 
事就这么成了:)  rake db:migrate? 不用了, MongoDB是schemaless database.
因为首页还是默认的Welcome页面, 所以要处理一下:
1 
2 
3 
4 
5 
6 
$  rm public/index.html
$  vi config/routes.rb
Neten::Application.routes.draw do 
    resources :articles
  root :to = > 'articles#index' 
 end
 
 
现在去http://localhost:3000  预览一下吧
Mongoid为model提供了generator, ActiveRecord没有用到, 包含了Mongoid::Document,如果到现在才发现没有加入文章发布时间也关系不大, MongoDB嘛, schema-less, 好处就是在model直接加个字段名称就好了, 不用在db文件夹那里添加东西了.
另外因为加入了Date类型的字段, 所以要加上这句include Mongoid::MultiParameterAttributes, 不然没办法显示出来 .
/app/models/article.rb 1 
2 
3 
4 
5 
6 
7 
class  Article 
  include  Mongoid :: Document 
   include  Mongoid :: MultiParameterAttributes 
   field  :name ,  :type  =>  String 
   field  :content ,  :type  =>  String 
   field  :published_on ,  :type  =>  Date 
 end 
 
view也要相应改一下:
/app/views/articles/_form.html.erb 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
<div class="field"> 
<%=  f . label  :name  %> <br /> 
<%=  f . text_field  :name  %>  
</div> 
<div class="field"> 
<%=  f . label  :published_on  %> <br /> 
<%=  f . date_select  :published_on  %>  
</div> 
<div class="field"> 
<%=  f . label  :content  %> <br /> 
<%=  f . text_area  :content  %>  
</div> 
<div class="actions"> 
<%=  f . submit  %>  
</div> 
 
/app/views/articles/show.html.erb 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
<p id="notice"> <%=  notice  %> </p> 
 <p> 
  <b>Name:</b> 
   <%=  @article . name  %>  
</p> 
 <p> 
  <b>Content:</b> 
   <%=  @article . content  %>  
</p> 
<p> 
  <b>Published:</b> 
   <%=  @article . published_on  %>  
</p> 
 <%=  link_to  'Edit' ,  edit_article_path ( @article )  %>  | 
<%=  link_to  'Back' ,  articles_path  %>  
<%=  debug  @article  %>  
 
<%= debug @article %> 是调试信息, 有这句话, 在前台页面上就会在页脚出现@article的信息.
Mongoid 会使用 ActiveModel 来处理一些事务, 这就有点像我们熟悉的ActiveRecord一样. 比如说在ActiveRecord中用到的validations, callbacks, dirty tracking, attr_accessible都可以搬过来用.
下面我们为:name字段加上 必需输入 的验证要求:
/app/models/article.rb 1 
2 
3 
4 
5 
6 
7 
8 
class  Article 
  include  Mongoid :: Document 
   include  Mongoid :: MultiParameterAttributes 
   field  :name ,  :type  =>  String 
   field  :content ,  :type  =>  String 
   field  :published_on ,  :type  =>  Date 
   validates_presence_of  :name 
 end 
 
不允许评论的blog会表现得有点言论暴力, 要加上comments在用数据库的情况下, 一个has_many, 就解决问题了, 现在我们就要在mongoid中寻找has_many的替代了.
/app/models/article.rb 1 
2 
3 
4 
5 
6 
7 
8 
9 
class  Article 
  include  Mongoid :: Document 
   include  Mongoid :: MultiParameterAttributes 
   field  :name ,  :type  =>  String 
   field  :content ,  :type  =>  String 
   field  :published_on ,  :type  =>  Date 
   validates_presence_of  :name 
   embeds_many  :comments 
 end 
 
添加完关联后, 来生成评论model
1 
2 
3 
$  rails g model comment name:string content:text
      invoke  mongoid
       create    app/models/comment.rb
 
 
再让comment和article手拉手:
app/models/comment.rb 1 
2 
3 
4 
5 
6 
class  Comment 
  include  Mongoid :: Document 
   field  :name 
   field  :content 
   embedded_in  :article ,  :inverse_of  =>  :comments 
 end 
 
embedded_in顾名思意就是说Comment是嵌在Article里面的, inverse_of呢是表明comment是通过comments嵌套在Article里面的.
这个手拉手的关系也要在routes里面申明一下:
confing/routes.rb 1 
2 
3 
4 
5 
Neten :: Application . routes . draw  do 
  resources  :articles  do 
     resources  :comments 
   end 
 end 
 
好了, 现在创建comment的controller
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
$  rails g controller comments
      create  app/controllers/comments_controller.rb
       invoke  erb
       create    app/views/comments
       invoke  helper
       create    app/helpers/comments_helper.rb
       invoke  assets
       invoke    coffee
       create      app/assets/javascripts/comments.js.coffee
       invoke    scss
       create      app/assets/stylesheets/comments.css.scss
 
 
Comment和Article手拉手之后, 一切都要看Article的眼色(:article_id)行事
app/controllers/comments_controller.rb 1 
2 
3 
4 
5 
6 
7 
class  CommentsController  <  ApplicationController 
  def  create 
     @article  =  Article . find ( params [ :article_id ] ) 
     @comment  =  @article . comments . create! ( params [ :comment ] ) 
     redirect_to  @article ,  :notice  =>  "Comment created!" 
   end 
 end 
 
在app/views/articles/show.html.erb的最后面加上comments的代码:
app/views/articles/show.html.erb 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
<%  if  @article . comments . size  >  0  %>  
  <h2>Comments</h2> 
   <%  for  comment  in  @article . comments  %>  
    <h3> <%=  comment . name  %> </h3> 
    <p> <%=  comment . content  %> </p> 
   <%  end  %>  
<%  end  %>  
 <h2>New Comment</h2> 
 <%=  form_for  [ @article ,  Comment . new ]  do  | f |  %>  
  <p> <%=  f . label  :name  %>   <%=  f . text_field  :name  %> </p> 
  <p> <%=  f . text_area  :content ,  :rows  =>  10  %> </p> 
  <p> <%=  f . submit  %> </p> 
<%  end  %>  
 
好, 现在打开网站尽情地发表自己的观点吧.
有的blog可能是夫妻店, 所以把Blog的作者放到文章里面还是很有用处的, scaffold又要上班了.
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
$  rails g scaffold author name:string
      invoke  mongoid
       create    app/models/author.rb
        route  resources :authors
       invoke  scaffold_controller
       create    app/controllers/authors_controller.rb
       invoke    erb
       create      app/views/authors
       create      app/views/authors/index.html.erb
       create      app/views/authors/edit.html.erb
       create      app/views/authors/show.html.erb
       create      app/views/authors/new.html.erb
       create      app/views/authors/_form.html.erb
       invoke    helper
       create      app/helpers/authors_helper.rb
       invoke  assets
       invoke    coffee
       create      app/assets/javascripts/authors.js.coffee
       invoke    scss
       create      app/assets/stylesheets/authors.css.scss
       invoke  scss
    identical    app/assets/stylesheets/scaffolds.css.scss
 
 
添加一个key字段, 这样可以在生成链接的时候, 将小写的:name字段做链接名称, 并在表中作_id. 中文没有小写, 不过没关系, 直接用汉字做链接, 比如: http://localhost:3000/authors/张三/edit, references_many可以定义其与articles的关系.
app/models/author.rb 1 
2 
3 
4 
5 
6 
class  Author 
  include  Mongoid :: Document 
   field  :name ,  :type  =>  String 
   key  :name ,  :type  =>  String 
   references_many  :articles 
 end 
 
用这个链接可以添加一些作者 http://localhost:3000/authors/ 
添加过后去命令行看一下:
1 
2 
3 
4 
5 
>  use  neten_development 
switched  to  db  neten_development 
>  db . authors . find () 
{  "_id"  :  "peter" ,  "name"  :  "Peter"  } 
{  "_id"  :  "paul" ,  "name"  :  "Paul"  } 
 
在app/models/article.rb里面要用referenced_in.
app/models/article.rb 1 
2 
3 
4 
5 
6 
7 
8 
9 
class  Article 
  include  Mongoid :: Document 
   field  :name 
   field  :content 
   field  :published_on ,  :type  =>  Date 
   validates_presence_of  :name 
   embeds_many  :comments 
   referenced_in  :author 
 end 
 
为了方便写Blog的人能够添加作者, 我们可以建立一个Select代码.
app/views/articles/_form.html.erb 1 
2 
3 
4 
<div class="field"> 
   <%=  f . label  :author_id  %> <br /> 
   <%=  f . collection_select  :author_id ,  Author . all ,  :id ,  :name  %>  
</div> 
 
当然不要忘记在展示页面也要让作者出现, 因为前面有可能没有定义作者, 还是加个if吧, 这样不会出错.
app/views/articles/show.html.erb 1 
2 
3 
4 
5 
6 
<%  if  @article . author . name . size  >  0  %>  
<p> 
  <b>Author:</b> 
   <%=  @article . author . name  %>  
</p> 
<%  end  %>  
 
最后总结一下Comment和Author, 因为Comments是Embeded在Articles中的, 所以它就是个附庸, 没有自己的家(Collection), 而Author是一个Reference, 所以它有自己的房子(Collection).
1 
2 
3 
4 
>  show  collections 
articles 
authors 
system . indexes 
 
从Rails控制台检索一下结果:
1 
2 
3 
4 
5 
6 
7 
$  rails  c 
Loading  development  environment  ( Rails  3 . 2 . 3 ) 
1 . 9 . 3 p194  : 001  >  Article . first 
 =>  #<Article _id: 4fbd001de6fc8c2470000002, _type: nil, name: "first", content: "first post", published_on: 2012-03-03 00:00:00 UTC, author_id: "peter", published_on(1i): "2012", published_on(2i): "3", published_on(3i): "3"> 
 
 1 . 9 . 3 p194  : 00 9  >  Article . first . comments 
 =>  [ #<Comment _id: 4fbe5755e6fc8c6c3f000004, _type: nil, name: "good", content: "this is a awesome blog">] 
 
 
从MongoDB的命令行检索一下, 和上面作个对比:
1 
2 
3 
4 
5 
$  mongo 
>  use  neten_development 
switched  to  db  neten_development 
>  db . articles . find (  { name :  "first" } ) 
{  "_id"  :  ObjectId ( "4fbd001de6fc8c2470000002" ),  "author_id"  :  "peter" ,  "comments"  :  [   {   "_id"  :  ObjectId ( "4fbe5755e6fc8c6c3f000004" ),    "name"  :  "good" ,   "content"  :  "this is a awesome blog"  }  ],  "content"  :  "first post" ,  "name"  :  "first" ,  "published_on"  :  ISODate ( "2012-03-03T00:00:00Z" ),  "published_on(1i)"  :  "2012" ,  "published_on(2i)"  :  "3" ,  "published_on(3i)"  :  "3"  }