|
该帖已经被评为良好帖
|
|
|---|---|
| 作者 | 正文 |
|
最后更新时间: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 上找到,而且很详细, 我们快去看吧 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
最后更新时间: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! |
|
| 返回顶楼 | |
|
最后更新时间: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,具体使用方法可以 一下. 页面的最后有这么一段: <%= 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! |
|
| 返回顶楼 | |
|
最后更新时间: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 的实际应用。 明天继续 ! |
|
| 返回顶楼 | |
|
最后更新时间: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 很简单,就是更新了一下数据。 今天就到这里,下次继续! |
|
| 返回顶楼 | |
|
最后更新时间: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给抽象了。 好了,明天继续。 |
|
| 返回顶楼 | |
|
最后更新时间: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 的优势,一个调用,可以返回 三种结果。 |
|
| 返回顶楼 | |
|
最后更新时间: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 方法。 |
|
| 返回顶楼 | |
|
最后更新时间: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! |
|
| 返回顶楼 | |
|
最后更新时间: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. 这个逻辑并不复杂,只是之前我们没有看到过事务的使用。 |
|
| 返回顶楼 | |



