Hits:10 ... « 1 2

Rails acts_as_paranoid を覗く

前回のBLOG 続き,

では,init.rbを覗いてみる.(素人なのでー間違っている箇所が多いと思います.ツッコミいただければー幸いです.)

# vendor/plugins/acts_as_paranoid/init.rb

class << ActiveRecord::Base
  def belongs_to_with_deleted(association_id, options = {})
    with_deleted = options.delete :with_deleted
    returning belongs_to_without_deleted(association_id, options) do
      if with_deleted
        reflection = reflect_on_association(association_id)
        association_accessor_methods(reflection,            Caboose::Acts::BelongsToWithDeletedAssociation)
        association_constructor_method(:build,  reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
        association_constructor_method(:create, reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
      end
    end
  end

  alias_method_chain :belongs_to, :deleted
end
ActiveRecord::Base.send :include, Caboose::Acts::Paranoid

1行目からわからない! " class A < B " なら AクラスはBクラスを継承するということだが…. これなんなん!?

これは,クラスメソッドの追加でした.つまり,ActiveRecord::Base クラスに belongs_to_with_deleted メソッドを追加している.下記にちょこっと試してみた!

$ irb
> class Base
>    def hoge
>        'hoge'
>    end
> end
> class << Base
>    def hige
>         'hige'
>    end
> end
>
> Base.hige    # 追加されたクラスメソッドの実行
=> "hige"
> Base.hoge   # インスタンスメソッドだから実行できない
NoMethodError: undefined method `hoge' for Base:Class
> obj = Base.new
> obj.hige       # クラスメソッドだから実行できない
NoMethodError: undefined method `hige' for #<Base:0xb7e8738c>
> obj.hoge      # インスタンスメソッドの実行
=> "hoge"

とういうことで納得.次に「alias_method_chain :belongs_to, :deleted」これ何してんだろ…. これrubyじゃなくてーrailsのメソッドだった…. このファイルを覗くと答えが見れる.

ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/core_ext/module/aliasing.rb`

alias という言葉通りな感じですね. 試しに使ってみる.

$ ./script/console
>> class Gly
>>     def name
>>         'glycine'
>>     end
>> end
>> class G < Gly
>>     def name_with_short
>>         name_without_short + '( GLY )'
>>     end
>>     alias_method_chain :name, :short
>> end
>> Gly.new.name
=> "glycine"
>> G.new.name
=> "glycine( GLY )"

name_without_short は,拡張前のメソッドを呼び出せる. name_with_short は,拡張したメソッドを呼び出せる. この説明だとわかりにくいですよね.

この段階になっても,Model に acts_as_paranoid と書いただけでープラグインがうまく動くことは理解できない. 最後の行の「ActiveRecord::Base.send :include, Caboose::Acts::Paranoid」これですね. ここは,(クラスメソッドが追加された)ActiveRecord::Base に Caboose::Acts::Paranoid モジュールを include している. Paranoidモジュールは,vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb にあります. しかし,このモジュールが読み込まれてもー (ClassMethodsモジュール内の)acts_as_paranoid メソッドは,Model から呼び出せない. と思ったらー(Paranoidモジュールの)self.included(base) ってーのが ClassMethodsモジュールをextend してるわ!ということで Model に acts_as_paranoid と書くとー Modelを 読み込んだとき に acts_as_paranoidメソッドが実行される.

メモがてらにモジュールのincludeを試してみる.

$ irb
> module Paranoid
>     def self.included(base)
>         p self    #=> Pranoid
>         p base  #=> Base
>         base.extend ClassMethods
>     end
>
>     module ClassMethods
>        def acts_as_paranoid
>             'ACTS_AS_PARANOID'
>        end
>     end
> end
>
> class Base
>     include Paranoid
> end
>
> Base.acts_as_paranoid
=> "ACT_AS_PARANOID"

これで何とかーModelにacts_as_paranoid と書くことだけでーModelにプラグインが反映されることが理解. いやー疲れた.やっぱ言語勉強しないとね.今回もー toshi78 さんにも協力してもらっちゃいました.

結局,このプラグインを全部解読してませんが,色々と疑問. なんで,こんなにモジュールがネストされているんだろ? また,ActiveRecord::Baseにクラスメソッドを最初に追加したんだろ? だってーこの init.rb は Rails がロードされたときに読み込まれる.だもんでー 論理削除を必要としないModelにまでー不必要なクラスメソッドが追加されちゃいません?パフォーマンス悪くなっちゃうんじゃない? 素人の俺には「ActiveRecord::Base.send :include, Caboose::Acts::Paranoid」の中でーうまく「belongs_to_with_deleted」クラスメソッドを追加する処理をすればいいのではないのかなぁ〜.

と,素人の考えが色々と浮かぶ….

[RubyonRails] [Ruby]

2008/02/19 01:16 | Comments(0)

Railsインストール

個人メモ

Railsの勉強をしなきゃいけなくなりそうなのでRailsインストールしてみた. とりあえず開発環境だけできればいいから必要最低限度のモノをインストール.

環境はFC5です.

rubyのインストール

$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6-p111.tar.bz2
$ bunzip2 ruby-1.8.6-p111.tar.bz2
$ tar xvf ruby-1.8.6-p111.tar
$ cd ruby-1.8.6-p111
$ ./configure --prefix=/usr/local/ruby
$ make
$ sudo make install

gemsのインストール

$ wget http://rubyforge.org/frs/download.php/29548/rubygems-1.0.1.tgz
$ tar xzvf rubygems-1.0.1.tgz
$ cd rubygems-1.0.1
$ sudo /usr/local/ruby/bin/ruby setup.rb

railsのインストール

$ sudo /usr/local/ruby/bin/gem install -v=1.2.6 rails

サンプルが画面作成することができた. MySQLとも連携できた.とりあえずOK.

[RubyonRails]

2008/02/13 17:05 | Comments(0)

Railsプラグイン acts_as_paranoid

Railsのプラグインとやらは結構色々あるみたい.ということで,Railsプラグインを使ってみた. 論理削除をするプラグインってーのがあるので試しに…. (論理削除するプラグインなんていらないでしょ!? と思う方も多いのではないかな!? 実際自分は思ったがープラグインを試すには簡単そうだったのでね)

単純な登録/閲覧/更新/物理削除 できるサンプルアプリに論理削除プラグインを追加してみた.

# db/migrate/001_create_entries.rb

class CreateEntries < ActiveRecord::Migration
  def self.up
    create_table :entries do |t|
      t.column      :name,      :string,        :null => false
      t.column      :created_at,:datetime,      :null => false
      t.column      :updated_at,:datetime,      :null => false
    end
  end

  def self.down
    drop_table :entries
  end
end

プラグインのインストール.

# リポジトリの追加
$ ./script/plugin source http://techno-weenie.net/svn/projects/plugins
# 論理削除プラグイン(acts_as_paranoid)のインストール
$ ./script/plugin install acts_as_paranoid

インストールって,svn::export しているだけのようです.exportは,vendor/plugins/acts_as_paranoid/ にされてます. 本体の「lib/commands/plugin.rb」の中を覗くとー実行コマンドが覗ける.

カラム(deleted_at)の追加

# db/migrate/002_add_delete.rb

class AddDelete < ActiveRecord::Migration
  def self.up
    add_column      :entries, :deleted_at, :datetime
  end

  def self.down
    remove_column   :entries, :deleted_at
  end
end

Entryモデルに「acts_as_paranoid」を記述!

# app/models/entry.rb

class Entry < ActiveRecord::Base
acts_as_paranoid
end

これで終了! だけど下記のようなエラーが発生.

undefined method `construct_count_options_from_args' for Entry:Class

Trace覗くと,

vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb:91:in `count_with_deleted'

となっていたので,Google検索で調べたらー

90 def count_with_deleted(*args)
91     #calculate_with_deleted(:count, *construct_count_options_from_args(*args))       # 変更前
92     calculate_with_deleted(:count, *construct_count_options_from_legacy_args(*args)) # 変更後
93 end

とやったら修正できた. ほんで,試しにエントリーを削除してみたらー,論理削除された!

DBを覗いてみたら

mysql> SELECT * FROM entries;
+----+-----------+---------------------+---------------------+---------------------+
| id | name      | created_at          | updated_at          | deleted_at          |
+----+-----------+---------------------+---------------------+---------------------+
|  1 | ENDLESS   | 2008-02-18 12:02:00 | 2008-02-18 12:02:36 | NULL                |
|  2 | TEST NAME | 2008-02-18 12:02:00 | 2008-02-18 12:02:44 | 2008-02-18 12:16:09 |
+----+-----------+---------------------+---------------------+---------------------+

id=2 を削除したら, deleted_at に削除した日時が記録された. あれ!? ケド updated_at は更新されてないみたい.まぁ~削除日時が登録されているからいいのか….

と,プラグインを追加して論理削除が成功したのはいいのですがーこの「acts_as_paranoid」1行が何をしたのかが問題ですよね. Rubyをまともに読めない俺だからいけないのでしょう...

ということで,プラグインを覗いてみる! 怪しいファイル見っけ! vendor/plugins/acts_as_paranoid/init.rb これが勝手に読み込まれているようだ!

あっ...早速言語の壁!Ruby(Rails?)が読めない! alias_method_chain,association_accessor_methods この辺りから調べねば…. ということで,今日はここまでです.

[RubyonRails]

2008/02/18 20:24 | Comments(0)

単一テーブル継承

Rails「単一テーブルの継承」ってーのを利用して遊んでみたら,わけがわからなくなった. 単一テーブルの継承とは,同じようなテーブル(カラム)構成の時に複数のテーブルを作成するのを避けたりするときに,継承しちゃえー的な!

下記の例は,商品名と価格の情報を持つ食べ物(Food)と飲み物(Drink)を商品(Item)という1つのテーブルに任せましょ! だもんで,このitemsテーブルにはtypeカラムが必須となる.

・ テーブル作成.

$ vi db/migrate/001_create_items.rb
class CreateItems < ActiveRecord::Migration
   def self.up
     create_table :items do |t|
        t.column      :name,      :string,        :null => false
        t.column      :price,     :integer,       :null => false
        t.column      :created_at,:datetime,      :null => false
        t.column      :updated_at,:datetime,      :null => false
        t.column      :type,      :string,        :null => false    #必須
     end
   end

   def self.down
      drop_table :items
   end
 end

・ Model Itemモデルを継承してあげるだけ.

class Item < ActiveRecord::Base
end

class Food < Item
end

class Drink < Item
end

では,実験.

$ ./script/console
>> Item.find(:all)
=> []
>> Food.find(:all)
=> []
>> Drink.find(:all)
=> []
>> Food.create(
?>     :name => 'apple',
?>     :price => 150
?> )
>> Item.find(:all)
=> [#<Food:0x229b088 @attributes={"name"=>"apple", "updated_at"=>"2008-02-17 00:00:00", "price"=>"150", "type"=>"Food", "is_active"=>"1", "id"=>"1", "created_at"=>"2008-02-17 00:00:00"}>]
>> Item.find(:all)
=> [#<Food:0x2297d5c @attributes={"name"=>"apple", "updated_at"=>"2008-02-17 00:00:00", "price"=>"150", "type"=>"Food", "is_active"=>"1", "id"=>"1", "created_at"=>"2008-02-17 00:00:00"}>]
>> Food.find(:all)
=> [#<Food:0x2294864 @attributes={"name"=>"apple", "updated_at"=>"2008-02-17 00:00:00", "price"=>"150", "type"=>"Food", "is_active"=>"1", "id"=>"1", "created_at"=>"2008-02-17 00:00:00"}>]
>> Drink.find(:all)
=> []

DBでは,

mysql> SELECT * FROM items;
+----+-------+-------+---------------------+---------------------+-----------+------+
| id | name  | price | created_at          | updated_at          | is_active | type |
+----+-------+-------+---------------------+---------------------+-----------+------+
|  1 | apple |   150 | 2008-02-17 14:43:39 | 2008-02-17 14:43:39 |         1 | Food |
+----+-------+-------+---------------------+---------------------+-----------+------+

ここまではよかったんだが,実際にフォームを作ってWebアプリとして作成してみようとしたら,

# @tags = Tag.find(:all).collect {|t| [t.name, t.model]}
# => [['食べ物', 'Food'], ['飲み物', ''Drink]]

<% form_tag :action => "create" do %>
    <%= text_field :item, :name %>
    <%= text_field :item, :price %>
    <%= select :item, :type, @tags %>
    <%= submit_tag "Post" %>
<% end %>

てーな感じでフォームを作ろうとしたら,wrong argument type String (expected Module) てなエラーでて困った. ここ 見るとーtypeは駄目なんだと….でーも単一テーブルの継承するにはtype必要なんでしょ!?と思って名前を変えたりして無理矢理対応していたんだが….

よく考えるとー別にtypeでモデルを切り替えてーデータをつくるんだからー俺がtypeを意識する必要ないじゃん.つまり,意図的にtypeを params[:item] に含める必要がなかった….

まぁ結局俺がアホなだけですね.わけのわからんサンプルを作ってしまった.

[RubyonRails]

2008/02/26 22:09 | Comments(0)

ダイナミックにインスタンス化

Railsのメモ.

文字列(クラス名)からインスタンス化させようとしたとき,Rails上ならこれでいける.

cls = "ClassName".constantize
obj = cls.new

# とか,

module_name = "ModuleName"
class_name = "ClassName"
cls = "#{module_name}::#{class_name}".constantize
obj = cls.new

といける.

constantizeを覗いてみると,Object.module_eval() をしていた. 結局evalですよね.evalしないでうまくやる方法を探していたんですがね. もちろん,ハッシュでクラスを定義しちゃうっていうのが楽なんですがーちょっと数が多くてね.

[RubyonRails]

2008/04/23 23:39 | Comments(0)

Hits:10 ... « 1 2