20469


Ruby on Railsで開発をしています。IDEはRubyMineを使ってます。

10個以上のモデルの中、3つのモデルだけモデル名を変更する必要があり、  
テーブル名と違って、テーブルとの連携のためモデルには

[code]

    def self.table_name_prefix
      'm_'
    end
    
[/code]

このようなメソッドを持っています。（３つのモデルに対応するテーブルは全部モデル名の前に「m_」をつけているようになっています。）  
同じメソッドを持っているからこれを共通的にまとめて簡単にしたいんです。  
ですが、まだRails初心者なので、どうすればいいかよくわかりません。  
どうすればきれいに取りまとめることができるのか教えていただきたいんです！


CC BY-SA 3.0
2015-12-29T06:51:33.100
20449
2015-12-30T06:52:30.163
10710
question
ruby-on-rails ruby rubymine
Railsのモデルで共通部分をまとめる方法を教えてください。


実装の共有はモジュールを使うのが選択肢の一つかと思います。例えば`models/concerns`にモジュールを作り、それをモデルクラスで`extend`するのはどうでしょうか？(特異メソッドの定義になるので、`include`ではなく`extend`になる)

[code]

    -- app/models/concerns/hoge.rb --
    module TblnamePrefix
      def table_name_prefix
        'm_'
      end
    end
    
    -- app/models/hoo.rb --
    require 'hoge'
    class Hoo < ActiveRecord::Base
      extend TblnamePrefix
    end
    
    (irbでの実行例)
    irb(main):003:0> Hoo.table_name_prefix
    => "m_"
    
[/code]


CC BY-SA 3.0
2015-12-29T07:12:48.950
20450
2015-12-30T03:48:06.373
2015-12-30T03:48:06.373
9403
9403
20449
answer


ActiveSupport::Concernを使ってみました。  
こんな感じでどうでしょうか？  
User, Item, Orderの3つのクラスがあり、UserとItemはテーブル名に"m_"というprefixが付く設計です。

[code]

    # db/schema.rb
    ActiveRecord::Schema.define(version: 20151230050425) do
    
      create_table "m_items", force: :cascade do |t|
        t.string   "name"
        t.datetime "created_at", null: false
        t.datetime "updated_at", null: false
      end
    
      create_table "m_users", force: :cascade do |t|
        t.string   "name"
        t.datetime "created_at", null: false
        t.datetime "updated_at", null: false
      end
    
      create_table "orders", force: :cascade do |t|
        t.integer  "user_id"
        t.integer  "item_id"
        t.datetime "created_at", null: false
        t.datetime "updated_at", null: false
      end
    
      add_index "orders", ["item_id"], name: "index_orders_on_item_id"
      add_index "orders", ["user_id"], name: "index_orders_on_user_id"
    
    end
    
    # app/models/concerns/master_table_prefix.rb
    module MasterTablePrefix
      extend ActiveSupport::Concern
    
      module ClassMethods
        def table_name_prefix
          'm_'
        end
      end
    end
    
    # app/models/user.rb
    class User < ActiveRecord::Base
      include MasterTablePrefix
    
      has_many :orders
    end
    
    # app/models/item.rb
    class Item < ActiveRecord::Base
      include MasterTablePrefix
    
      has_many :orders
    end
    
    # app/models/order.rb
    class Order < ActiveRecord::Base
      belongs_to :user
      belongs_to :item
    end
    
[/code]

以下は動作確認用のテストコードです。

[code]

    # test/models/order_test.rb
    require 'test_helper'
    
    class OrderTest < ActiveSupport::TestCase
      test "Create order" do
        user = User.create! name: 'Alice'
        item = Item.create! name: 'MacBook'
        order = Order.create! user: user, item: item
    
        assert_equal order, Order.first
      end
    end
    
[/code]

GitHubにもコードをアップしているので参考にしてみてください。

[JunichiIto/module-prefix-sandbox](https://github.com/JunichiIto/module-
prefix-sandbox)


CC BY-SA 3.0
2015-12-30T05:16:59.020
20469
2015-12-30T05:16:59.020
85
20449
answer


直接の回答ではないのですが、これぐらいだと必要なモデルに直接書いておいた方がいいのではないでしょうか。

たとえば、複数のモデルに

[code]

    validate_presence_of :name
    
[/code]

が共通して存在するからと言って、それを共通化することは普通はやりません。`table_name_prefix`も設定の一部であると考えると、共通化して隠蔽するには適さない内容だと思います。

すべてのモデルに共通しているなら共通化しても良いかもしれません。また、その3つのモデルに他と明らかに違う特徴があり、それをモジュールなり継承なりで表現できるのであれば、それは共通化の理由になります。

* * *

とは言っても、`def table_name_prefix; 'm_'; end`というのはみっともないので、gem作りました。

Gemfileに

[code]

    gem 'table_prefix'
    
[/code]

を追加して`bundle install`してください。

モデルでは

[code]

    class Post < ActiveRecord::Base
      def table_name_prefix
        'm_'
      end
    end
    
[/code]

の替わりに

[code]

    class Post < ActiveRecord::Base
      table_prefix 'm_'
    end
    
[/code]

と書けるようになります。

`table_prefix`は`table_name_prefix`メソッドを定義するメソッドになっています

[code]

    module TablePrefix
      def table_prefix(prefix)
        class_eval { class << self; self; end }.class_eval do
          define_method('table_name_prefix') do
            prefix.to_s
          end
        end
      end
    end
    ActiveRecord::Base.extend TablePrefix
    
[/code]


CC BY-SA 3.0
2015-12-30T06:52:30.163
20471
2015-12-30T06:52:30.163
5793
20449
answer