属性可用before_type_cast但直接访问时为nil

时间:2012-03-23 09:56:01

标签: ruby-on-rails sqlite activerecord ruby-on-rails-3.2

我有一个ActiveRecord对象,它有一个before_create钩子,可以生成SHA哈希并将结果存储在对象的属性中:

before_create :store_api_id

attr_reader :api_id

def store_api_id
  self.api_id = generate_api_id
end

private
def generate_api_id                                                                                                                     
  Digest::SHA1.hexdigest([Time.now.nsec, rand].join).encode('UTF-8')                                                                    
end

这样可以创建api_id属性并将其作为文本存储在数据库中(通过调用.encode('UTF-8')强制执行,否则SQLite将尝试将结果存储为二进制数据。

但是以下规格失败了:

it "should be available as ad.api_id" do                                                                                                  
  @ad.save!                                                                                                                               
  @ad.api_id.should_not be_nil                                                                                                            
end                                                                                                                                       

it "should match the directly accessed attribute" do                                                                                      
  @ad.save!                                                                                                                               
  @ad.api_id.should == @ad.attributes['api_id']                                                                                           
end

我可以使用ad.api_id_before_type_castad.attributes['api_id']获取正确的哈希值,但在使用ad.api_id时则无法获取。

Ad.find_by_api_id('string api id')也按预期工作,但在返回的对象上调用.api_id时仍返回null。

我已经仔细检查了数据库中的类型,如下所示:

sqlite> select typeof(api_id) from ads;
text

这是一个关于新Rails 3.2.2 / ruby​​ 1.9.3-p125应用程序的示例rails console会话:

Loading development environment (Rails 3.2.2)
irb(main):001:0> ex = Example.new
=> #<Example id: nil, api_id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> ex.save!   (0.1ms)  begin transaction
SQL (7.3ms)  INSERT INTO "examples" ("api_id", "created_at", "updated_at") 
             VALUES (?, ?, ?)  [["api_id", "fc83bb94420cf8fb689b9b33195318778d771c4e"],
                               ["created_at", Fri, 23 Mar 2012 10:17:24 UTC +00:00], 
                               ["updated_at", Fri, 23 Mar 2012 10:17:24 UTC +00:00]]
(1.0ms)  commit transaction
=> true
irb(main):003:0> ex.api_id
=> nil
irb(main):004:0> ex.api_id_before_type_cast
=> "fc83bb94420cf8fb689b9b33195318778d771c4e"
irb(main):005:0> ex.attributes['api_id']
=> "fc83bb94420cf8fb689b9b33195318778d771c4e"
irb(main):006:0> 

1 个答案:

答案 0 :(得分:0)

正如我上面所写,使用attr_readonly代替attr_reader保护属性可以解决此问题,在这种情况下实际上更接近我想要的内容。

正如API docs注意:

  

列为readonly的属性将用于创建新记录,但更新操作将忽略这些字段。