月归档:四月 2007

Ruby on Rails HTML表单语句大全

表单开始标签:
<%= form_tag { :action => :save }, { :method => :post } %>
Use :multipart => true to define a Mime-Multipart form (for file uploads)
表单结束标签:
<%= end_form_tag %>

文本框 Text fields
<%= text_field :modelname, :attribute_name, options  %>
生成:
<input type="text" name="modelname[attribute_name]" id="attributename" />

实例:

text_field "post", "title", "size" => 20
    <input  type="text" id="post_title" name="post[title]"
            size="20" value="#{@post.title}" />

隐藏框:
<%= hidden_field … %>

密码框:
<%= password_field … %>

文件框
<%= file_field … %>

Rails Textarea框
<%= text_area … %>
实例:
text_area "post", "body", "cols" => 20, "rows" => 40
    <textarea cols="20" rows="40" id="post_body" name="post[body]">
        #{@post.body}
    </textarea>

单选框 Radio Buttons
<%= radio_button :modelname, :attribute, :tag_value, options %>
实例:
radio_button "post", "category", "rails"
radio_button "post", "category", "java"
    <input type="radio" id="post_category" name="post[category]" value="rails"
           checked="checked" />
    <input type="radio" id="post_category" name="post[category]" value="java" />

多选框 Check Box
<%= check_box :modelname, :attribute, options, on_value, off_value %>
实例
check_box "post", "validated"   # post.validated? returns 1 or 0
    <input type="checkbox" id="post_validate" name="post[validated]"
           value="1" checked="checked" />
    <input name="post[validated]" type="hidden" value="0" />

check_box "puppy", "gooddog", {}, "yes", "no"
    <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
    <input name="puppy[gooddog]" type="hidden" value="no" />

<%= select :variable, :attribute, choices, options, html_options %>

下拉菜单框 Select Menu
select  "post",        
        "person_id",   
        Person.find_all.collect {|p| [ p.name, p.id ] },
        { :include_blank => true }

 <select name="post[person_id]">
   <option></option>
   <option value="1" selected="selected">David</option>
   <option value="2">Sam</option>
   <option value="3">Tobias</option>
 </select>

Collection Selection
<%= collection_select :variable, :attribute, choices, :id, :value %>

日期选择框:
<%= date_select :variable, :attribute, options %>
<%= datetime_select :variable, :attribute, options %>
实例:
date_select "post", "written_on"
date_select "user", "birthday", :start_year => 1910
date_select "user", "cc_date", :start_year => 2005,
                               :use_month_numbers => true,
                               :discard_day => true,
                               :order => [:year, :month]

datetime_select "post", "written_on"

发表在 Ruby on Rails | 留下评论

Ruby on Rails数据库操作

数据库元被影射成对象
(object-relational mapping (ORM)层)
table<=>class
row<=>object
column<=>object attribute

class和table的命名对应关系实例
Order<=>orders
TaxAgency<=>tax_agencies
Person<=>people

复数形式关闭方法config/environment.rb:
ActiveRecord::Base.pluralize_table_names = false
自定义表格名称方法:
class Sheep < ActiveRecord::Base
 set_table_name "sheep"
end

Ruyb数据类型和SQL数据类型对应关系表
int,integer<=>Fixnum
float,double,decimal,numeric<=>Float
interval,date<=>Date
datetime,time<=>Time
char,varchar,string,clob,blob,text<=>String
boolean<=>see text…

访问属性(数据库列):
account[:balance] #=> 返回当前值
account[:balance] = 0.0 #=> 指定数值

修正数据库列的取值范围的方法:
class Account < ActiveRecord::Base
 def balance=(value)
  raise BalanceTooLow if value < MINIMUM_LEVEL
  self[:balance] = value
 end
end

访问属性(数据库列)更方便的方法:
account.balance #=> 返回当前值
account.balance = 0.0 #=> 指定数值

以上方式得到的数据库数据将是ruby按自身的数据类型格式化好的,如果要得到原始数据,可用以下形式代码:
account.balance_before_type_cast #=> "123.4", 字符串
account.release_date_before_type_cast #=> "20050301"

是非属性
在ruby中只有falsenil才被判断为false

数据库主键(Primary Keys)
Ruby on Rails默认以id为主键

自定义主键的方法:
class BadBook < ActiveRecord::Base
 set_primary_key "isbn"
end

数据创建,读取,更新和删除(CRUD:Create, Read, Update, Delete)

创建新数据

实例:
an_order = Order.new
an_order.name ="Dave Thomas"
an_order.save #在save()之前所有数据只存在内存中

用以下方式可以减少产生一个an_order变量:
Order.new do |o|
 o.name = "Dave Thomas"
 # . . .
 o.save
end

当数据来自HTML表单时,可以考虑用以下方式:
an_order = Order.new(
 :name =>"Dave Thomas",
#…
)
an_order.save

使用create()代换new()可直接保存到数据库,省去an_order.save:
an_order = Order.create(
 :name => "Dave Thomas")

可以使用hash同时保存多组数据:
 orders = Order.create(
  [ { :name =>"Dave Thomas" },
   { :name =>"Andy Hunt" } ] )

new()或create()也可以直接接参数:
order = Order.create(params)

读取数据
an_order = Order.find(27)
# 直接找出id = 27的数据,可以同时读取多个object.
an_order = Order.find_by_id(27)
# 效果类似,但一次只能读一个,在找不到符合的object时返回nil,而不是错误。
# 从一个表单读取product id列表,然后计算这些商品的总价:
product_list = params[:product_ids]
total = 0.0
Product.find(product_list).each {|prd| total += prd.total}

带条件的读取:
pos = Order.find(:all,
:conditions => "name = ‘dave’ and pay_type = ‘po’")

不安全的表单参数传递读取数据库:
name = params[:name]
# 此方法有被SQL注入方式入侵的风险!!!
pos = Order.find(:all,
:conditions =>"name = ‘#{name}‘ and pay_type = ‘po’")
#注意上面单双引号的使用及变量的传递方法

更安全的方法:
name = params[:name]
pos = Order.find(:all,
:conditions => ["name = ? and pay_type = 'po'", name])

你也可以这样:
name = params[:name]
pay_type = params[:pay_type]
pos = Order.find(:all,
:conditions => ["name = :name and pay_type = :pay_type",
{:pay_type => pay_type, :name => name}])

终极简化版:
pos = Order.find(:all,
:conditions => ["name = :name and pay_type = :pay_type", params])

排序和查找第3(?)至13(?)列的方法:
orders = Order.find(:all,
 :conditions =>"name = ‘Dave’",
 :order =>"pay_type, shipped_at DESC",
 :limit => 10
 :offset => 2)

联合数据表的查找方法(一般用不上):
LineItem.find(:all,
:conditions => "pr.title = ‘Programming Ruby’",
:joins =>"as li inner join products as pr on li.product_id = pr.id")

查找有序一列的方法:
order = Order.find( :first,
 :conditions =>"name = ‘Dave Thomas’",
 :order => "id DESC")

直接使用sql语句的查询方法:
items = LineItem.find_by_sql("select *, quantity*unit_price as total_price,products.title as title from line_items, products where line_items.product_id = products.id ")
li = items[0]
puts "#{li.title}: #{li.quantity}x#{li.unit_price} => #{li.total_price}"
你可以使用"as".

在这里你也可以传递参数:
Order.find_by_sql(["select * from orders where amount > ?",
params[:amount]])

计算行数
c1 = Order.count
c2 = Order.count(["name = ?", "Dave Thomas"])
c3 = LineItem.count_by_sql("select count(*) from line_items, orders  where line_items.order_id = orders.id and orders.name = ‘Dave Thomas’ ")
puts "Dave在#{c2}个定单里一共定了#{c3} 件商品 (目前定单总数:#{c1})"

动态查询
order = Order.find_by_name("Dave Thomas")#只查一列
orders = Order.find_all_by_name("Dave Thomas")
order = Order.find_all_by_email(params['email'])

可同时查多个条件,如:
user = User.find_by_name_and_password(name, pw)

重载数据库
stock = Market.find_by_ticker("RUBY")
loop do
 puts "Price = #{stock.price}"
 sleep 60
 stock.reload
end

更新数据
使用save()
order = Order.find(123)
order.name = "Fred"
order.save

orders = Order.find_by_sql("select id, name, pay_type from orders where id=123")
first = orders[0]
first.name ="Wilma"
first.save

使用update_attribute()
order = Order.find(123)
order.update_attribute(:name,"Barney")
order = Order.find(321)
order.update_attributes(:name => "Barney",
:email =>"barney@bedrock.com")

使用更快捷的update()
order = Order.update(12, :name => "Barney", :email => "barney@bedrock.com")

使用update_all()
result = Product.update_all("price = 1.1*price", "title like ‘%Java%‘")

save()和save!()
save()失败时返回nil
if order.save
 # 成功
else
 # 保存失败则…
end

save!()失败时出错
begin
 order.save!
rescue RecordInvalid => error
 # 保存失败RecordInvalid exception
end

数据锁(防止数据保存撞车)
加段:lock_version int default 0,

删除数据
delete()删除
Order.delete(123)
User.delete([2,3,4,5])
Product.delete_all(["price > ?", @expensive_price])

destroy()冻结(在model层面)
order = Order.find_by_name("Dave")
order.destroy
# … order将被冻结

数据库关系(相关键)参看:Ruby on Rails数据表格关系

发表在 Ruby on Rails | 留下评论

Ruby on Rails常用内置方法

对象是否为空blank?
puts [ ].blank? #=> true
puts { 1 => 2}.blank? #=> false
puts " cat ".blank? #=> false
puts "".blank? #=> true
puts " ".blank? #自动去除前后空格=> true
puts nil.blank? #=> true

排序:group_by()
groups = posts.group_by {|post| post.author_id}

组句:to_sentence()
puts [ "ant", "bat", "cat"].to_sentence #=> "ant, bat, and cat"
puts [ "ant", "bat", "cat"].to_sentence(:connector => "and not forgetting")
#=> "ant, bat, and not forgetting cat"
puts [ "ant", "bat", "cat"].to_sentence(:skip_last_comma => true)
#=> "ant, bat and cat"

分组:in_groups_of()
[1,2,3,4,5,6,7].in_groups_of(3) {|slice| p slice}
#=> [1, 2, 3]
[4, 5, 6]
[7,nil, nil]
[1,2,3,4,5,6,7].in_groups_of(3,"X") {|slice| p slice}
#=> [1, 2, 3]
[4, 5, 6]
[7,"X", "X"]

字符串方法:
string = "Now is the time"
puts string.at(2) #=> "w"
puts string.from(8) #=> "he time"
puts string.to(8) #=> "Now is th"
puts string.first #=> "N"
puts string.first(3) #=> "Now"
puts string.last #=> "e"
puts string.last(4) #=> "time"
puts string.starts_with?("No") #=> true
puts string.ends_with?("ME") #=> false

count = Hash.new(0)
string.each_char {|ch| count[ch] += 1}
p count #=> {" "=>3, "w"=>1, "m"=>1, "N"=>1, "o"=>1, "e"=>2, "h"=>1, "s"=>1, "t"=>2, "i"=>2}

改变字符串的显示格式:
puts "cat".pluralize #=> cats
puts "cats".pluralize #=> cats
puts "erratum".pluralize #=> errata
puts "cats".singularize #=> cat
puts "errata".singularize #=> erratum
puts "first_name".humanize #=> "First name"
puts "now is the time".titleize #=> "Now Is The Time"
你可以自定义特定单词的单复数转换规则

数字方法:
puts 3.ordinalize #=> "3rd"
puts 321.ordinalize #=> "321st"

数量方法:
puts 20.bytes #=> 20
puts 20.kilobytes #=> 20480
puts 20.megabytes #=> 20971520
puts 20.gigabytes #=> 21474836480
puts 20.terabytes #=> 21990232555520
puts 20.petabytes #=> 22517998136852480
puts 1.exabyte #=> 1152921504606846976

时间方法:
puts 20.seconds #=> 20
puts 20.minutes #=> 1200
puts 20.hours #=> 72000
puts 20.days #=> 1728000
puts 20.weeks #=> 12096000
puts 20.fortnights #=> 24192000
puts 20.months #=> 51840000
puts 20.years #=> 630720000

puts Time.now #=> Thu May 18 23:29:14 CDT 2006
puts 20.minutes.ago #=> Thu May 18 23:09:14 CDT 2006
puts 20.hours.from_now #=> Fri May 19 19:29:14 CDT 2006
puts 20.weeks.from_now #=> Thu Oct 05 23:29:14 CDT 2006
puts 20.months.ago #=> Sat Sep 25 23:29:16 CDT 2004
puts 20.minutes.until("2006-12-25 12:00:00".to_time)
#=> Mon Dec 25 11:40:00 UTC 2006
puts 20.minutes.since("2006-12-25 12:00:00".to_time)
#=> Mon Dec 25 12:20:00 UTC 2006

now = Time.now
puts now #=> Thu May 18 23:36:10 CDT 2006
puts now.to_date #=> 2006-05-18
puts now.to_s #=> Thu May 18 23:36:10 CDT 2006
puts now.to_s(:short) #=> 18 May 23:36
puts now.to_s(:long) #=> May 18, 2006 23:36
puts now.to_s(:db) #=> 2006-05-18 23:36:10
puts now.to_s(:rfc822) #=> Thu, 18 May 2006 23:36:10 -0500
puts now.ago(3600) #=> Thu May 18 22:36:10 CDT 2006
puts now.at_beginning_of_day #=> Thu May 18 00:00:00 CDT 2006
puts now.at_beginning_of_month #=> Mon May 01 00:00:00 CDT 2006
puts now.at_beginning_of_week #=> Mon May 15 00:00:00 CDT 2006
puts now.beginning_of_quarter #=> Sat Apr 01 00:00:00 CST 2006
puts now.at_beginning_of_year #=> Sun Jan 01 00:00:00 CST 2006
puts now.at_midnight #=> Thu May 18 00:00:00 CDT 2006
puts now.change(:hour => 13) #=> Thu May 18 13:00:00 CDT 2006
puts now.last_month #=> Tue Apr 18 23:36:10 CDT 2006
puts now.last_year #=> Wed May 18 23:36:10 CDT 2005
puts now.midnight #=> Thu May 18 00:00:00 CDT 2006
puts now.monday #=> Mon May 15 00:00:00 CDT 2006
puts now.months_ago(2) #=> Sat Mar 18 23:36:10 CST 2006
puts now.months_since(2) #=> Tue Jul 18 23:36:10 CDT 2006
puts now.next_week #=> Mon May 22 00:00:00 CDT 2006
puts now.next_year #=> Fri May 18 23:36:10 CDT 2007
puts now.seconds_since_midnight #=> 84970.423472
puts now.since(7200) #=> Fri May 19 01:36:10 CDT 2006
puts now.tomorrow #=> Fri May 19 23:36:10 CDT 2006
puts now.years_ago(2) #=> Tue May 18 23:36:10 CDT 2004
puts now.years_since(2) #=> Sun May 18 23:36:10 CDT 2008
puts now.yesterday #=> Wed May 17 23:36:10 CDT 2006
puts now.advance(:days => 30) #=> Sat Jun 17 23:36:10 CDT 2006
puts Time.days_in_month(2) #=> 28
puts Time.days_in_month(2, 2000) #=> 29

日期方法:
date = Date.today
puts date.to_s #=> "2006-05-18"
puts date.to_time #=> Thu May 18 00:00:00 CDT 2006
puts date.to_s(:short) #=> "18 May"
puts date.to_s(:long) #=> "May 18, 2006"
puts date.to_s(:db) #=> "2006-05-18"

将字符串转成时间日期:
puts "2006-12-25 12:34:56".to_time #=> Mon Dec 25 12:34:56 UTC 2006
puts "2006-12-25 12:34:56".to_date #=> 2006-12-25

发表在 Ruby on Rails | 留下评论

Ruby on Rails项目文件目录结构和环境设置

Ruby on Rails项目文件目录结构
README 说明文档
Rakefile 生成脚本
app/ 存放项目的Model, View和Controller等文件
components/  存放可重复使用的组件, 现在已经不大使用
config/ 配置文档及数据库连接配置文档
db/ 存放数据库schema和migration信息
doc/  存放自动生成的文档
lib/ 存放在Model, View和Controller间共享的代码
  为方便整理,不同功能的代码可放到其不同的子目录下.

  调用sub_dir下的my_lib_code.rb代码的方法为: require "sub_dir/my_lib_code"
log/ 存放程序生成的日志文件
  里面有三个最主要的log为:development.log, test.log和production.log,分别对应不同的运行环境.
public/ 存放对网络公开的文件
script/ 存放脚本工具
  不带参数直接运行大多数的脚本工具可以显示出相应的使用帮助信息.
  常见的script:
  about
  breakpointer
  destroy<=>generate
  server
  script/performance/benchmarker
test/  测试工具
tmp/  存放临时文件, 如缓存, session, socket…
vendor/ 存放外来代码, 如外来插件, 也可用来放rails构架本身, 以使rails兼容不同版本需求的程序.

运行环境
使用-e XXXX切换运行环境:
ruby script/server -e production #默认为development

配置数据库参数
修改config/database.yml文件

配置环境
修改config/environment.rb文件.

命名规范
所有变量以小写字母单词命名,单词间用下划线"_"分隔. 如order_status
所有class和module以大写开头的单词命名,单词间直接连接,如: LineItem

数据库中所有表格命名形式同变量名,且全为复数形式,如:orders
所有文件名命名形式也同变量名

Rails可推论相关文件名, 所以在框架内可以节省很多require "xxx". Rails会自动加入相关的调用.
Rails关于文件名的思维逻辑实例:
如果一个class名为:LineItem
ralis将推出:
1.此class对应的数据库表格名为line_items.
2.此class对应的文件名为line_item.rb
….

创建model下的controller
ruby script/generate controller Admin::Book action1 action2 …

使用debug() helper method
<%=debug(@order) %>

发表在 Ruby on Rails | 2 条评论

那个下午

那个下午,车从诺日朗出发

选了靠走道的位置坐下。不一会,他坐到了我前面。
眼睛大大的,个子很高的男孩,背了一个庞大的包,绑了一个相机的脚架。
一直没有交谈,只是沉默地乘车。
途中有绝美的风景,按捺不住,站起来叫停车。
他也站起来,跟我一起下车。
沿着木栈道,我们一前一后地走。
偶也停下来拍照,也是默不作声,各拍各的。
犀牛海是那天看到的最漂亮的海子。
阳光落在翠蓝的水面上,化成不停摇曳的亮片。
海边一排灌木,深秋时节,细叶泛红泛黄。

那个下午
犀牛海

发呆了一会,转身从包里拿东西时又看见了他。
他在不远处拍照。
看见我,他笑了笑,露出整齐而洁白的牙齿。
他走过来在我旁边坐下,给我看他刚拍的照片。
就这样认识了,他叫KEN,南方人,《华夏人文地理》的签约摄影师。
KEN对我说,每张照片其实都是记录一次消亡。
在按下快门的一瞬间,那样的景色已经成为过去,并且永远不复存在。

KEN不是很健谈的人,我也不是。
很多时候我们都沉默地享受阳光。
微微地眯起眼睛,享受那种快被晒成一滩软泥的感受。
后来,KEN教我当地的藏语。
睡觉念”呢“,谈恋爱念”哈给吃“,晒大阳念”应该的“。
渴了,KEN从硕大的包里找出两个红苹果,递了一个给我。
果皮红润透亮,水灵干净,很细致的男人。

大阳落山前我们道别。
他要去拍火花海的月夜,在沟里停留这么多天,为的就是这一晚的月圆。
我继续上路,没有目的,没有打算。
分开时他问我要手机号码。
我笑笑,告诉他之后不会有任何联系。
这样平静地邂逅,然后彼此消失,才是旅途中的人和事。
也会一直记得彼此。
是。一直记得
那个下午。

原作:曾珍《果壳里的村寨》 剪辑改编:卯时下雨

发表在 某时雨集 | 留下评论

Ruby on Rails实战–创建一个网上商店F收尾工作

上一节:Ruby on Rails实战–创建一个网上商店E用户管理模块

本节是depot电子商店的项目的最后一节。将完成生成XML Feed,并介绍如何生成项目文档。
生成通过商品ID反查定购用户信息的XML Feed.

orders的id和关联line_items表中的order_id,而line_items表中的product_id和products表中的id相关联, 通过orders可以查products的id,现在建立反向关联.
在depot/app/models/product.rb文件加入:
has_many :orders, :through => :line_items
使product通过line_item查order信息

新建一个info controller:

depot> ruby script/generate controller info
controllers/info_controller.rb文件内容为:
class InfoController < ApplicationController
  def who_bought
    @product = Product.find(params[:id])
    @orders = @product.orders
    respond_to do |accepts| #下面代码根据request的形式选择发送html还是xml格式内容
      accepts.html
      accepts.xml
    end
  end
end

加入html形式内容页面:depot/app/views/info/who_bought.rhtml,内容如下:
<h3>People Who Bought <%= @product.title %></h3>
<ul>
 <% for order in @orders -%>
  <li> <%= mail_to order.email, order.name %> </li>
 <% end -%>
</ul>

加入xml形式内容页面:depot/app/views/info/who_bought.rxml,内容如下:
xml.order_list(:for_product => @product.title) do
  for o in @orders
    xml.order do
      xml.name(o.name)
      xml.email(o.email)
    end
  end
end

现在通过http://localhost:3000/info/who_bought/1 地址就可以查出商品1(ID)的定购者信息了.

 

生成开发文档RDoc

项目中的doc/README_FOR_APP文件可以加入项目相关的说明信息.
使用depot> rake doc:app 可以生成项目的开发文档文件. 程序对项目中所有文件,classes和methods进行分析, 列出相关信息. 文件为html格式.

至此depot项目完成. 一个用Ruby on Rails续建的简单的网上商店完成了!

发表在 Ruby on Rails | 留下评论

绝对的经典–评越剧<五女拜寿>

岁寒方知松柏健
患难相守品德全
寿堂重叙天伦乐
情深和睦笑开颜
绝对的经典--评越剧<五女拜寿>

演出:浙江省小百花越剧团
编剧:顾锡东

看名字我以为这只是一场用来在节目表演的低俗奉承戏,但我错了,没想到这戏里面包容着这么多的人间甜酸.

越剧<五女拜寿>是绝对的经典.

第一是情节, 故事有起有伏, 有主有支, 有定有变… 以抒情见长的越剧突现出被磨灭了的述事能力.

第二是演员, 虽然没有哪个余音绕梁, 但都把角色表现得活灵活现, 有血有肉. 台上哪个可爱,哪个可恨, 哪个看了让人同情, 哪个看了让人谩骂, 历历在目!
没有主角, 没有配色, 是这个剧团,一个整体完成了这个杰作!
绝对的经典--评越剧<五女拜寿>
1984年<五女拜寿>电影版的海报

演员表:
董柯娣饰杨继康 徐爱武饰杨夫人
周美姣饰元芳(长女) 邵雁饰俞子云(长婿)
吴海丽饰双桃(次女)陈筱珍饰丁大富(次婿)
何英饰三春(三女) 方雪雯饰邹应龙(三婿)
应惠珠饰四春(四女)傅江凤饰陈文新(四婿)
陶慧敏饰五凤(五女) 江瑶饰陈文华(五婿)
茅威涛饰邹士龙 何赛飞饰翠云
王连琴饰陈松年 俞会珍饰陈夫人

发表在 电影评论, 越剧柔情 | 留下评论

Ruby on Rails实战–创建一个网上商店E用户管理模块

本节创建商店的用户管理系统. 创建存放用户资料的数据表,加入添加,删除用户功能, 后台管理用户权限审查功能,和用户登录 退出功能.
创建用户model: depot> ruby script/generate model user
修改depot/db/migrate/007_create_users.rb文件内容如下:
class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.column :name, :string
      t.column :hashed_password, :string
      t.column :salt, :string #生成密码hash用的salt
    end
  end

 
  def self.down
    drop_table :users
  end
end

应用depot> rake db:migrate

修改depot/app/models/user.rb文件内容如下:
require ‘digest/sha1′
#用来生成密码hash值
class User < ActiveRecord::Base

#检验添加用户表格
  validates_presence_of :name,
  :password,
  :password_confirmation
  validates_uniqueness_of :name
  validates_length_of :password,
  :minimum => 5,
  :message => "should be at least 5 characters long"
  attr_accessor :password_confirmation
  #自动"再输入一次密码"的检验
  validates_confirmation_of :password
 
  # ‘password’ 是一个 virtual attribute
  def password
    @password
  end
 
  def password=(pwd)
    @password = pwd
    create_new_salt
    self.hashed_password = User.encrypted_password(self.password, self.salt)
  end
 
  def self.authenticate(name, password)
    user = self.find_by_name(name)
    if user
      expected_password = encrypted_password(password, user.salt)
      if user.hashed_password != expected_password
        user = nil
      end
    end
    user
  end
 
  #安全删除,当试图删除用户表中最后一个用户时rollback数据库内容
  def safe_delete
    transaction do
      destroy
      if User.count.zero?
        raise "Can’t delete last user"
      end
    end
  end
 
  private
 
  def self.encrypted_password(password, salt)
    string_to_hash = password + "wibble" + salt
    Digest::SHA1.hexdigest(string_to_hash)
  end
 
  def create_new_salt
    self.salt = self.object_id.to_s + rand.to_s
  end
end

生成Login controller
depot> ruby script/generate controller Login add_user login logout delete_user list_users
后面接的大堆都是login里面的actions.
depot/app/controllers/login_controller.rb文件内容为:
class LoginController < ApplicationController
  #所有actions,除了login都要进行权限审查
  before_filter :authorize, :except => :login
  #加入admin的模板
  layout"admin"
 
  def index
    @total_orders = Order.count
  end
 
  def add_user
    @user = User.new(params[:user])
    if request.post? and @user.save
 #requset.post?用来检验request是否POST形式
      flash[:notice] = "User #{@user.name} created"
      @user = User.new
    end
  end
 
  def login
    session[:user_id] = nil
    if request.post?
      user = User.authenticate(params[:name], params[:password])
      if user
        session[:user_id] = user.id
        #如果原来页面存在,返回原先页面
        redirect_to(session[:original_url] || { :action => "index" })
      else
        flash[:notice] = "Invalid user/password combination"
      end
    end
  end
 
  def logout
    session[:user_id] = nil
    flash[:notice] = "Logged out"
    redirect_to(:action => "login")
  end
 
  def delete_user
    id = params[:id]
    if id && user = User.find(id)
      begin
   #安全删除
        user.safe_delete
        flash[:notice] = "User #{user.name} deleted"
      rescue Exception => e
        flash[:notice] = e.message
      end
    end
    redirect_to(:action => :list_users)
  end
 
  def list_users
    @all_users = User.find(:all)
  end
end

修改depot/app/controllers/application.rb文件,添加用户权限审查代码:
class ApplicationController < ActionController::Base
  private
  def authorize
    unless User.find_by_id(session[:user_id])
      session[:original_uri] = request.request_uri #记住原来页面
      flash[:notice] = "Please log in"
      redirect_to(:controller => "login", :action => "login")
    end
  end
end

向depot/app/controllers/admin_controller.rb文件加入审查要求:
class AdminController < ApplicationController
  before_filter :authorize

  #….

修改管理页面使用的页面模板depot/app/views/layouts/admin.rhtml,内容如下:
<html>
 <head>
  <title>Administer the Bookstore</title>
  <%= stylesheet_link_tag "scaffold", "depot", :media => "all" %>
 </head>
 <body id="admin">
  <div id="banner">
   <img src="/images/logo.png"/>
   <%= @page_title || "Pragmatic Bookshelf" %>
  </div>
  <div id="columns">
   <div id="side">
    <p>
     <%= link_to "Products", :controller => ‘admin’, :action => ‘list’ %>
    </p>
    <p>
     <%= link_to "List users", :controller => ‘login’, :action => ‘list_users’ %>
     <br/>
     <%= link_to "Add user", :controller => ‘login’, :action => ‘add_user’ %>
    </p>
    <p>
     <%= link_to "Logout", :controller => ‘login’, :action => ‘logout’ %>
    </p>
   </div>
   <div id="main">
    <% if flash[:notice] -%>
     <div id="notice"><%= flash[:notice] %></div>
    <% end -%>
    <%= @content_for_layout %>
   </div>
  </div>
 </body>
</html>

添加用户页面depot/app/views/login/add_user.rhtml内容:
<div class="depot-form">
 <%= error_messages_for ‘user’ %>
 <fieldset>
  <legend>Enter User Details</legend>
  <% form_for :user do |form| %>
   <p>
    <label for="user_name">Name:</label>
    <%= form.text_field :name, :size => 40 %>
    <!–上面代码自动生成input框–>
   </p>
   <p>
    <label for="user_password">Password:</label>
    <%= form.password_field :password, :size => 40 %>
   </p>
   <p>
    <label for="user_password_confirmation">Confirm:</label>
    <%= form.password_field :password_confirmation, :size => 40 %>
   </p>
   <%= submit_tag "Add User", :class => "submit" %>
  <% end %>
 </fieldset>
</div>

用户登录页面depot/app/views/login/login.rhtml内容为:
<div class="depot-form">
 <fieldset>
  <legend>Please Log In</legend>
  <%= start_form_tag %>
  <!–form开始–>
   <p>
    <label for="name">Name:</label>
    <%= text_field_tag :name, params[:name] %>
   </p>
   <p>
    <label for="password">Password:</label>
    <%= password_field_tag :password, params[:password] %>
   </p>
   <p>
    <%= submit_tag "Login" %>
   </p>
  <%= end_form_tag %>
 </fieldset>
</div>

用户主页depot/app/views/login/index.rhtml内容:
<h1>Welcome</h1>
It’s <%= Time.now %>.
We have <%= pluralize(@total_orders,"order") %>.
<!–pluralize自动根据@total_orders的数量决定order使用单数还是复数形式–>

用户列表页面depot/app/views/login/list_users.rhtml内容:
<h1>Administrators</h1>
<ul>
 <%  for user in @all_users %>
  <li><%= link_to "[X]", { # link_to 参数
  #在用户名前自动添加删除用户的链接
  :controller => ‘login’,
  :action => ‘delete_user’,
  :id => user},
  { # html 参数
  :post => true,
  :confirm => "Really delete #{user.name}?"
  } %>
  <%= user.name %>
  </li>
 <% end %>
</ul>

整理代码:
在depot/app/controllers/store_controller.rb中加入:
  before_filter :find_cart, :except => :empty_cart
  def find_cart
    @cart = (session[:cart] ||= Cart.new)
  end
并删除其它散杂的@cart赋值语句.

本节结束.

发表在 Ruby on Rails | 留下评论

Ruby on Rails实战–创建一个网上商店D收银台

上一节:Ruby on Rails实战–创建一个网上商店C小试Ajax
本节完成收银台功能. 为页面新增一个结账按钮,用户挑选商品后点击结账按钮时出现一个用户资料表单,用户填交后,系统将商品信息和用户信息保存到数据库中.

创建order model
depot> ruby script/generate model order
修改depot/db/migrate/005_create_orders.rb 内容为:
class CreateOrders < ActiveRecord::Migration
  def self.up
    create_table :orders do |t|
      t.column :name, :string
      t.column :address, :text
      t.column :email, :string
      t.column :pay_type, :string, :limit => 10
    end
  end

  def self.down
    drop_table :orders
  end
end

创建line_item model,
depot>ruby script/generate model line_item
修改depot/db/migrate/006_create_line_items.rb文件内容为:
class CreateLineItems < ActiveRecord::Migration
  def self.up
    create_table :line_items do |t|
      t.column :product_id, :integer, :null => false
      t.column :order_id, :integer, :null => false
      t.column :quantity, :integer, :null => false
      t.column :total_price, :integer, :null => false
    end
 #创建两个foreign key
    execute "alter table line_items
            add constraint fk_line_item_products
            foreign key (product_id) references products(id)"

    execute "alter table line_items
            add constraint fk_line_item_orders
            foreign key (order_id) references orders(id)"
  end
 
  def self.down
    drop_table :line_items
  end
end

向数据库应用
depot> rake db:migrate

关联model:
在depot/app/models/order.rb和
depot/app/models/product.rb两文件者加入:
has_many :line_items
声明这两个model下有多个line_items
在depot/app/models/line_item.rb文件中加入:
belongs_to :order
belongs_to :product
声明这个model从属于order和product
*if a table has foreign keys, the corresponding model should have a belongs_to for each.

添加结账按钮:
在depot/app/views/store/_cart.rhtml文件:
<%= button_to "Empty cart", :action => :empty_cart %>
行上方加入:
<%= button_to "Checkout", :action => :checkout %>

checkout action
在depot/app/controllers/store_controller.rb文件中定义checkout action 和save_order action:
  def checkout
    @cart = find_cart
    if @cart.items.empty?
 #判断篮中是否为空
      redirect_to_index("Your cart is empty")
    else
      @order = Order.new
    end
  end

  def save_order
    @cart = find_cart
    @order = Order.new(params[:order])
    @order.add_line_items_from_cart(@cart)
    if @order.save #保存数据
      session[:cart] = nil #清空购物篮
      redirect_to_index("Thank you for your order")
    else
      render :action => :checkout
    end
  end

相应 checkout 的view文件depot/app/views/store/checkout.rhtml内容为:
<div class="depot-form">
 <%= error_messages_for ‘order’ %>
 <fieldset>
  <legend>Please Enter Your Details</legend>
  <% form_for :order, :url => { :action => :save_order } do |form| %>
   <p>
    <label for="order_name">Name:</label>
    <%= form.text_field :name, :size => 40 %>
   </p>
   <p>
    <label for="order_address">Address:</label>
    <%= form.text_area :address, :rows => 3, :cols => 40 %>
   </p>
   <p>
    <label for="order_email">E-Mail:</label>
    <%= form.text_field :email, :size => 40 %>
   </p>
   <p>
    <label for="order_pay_type">Pay with:</label>
    <%=
     form.select :pay_type,
     Order::PAYMENT_TYPES,
     #这个下拉列表内容另定义
     :prompt => "Select a payment method"
    %>
   </p>
   <%= submit_tag "Place Order", :class => "submit" %>
  <% end %>
 </fieldset>
</div>
上面蓝色的代码用来组建form.

在depot/app/models/order.rb文件中定义上面下拉列表的PAYMENT_TYPES内容:
  PAYMENT_TYPES = [
  # 显示文字 数据库内容
  [ "Check", "check" ],
  ["Credit card", "cc" ],
  ["Purchase order", "po" ]
  ]
  #校验用户名,地址,信箱地址和支付方式是否为空
  validates_presence_of :name, :address, :email, :pay_type
  #校验支付方式是否合法(加强安全性)
  validates_inclusion_of :pay_type, :in => PAYMENT_TYPES.map {|disp, value| value}
 
  #定义上面用到的add_line_items_from_cart()
   def add_line_items_from_cart(cart)
    cart.items.each do |item|
      li = LineItem.from_cart_item(item)
      line_items << li
    end
  end

修改depot/app/models/line_item.rb文件内容为:
class LineItem < ActiveRecord::Base
  belongs_to :order
  belongs_to :product
  def self.from_cart_item(cart_item)
    li = self.new
    li.product = cart_item.product
    li.quantity = cart_item.quantity
    li.total_price = cart_item.price
    li
  end
end

在depot/app/views/store/add_to_cart.rjs文件中加入:
page.select(‘div#notice’).each { |div| div.hide }
搜索页面中id=notice的div,如有找到,将div隐藏.
在用户结账后继续购物时隐藏结账成功提示语.

本节结束

下一节:Ruby on Rails实战–创建一个网上商店E用户管理模块

发表在 Ruby on Rails | 留下评论

Ruby on Rails实战–创建一个网上商店C小试Ajax

上一节:Ruby on Rails实战–创建一个网上商店B前台

本节将向商店加入一个Ajax购物篮,使商店产品列表在不刷新的情况下更新显示购物篮信息

修改depot/app/controllers/store_controller.rb文件,如下:
  def index
    @products = Product.find_products_for_sale
    @cart = find_cart #加入对@cart的定义
  end
   def add_to_cart
    begin
      @product = Product.find(params[:id])
    rescue
      logger.error("Attempt to access invalid product #{params[:id]}")
      redirect_to_index("Invalid product")
    else

      @cart = find_cart
      @current_item = @cart.add_product(@product)
      redirect_to_index unless request.xhr?
   #request.xhr判断是否xmlHttpRequest, 否将指向index action, 用来兼容关闭了javascript功能的浏览器
   #开发时以无Ajax的为蓝本,再修改到ajax,以方便兼容性处理
    end
  end
  #….
  private
  def redirect_to_index(msg = nil)
#msg = nil 指定msg的默认值, 如果msg没有说明, msg = nil.
    flash[:notice] = msg if msg
 #加入对无参数的支持
    redirect_to :action => :index
  end
  #…

修改depot/app/models/cart.rb文件,如下:
def add_product(product)
 current_item = @items.find {|item| item.product == product}
 if current_item
  current_item.increment_quantity
 else
  current_item = CartItem.new(product)
  @items << current_item
 end
 current_item #新加,使返回修改商品
end

修改depot/app/controllers/store_controller.rb文件,如下:
def add_to_cart
 begin
  @product = Product.find(params[:id])
 rescue
  logger.error("Attempt to access invalid product #{params[:id]}")
  redirect_to_index("Invalid product")
 else
  @cart = find_cart
  @current_item = @cart.add_product(@product) #新加,定义@current_item
 end
end
#….
  def empty_cart
    session[:cart] = nil
    redirect_to_index #清空购物篮后返回商品列表
  end
#…

修改depot/app/models/cart.rb文件,增加一个购物篮内商品数据变量
def total_items
 @items.inject(0) {|sum, item| sum + item.quantity}
end

在depot/app/views/store/index.rhtml
中将:
 <%= button_to "Add to Cart", :action => :add_to_cart, :id => product %>
改为:
  <%= form_remote_tag :url => { :action => :add_to_cart, :id => product } %>
        <%= submit_tag "Add to Cart" %>
        <%= end_form_tag %>
 
在depot/app/views/layouts/store.rhtml文件里加入模板对javascript的支持,如下:
<html>
 <head>
  <title>Pragprog Books Online Store</title>
  <%= stylesheet_link_tag "depot", :media => "all" %>
  <%= javascript_include_tag :defaults %>
 </head>

在depot/app/views/store/新加一个partial template _cart.rhtml,内容如下:
<div class="cart-title">Your Cart</div>
<table>
 <%= render(:partial => "cart_item", :collection => cart.items) %>
 <!–调用另一个partial template _cart_item.rhtml–>
 <tr class="total-line">
  <td colspan="2">Total</td>
  <td class="total-cell"><%= format_price(cart.total_price) %></td>
 </tr>
</table>
<%= button_to "Empty cart", :action => :empty_cart %>
partial template以下找线开头,由render的调用.

在depot/app/views/store/新加一个partial template _cart_item.rhtml,内容如下:
<% if cart_item == @current_item %>
<!–后面用来显示特殊效果–>
 <tr id="current_item">
 <% else %>
 <tr>
<% end %>
  <td><%= cart_item.quantity %>&times;</td>
  <td><%= h(cart_item.title) %></td>
  <td class="item-price"><%= format_price(cart_item.price) %></td>
 </tr>

在depot/app/views/layouts/store.rhtml文件中增加调用partial template的代码:
   <div id="side">
                <%= hidden_div_if(@cart.items.empty?, :id => "cart") %>
    <!–hidden_div_if 这个 helpper method用来向div加入display:none属性, 代码见下文–>
                    <%= render(:partial =>"cart", :object => @cart) %>
                </div>
    …..

创建hidden_div_if  helpper method
  def hidden_div_if(condition, attributes = {})
    if condition
      attributes["style"] = "display: none"
    end
    attrs = tag_options(attributes.stringify_keys)
    "<div #{attrs}>"
  end

删除不用了的add_to_cart.rhtml

新建depot/app/views/store/add_to_cart.rjs文件,内容如下:
page[:cart].replace_html :partial => ‘cart’, :object => @cart
#将页面中id=cart的元素换成partial _cart.rhtml的内容
page[:cart].visual_effect :blind_down if @cart.total_items == 1
#当向购物篮添加第一个商品时,特效显示出购物篮
page[:current_item].visual_effect :highlight,
               :startcolor =>"#88ff88",
               :endcolor =>"#114411"
#当购物篮商品发生变化时,特效显示变化行

本节完成,为使ajax效果生效, 我重新启动了服务器

下一节:Ruby on Rails实战–创建一个网上商店D收银台

本文最后更新 20070108

发表在 Ruby on Rails | 留下评论