论坛首页 入门讨论版 Ruby

阅读beast day by day(增加了如何安装beast)

浏览 8048 次
该帖已经被评为良好帖
作者 正文
最后更新时间:2007-07-02
发在这里是希望新人们能一起学习,相信不少人和我一样比较懒,不爱自己捉摸,喜欢寻找现成的
答案。我正在逐渐改掉这个坏毛病,希望更多的象我一样的人也能一起改变:)
beast 是一个规模不大的开源论坛,我们就从 beast 开始吧,阅读的过程中,肯定能学到一些东西的。
当然更加期待高手的参与。


第一天: /config/routes.rb

之所以从这开始,觉得 routes 是 REST 的基础,把这弄明白了,才好继续分析下去。


以下是 routes.rb 的全部内容:
ActionController::Routing::Routes.draw do |map|
  map.home '', :controller => 'forums', :action => 'index'

  map.open_id_complete 'session', :controller => "session", :action => "create", :requirements => { :method => :get }
  map.resource :session
  
  map.resources :users, :member => { :admin => :post } do |user|
    user.resources :moderators
  end
  
  map.resources :forums do |forum|
    forum.resources :topics do |topic|
      topic.resources :posts
      topic.resource :monitorship, :controller => :monitorships
    end
  end

  map.resources :posts, :name_prefix => 'all_', :collection => { :search => :get }

  %w(user forum).each do |attr|
    map.resources :posts, :name_prefix => "#{attr}_", :path_prefix => "/#{attr.pluralize}/:#{attr}_id"
  end

  map.signup   'signup',        :controller => 'users',   :action => 'new'
  map.settings 'settings',      :controller => 'users',   :action => 'edit'
  map.activate 'activate/:key', :controller => 'users',   :action => 'activate'
  map.login    'login',         :controller => 'session', :action => 'new'
  map.logout   'logout',        :controller => 'session', :action => 'destroy'
  map.with_options :controller => 'posts', :action => 'monitored' do |map|
    map.formatted_monitored_posts 'users/:user_id/monitored.:format'
    map.monitored_posts           'users/:user_id/monitored'
  end

  map.exceptions 'logged_exceptions/:action/:id', :controller => 'logged_exceptions', :action => 'index', :id => nil
end



内容不多,无非是以下几种:

a) map.home, map.signup  , map.settings 形式:
这种是自己定制 route的方式,例如:
map.signup   'signup',        :controller => 'users',   :action => 'new'
当调用 signup 时,会调用相应的controller和action.

b) map.resources 形式。
这是REST使用的方式。REST的内容自己看吧,这里主要是分析Beast里的代码。

先来个简单的:
map.resources :posts, :name_prefix => 'all_', :collection => { :search => :get }

这里主要是这个 “ :collection => { :search => :get } ” ,意思是给这个url增加一个action 叫 “search”,方式是 “get” 方式。
实际的URL path 就是:/post;search
这个 “ :collection “ 是rails 中的一种用法。

除了 “ :collection”,还有 “ :member ”,
如果:
map.resources :posts, :name_prefix => 'all_', :member=> { :search => :get }
那么实际的url 就是:/post/1;search

可见,“ :collection”是针对所有的记录,“:member”是针对特定的纪录。
一般来说,“ :collection” 都是用于查询的。


再看个稍微复杂点的 map.resources:
  	map.resources :forums do |forum|
    		forum.resources :topics do |topic|
      			topic.resources :posts
      			topic.resource :monitorship, :controller => :monitorships
    		end
  	end

其实这就是嵌套的 resources, 可以理解为 一个 forums 包含多个 topics,一个topics 包含多个 post 和 montitorship.

例如:一个 post 的url应该是:/forums/1/topics/1/post/1
再例如一个link会这么写:
link_to “Show”, post_path(@forums, @topics, @posts)
当然,@forums, @topics, @posts 都是查询出来的记录。


不过我想这段可能会让一些人迷惑:
map.with_options :controller => 'posts', :action => 'monitored' do |map|
    map.formatted_monitored_posts 'users/:user_id/monitored.:format'
    map.monitored_posts           'users/:user_id/monitored'
  end

其实这是 rails 1.2 新加的特性,上面这段代码等同于
map.formatted_monitored_posts 'users/:user_id/monitored.:format',:controller => 'posts', :action => 'monitored'  

map.monitored_posts           'users/:user_id/monitored', :controller => 'posts', :action => 'monitored' 


这些内容其实都可以在 Agile Web Development with Rails 2nd edition 上找到,而且很详细,
我们快去看吧
   
最后更新时间:2007-07-02
既然 routes.rb 已经弄明白了,那么再继续看看。那么就从beast的首页看起吧!

通过 routes.rb 中的这一句:
map.home '', :controller => 'forums', :action => 'index'

可以知道,首页是  ForumsController 里的 index action 处理的:
那么这个 action 作了什么?
 def index
    @forums = Forum.find( :all, : order => "position")
    respond_to do |format|
      format.html
      format.xml { render : xml => @forums.to_xml }
    end
  end


原来就是从 forums 表里取出全部数据,按照 position 排序。
然后就是按照 html 和 xml 返回给客户端。
html 的方式 自然是 views/forums/index.rhtml
也就是
http://svn.techno-weenie.net/projects/beast/trunk/app/views/forums/index.rhtml

明天继续分析这个 rhtml!
   
0 请登录后投票
最后更新时间:2007-05-11
下面分析一下这个 views/forums/index.rhtml

beast 里面使用了 gettext 插件,关于这部门的内容可以自己找资料看看.

页面开头有这么一段:
<% if admin? %>
<h6><%= 'Admin'[:admin_title] %></h6>
<p><%= link_to 'Create New Forum'[:create_new_forum], new_forum_path, :class => 

"utility" %></p>
<% end %>

如果是 admin 用户的话,那么就是显示 create new forum 链接,可以看到
这个 link_to 'Create New Forum'[:create_new_forum], new_forum_path
是一个 REST 的形式.

再下来有这么一段:
<%= feed_icon_tag "Recent Posts"[:recent_posts], formatted_all_posts_path(:format => 

'rss') %>


feed_icon_tag 是一个 helper 方法,其中 "formatted_all_posts_path" 是一个插件,这个插件
的作用就是生成  formatted_xxx_path 的 helper 方法. 可以格式化 url,具体使用方法可以

google
一下.

页面的最后有这么一段:
<%= link_to 'view', topic_path(:forum_id => forum, :id => forum.posts.last.topic_id, 

:page => forum.posts.last.topic.last_page, :anchor => forum.posts.last.dom_id) %>

这段就是一个嵌入式的 route 形式。
一个 topic 必须关联一个 forum_id, 所以会有“:forum_id => forum”。
后面的 “:page => forum.posts.last.topic.last_page, :anchor =>

forum.posts.last.dom_id” 则是参数。
实际的url 就好象这个样子:
http://beast.caboo.se/forums/3/topics/1007?page=1#posts-3037


到此 这个页面也没有其他的特殊的地方了,之后,就可以从这个页面延伸下去,逐渐
学习beast!
   
0 请登录后投票
最后更新时间:2007-05-12
今天来看一看 views/forums/index.rhtml 里的一个功能:创建新 forum.
在 index.rhtml 里,是这样的代码:
<% if admin? %>
<h6><%= 'Admin'[:admin_title] %></h6>
<p><%= link_to 'Create New Forum'[:create_new_forum], new_forum_path, :class => 

"utility" %></p>

<% end %>

这是一个标准的 REST的调用 new action 的方式。

按道理说,ForumsController 里应该有一个 new 的 action,但是在 controller 里,
我们却找不到这个 action, 看来对于标准的 REST 方法,可以不定义。

这样当创建新的论坛的时候,直接显示给我们 new.rhtml,只看其中的部分代码:

<% form_for :forum, :url => forums_path do |f| -%>
<%= render :partial => "form", :object => f %>
<%= submit_tag 'Create'[:Create], :or => link_to('Cancel'[:cancel], forums_path) %>
<% end -%>



这个 form 也是标准的 REST 方式。这个页面自然也会调用 _form.rhtml, 这个页面
就没什么可说的了。

new.rhtml 里的form,提交以后会调用  ForumsController 的  create action:

  def create
    @forum.attributes = params[:forum]
    @forum.save!
    respond_to do |format|
      format.html { redirect_to forums_path }
      format.xml  { head :created, :location => formatted_forum_url(:id => @forum,

:format => :xml) }
    end
  end

这个 action 就没什么好说的了,就是保存一条记录。


好了,通过学习,我们了解了一些 链接,form的 REST 的实际应用。
明天继续 !
   
0 请登录后投票
最后更新时间:2007-05-13
昨天学习了如何创建一个新的forum,今天我们还是先从 views/forums/index.rhtml 入手。
页面上有显示所有 forums 的代码,是一个循环:
<% for forum in @forums do %>  
...
...


我们还是先看看这个页面的admin所具有的功能:
<%= link_to 'Edit'[:edit_title], edit_forum_path(forum), :class => "tiny", :rel => "directory", :style => "float:right" if 

admin? %>

这行代码体现了2个地方:
1、 ruby 的习惯写法,就是这个 if,当只判断true 的时候,如果是 java,或许会这么写:
    if (condition) {

    }

    但是 ruby 却会这么写:
   
    do something if condition

    我想争论谁好谁坏是一个愚蠢的方式,既然使用ruby,就遵循ruby的方式好了 :-)

2. REST 的用法:
  
   edit_forum_path(forum) 这是调用 ForumsController 的 edit action.


好,那么就看看这个 edit action 的内容. ForumsController 竟然没有这个方法, 可见,beast
使用了默认的方法了,直接看看 edit.rhtml 吧!


<% form_for :forum,
     :url  => forum_path(@forum),
     :html => { :method => :put } do |f| -%>
<%= render :partial => "form", :object => f %>
<%= submit_tag 'Save Forum'[:save_forum], :or => link_to('Cancel'[:cancel], forums_path) %>
<% end -%>



这又是一个REST用法。在准备保存的form中,增加了一个:
:html => { :method => :put }
并且注意这个:
:url  => forum_path(@forum)
传递了一个参数 @forum, 这会保证会编辑哪一个 forum.
有点忘了 REST 内容?快回去看看吧!

页面上其他内容都没什么可说的,直接看看 update action:

  def update
    @forum.attributes = params[:forum]
    @forum.save!
    respond_to do |format|
      format.html { redirect_to forums_path }
      format.xml  { head 200 }
    end
  end

很简单,就是更新了一下数据。

今天就到这里,下次继续!
   
0 请登录后投票
最后更新时间:2007-05-14
今天继续学习,在首页上,我们已经学习过关于admin部分的代码,今天看看显示某一个forum 的部分吧。

在首页上,有这样的代码:
<%= link_to h(forum.name), forum_path(forum), :class => "title" %>

这是一个 REST 用法,会调用 ForumController的 show action.

看看这个 show action:
def show
    respond_to do |format|
      format.html do
        # keep track of when we last viewed this forum for activity indicators
        (session[:forums] ||= {})[@forum.id] = Time.now.utc if logged_in?
        (session[:forum_page] ||= Hash.new(1))[@forum.id] = params[:page].to_i if params[:page]
        @topic_pages, @topics = paginate(:topics, :per_page => 25, :conditions => ['forum_id = ?', @forum.id], :include => :replied_by_user, :order => 'sticky desc, replied_at desc')
      end
      format.xml { render :xml => @forum.to_xml }
    end
  end

这个 action 也没有什么特别的地方。我们直接去看看 views/forums/show.rhtml

看看这个循环显示所有topic 的部分:

<% for topic in @topics %>
  …. 省略
 <%= topic_title_link (topic), :class => "entry-title", :rel => "bookmark" %>
…省略
 <% if topic.paged? -%>
    <small><%= link_to 'last', topic_path(:forum_id => @forum, :id => topic, :page => topic.last_page) %></small>
    <% end -%>



其中这个 topic_title_link 是个helper方法,在ApplicationHelper里,其实也是调用了 “topic_path”.
但是我们需要注意这个代码:
topic_path(:forum_id => @forum, :id => topic, :page => topic.last_page)


为什么它不是下面这样呢?:
topic_path(@forum, topic, :page => topic.last_page)


因为 topic_path 是一个标准的helper 方法,根据 routes.rb, topic_path 只能接受两个参数: forum 和topic.

但是现在我们要同时传递过去一个 :page 参数,所以就必须用 “:forum_id=> ”这种形式了。



学习了这几天,对 REST 有什么感受呢?感觉就是在 REST中,链接之间的调用,就好像调用函数一样,传递不同的参数,生成不同的url.似乎是把url给抽象了。

好了,明天继续。
   
0 请登录后投票
最后更新时间:2007-05-15
昨天我们看到 topic_path 部分,这个 topic_path 生成的 url 会直接调用
TopicsController 的 show action.

看看这个 show action:

def show
    respond_to do |format|
      format.html do
        # see notes in application.rb on how this works
        update_last_seen_at
        # keep track of when we last viewed this topic for activity indicators
        (session[:topics] ||= {})[@topic.id] = Time.now.utc if logged_in?
        # authors of topics don't get counted towards total hits
        @topic.hit! unless logged_in? and @topic.user == current_user
        @post_pages, @posts = paginate(:posts, :per_page => 25, :order => 'posts.created_at', :include => :user, :conditions => ['posts.topic_id = ?', params[:id]])
        @post   = Post.new
      end
      format.xml do
        render :xml => @topic.to_xml
      end
      format.rss do
        @posts = @topic.posts.find(:all, :order => 'created_at desc', :limit => 25)
        render :action => 'show.rxml', :layout => false
      end
    end
  end

可以看出,这个 action 展示了 REST 的优势,一个调用,可以返回
三种结果。
   
0 请登录后投票
最后更新时间:2007-05-17
昨天看了 TopicController show 这个action, 今天来看看 show.rhtml.

这页的内容比较多,我们只挑主要的来看看。

这里的 REST的 helper方法我们就不看了,倒是有一个我们之前没遇到过,
就是 link_to_function, 这个方法就是给 link 加一个 javascript.

当然这个方法没什么好说的,但是可以去看看 public/javascript/application.js

我们可以学习一下beast里的js的写法。

今天的重点应该是学习那些 js,页面上的东西倒没什么新鲜的了。

我想这样的 js 应该会迷惑一些对js不是特别熟悉的人:
var TopicForm = {
  editNewTitle: function(txtField) {
    $('new_topic').innerHTML = (txtField.value.length > 5) ? txtField.value : 'New Topic';
  }
}


换一种写法或许会看明白:

<script>
var xxx = {
  aaa: "This is AAA",  bbb: "This is BBB"
}
alert(xxx.aaa);
</script>



其实这就是:

var xxx={a:1,b:2}
相当于
var xxx=new Object();
xxx.a=1;
xxx.b=2;




beast 里的变量都是函数。

就是这样!

但是,很明显,beast 这样的写法更容易阅读,是不是从中学到了一些什么呢?



明天,我们继续看看 edit topic 方法。
   
0 请登录后投票
最后更新时间:2007-05-17
今天来看看如何编辑一个 topic. 还是从  views/topic/show.rhtml 看起。

<h1 id="topic-title" style="margin-top:0.5em;"<%= %( onmouseover="$('topic_mod').show();" onmouseout="$('topic_mod').hide();") if logged_in? %>>


这段代码用了一个小小的javascript,如果鼠标移动到上面,那么显示 "topic_mod"这个div,
如果鼠标移走,那么隐藏这个div.
那么,这个div 是什么内容呢?我们来看看:
<span style="display:none;" id="topic_mod">
      <% if @topic.editable_by?(current_user) -%>
        <%= link_to('edit'[], edit_topic_path(@forum, @topic), :class => "utility") %> |
        <%= link_to('delete'[], topic_path(@forum, @topic), :class => "utility", :method => :delete, :confirm => 'Delete this topic forever?'[:delete_conf]) %>
      <% end -%>
    </span>


其实就是2个链接:编辑和删除。这个构造URL的方式我们就不用说了,看了这么多天,
一看这就是标准的REST应用。
我们直接去看看 TopicController 的 edit 方法吧。
不出所料。。。Controller里没这个方法,看来还是使用默认的,好,那么直接去看
edit.rhtml!

也是很简单的页面,就是一个标准的REST编辑页面。
这个编辑页面最后会调用 Controller的 update 方法,这个方法也很简单,
实在没什么可说的。

同样,删除一个topic 也是很简单,就不罗唆了 :-)


明天看看如何创建一个 topic!
   
0 请登录后投票
最后更新时间:2007-05-20
创建一个新的 topic, 我们先回到 views/forums/show.rhtml 里,最后一行有下面的
代码:
<p><%= link_to 'New topic'[:new_topic], new_topic_path(@forum), :class => "utility" 

%></p>


这个链接会调用 TopicController 的 new 方法。

这个方法很简单:

  def new
    @topic = Topic.new
  end

好,再去看看 new.rhtml:

涉及到 form 的代码很简单:

<% form_for :topic,
     :url  => topics_path(@forum) do |f| -%>
<%= render :partial => "form", :object => f %>
<%= submit_tag 'Post Topic'[], :or => link_to('Cancel'[], forum_path(@forum)) %>
<% end -%>



这是标准的REST调用,没什么可以说的了,直接去看看 Topic Controller的create 方法。

def create
    # this is icky - move the topic/first post workings into the topic model?
    Topic.transaction do
      @topic  = @forum.topics.build(params[:topic])
      assign_protected
      @post   = @topic.posts.build(params[:topic])
      @post.topic=@topic
      @post.user = current_user
      # only save topic if post is valid so in the view topic will be a new record

if there was an error
      @topic.save! if @post.valid?
      @post.save!
    end
    respond_to do |format|
      format.html { redirect_to topic_path(@forum, @topic) }
      format.xml  { head :created, :location => formatted_topic_url(:forum_id =>

@forum, :id => @topic, :format => :xml) }
    end
  end

这个 create 首先用了一个事务,其次使用了 build 方法构造了2个提交的对象。
然后保存了 topic 和 post.

这个逻辑并不复杂,只是之前我们没有看到过事务的使用。
   
0 请登录后投票
论坛首页 入门讨论版 Ruby

跳转论坛:
JavaEye推荐