1

私が次のようなクラスを持っているとしましょう:

class Basket < ActiveRecord::Base
  has_many :fruits

ここで、「fruits」は、「apples」、「oranges」などのサブクラスを持つSTI基本クラスです。

次のようなバスケットでセッターメソッドを使用できるようにしたいと思います。

def fruits=(params)
  unless params.nil?
    params.each_pair do |fruit_type, fruit_data|
      fruit_type.build(fruit_data)
    end
  end
end

しかし、明らかに、次のような例外が発生します。

NoMethodError (undefined method `build' for "apples":String)

私が考えた回避策は次のようなものです。

def fruits=(params)
  unless params.nil?
    params.each_pair do |fruit_type, fruit_data|
      "#{fruit_type}".create(fruit_data.merge({:basket_id => self.id}))
    end
  end
end

ただし、これにより、Fruit STIオブジェクトがBasketクラスの前にインスタンス化されるため、basket_idキーがFruitサブクラスに保存されることはありません(basket_idがまだ存在しないため)。

私は完全に困惑しています。誰かアイデアはありますか?

4

2 に答える 2

1

バスケットにセッターメソッドを追加する代わりに、フルーツに追加します。

class Fruit < ActiveRecord::Base
  def type_setter=(type_name)
    self[:type]=type_name
  end
end

これで、関連付けを介してオブジェクトを作成するときに、タイプを渡すことができます。

b = Basket.new
b.fruits.build(:type_setter=>"Apple")

:type一括割り当てから保護されているため、この方法で割り当てることはできないことに注意してください。

編集

ああ、サブクラスに応じて異なるコールバックを実行したいですか?右。

あなたはこれを行うことができます:

fruit_type = "apples"
b = Basket.new
new_fruit = b.fruits << fruit_type.titleize.singularize.constantize.new
new_fruit.class # Apple

has_manyまたは、タイプごとに関連付けを定義します。

require_dependency 'fruit' # assuming Apple is defined in app/models/fruit.rb

class Basket
  has_many :apples
end

それから

fruit_type = "apples"
b = Basket.new
new_fruit = b.send(fruit_type).build
new_fruit.class # Apple
于 2010-11-04T19:18:32.083 に答える
-1

Rubyの用語では、は、文字列の値が文字列自体とまったく同じであるの"#{x}"と単純に同等です。x.to_sPHPなどの他の言語では、文字列の参照を解除してクラスとして扱うことができますが、ここではそうではありません。あなたがおそらく意味するのはこれです:

fruit_class = fruit_type.titleize.singularize.constantize
fruit_class.create(...)

このconstantizeメソッドは文字列から同等のクラスに変換しますが、大文字と小文字が区別されます。

fruit_type誰かがに設定して何かを作成し"users"、次に先に進んで管理者アカウントを作成する可能性に自分自身をさらしていることに注意してください。おそらくもっと責任があるのは、あなたが作っているものが実際に正しいクラスのものであるかどうかを追加でチェックすることです。

fruit_class = fruit_type.titleize.singularize.constantize
if (fruit_class.superclass == Fruit)
  fruit_class.create(...)
else
  render(:text => "What you're doing is fruitless.")
end

この方法でクラスをロードするときに注意すべきことの1つconstantizeは、アプリケーションでクラスをスペルアウトするようにクラスを自動ロードしないことです。開発モードでは、明示的に参照されていないサブクラスを作成できない場合があります。これを回避するには、潜在的なセキュリティ問題を解決するマッピングテーブルを使用し、一度にすべてをプリロードします。

fruit_class = Fruit::SUBCLASS_FOR[fruit_type]

この定数は次のように定義できます。

class Fruit < ActiveRecord::Base
  SUBCLASS_FOR = {
    'apples' => Apple,
    'bananas' => Banana,
    # ...
    'zuchini' => Zuchini
  }
end

モデルでリテラルクラス定数を使用すると、それらをすぐにロードする効果があります。

于 2010-11-04T19:05:10.337 に答える