6

だから、私はこれが昨夜うまくいったと思っていた、それを誓うことができた. 今はうまくいかないので、そろそろ助けを求める時が来たと思います。

私はデータベースで動的フィールドを半 EAV スタイルで定義していますが、EAV が良いアイデアかどうかについてあなたの意見を聞く気にはなりません:)

基本的に、属性 (またはフィールド) が追加されると、特定の属性テーブルの移行に列を追加して実行します (または削除します)。すべての属性が定義されている直接的な関係であるカテゴリレイヤーが真ん中にあるため、属性はカテゴリ固有であるため、実際の属性名を列名として使用することはできません。

それで、それがあなたが視覚化するのに役立つなら

    Entity
    belongs_to :category

    Category
    has_many :entities

    EntityAttribute
    belongs_to :category

    EntityAttributeValue
    belongs_to :entity_attribute
    belongs_to :entity        

また、新しい属性が作成されると、EAV テーブルは水平方向に広がり、attribute_1 attribute_2 というラベルの付いた列があり、その特定のエンティティの値が含まれます。

とにかく - 私はエンティティモデルでメソッドを動的にしようとしているので、@entity.entity_attribute_value.field_5ではなく@entity.actual_attribute_nameを呼び出すことができます

ここに私が働いていると思ったコードがあります -

    def method_missing(method, *args)

      return if self.project_category.blank?

      puts "Sorry, I don't have #{method}, let me try to find a dynamic one."
      puts "let me try to find a dynamic one"

      keys = self.project_category.dynamic_fields.collect {|o| o.name.to_sym }

      if keys.include?(method)
        field = self.project_category.dynamic_fields.select { |field| field.name.to_sym == method.to_sym && field.project_category.id == self.project_category.id }.first
        fields = self.project_category.dynamic_field_values.select {|field| field.name.to_sym == method }
        self.project_category_field_value.send("field_#{field.id}".to_sym, *args)
      end

    end

その後、今日コードに戻ると、Rails コンソールで属性を設定できたにもかかわらず、正しいフィールドが返されることに気付きました。レコードを保存したときに、EntityAttributeValue が更新されていませんでした (上で self.project_category_field_value として表されています)。 )

さらに詳しく調べたところ、属性を手動で保存するために before_update または before_save コールバックを追加する必要があるように見えました。コールバックで、オブジェクトが複製されているかのように、method_missing コールバックが再実行されることに気づきました。 (そして新しいオブジェクトは元のオブジェクトのコピーでした)、または何か、よくわかりません。しかし、保存プロセス中またはその前のある時点で、私の属性は忘却の中に消えます。

だから、入力した後、自分の質問に途中で答えたと思います。インスタンス変数を設定し、method_missing メソッドの最初に存在するかどうかを確認する必要があります (右?) 多分それは何が起こっているのかわかりません、しかし、私がやろうとしていることを行うためのより良い方法があるかどうかも尋ねています.

また、method_missing の使用が悪い考えである場合は、その理由を説明してください。メソッドの欠落に関する投稿を調べていると、何人かの人々がそれを非難していると聞きましたが、メソッドの欠落が悪い解決策である理由について合理的な説明を提供する人は誰もいませんでした.

前もって感謝します。

4

3 に答える 3

3

ActiveRecordで関連モデルEAVにメソッドを委任する方法を説明した私のプレゼンテーションを見ることができます

たとえば、製品モデルにSTIを使用し、それらに属性モデルを関連付けました。

まず、抽象属性モデルを作成します

class Attribute < ActiveRecord::Base
  self.abstract_class = true
  attr_accessible :name, :value
  belongs_to :entity, polymorphic: true, touch: true, autosave: true
end

次に、すべての属性モデルがこのクラスから継承されます。

class IntegerAttribute < Attribute
end

class StringAttribute < Attribute
end

次に、基本製品クラスを説明する必要があります

class Product < ActiveRecord::Base
  %w(string integer float boolean).each do |type|
    has_many :"#{type}_attributes", as: :entity, autosave: true, dependent: :delete_all
  end

  def eav_attr_model(name, type)
    attributes = send("#{type}_attributes")
    attributes.detect { |attr| attr.name == name } || attributes.build(name: name)
  end

  def self.eav(name, type)
    attr_accessor name

    attribute_method_matchers.each do |matcher|
      class_eval <<-EOS, __FILE__, __LINE__ + 1
        def #{matcher.method_name(name)}(*args)
          eav_attr_model('#{name}', '#{type}').send :#{matcher.method_name('value')}, *args
        end
      EOS
    end
  end
end

そこで、関連するモデルにプロキシ#eav_attr_modelメソッドであるメソッドと属性メソッドを生成するメソッドを追加しました。.eav

それがすべてです。これで、 Productクラスから継承された製品モデルを作成できます。

class SimpleProduct < Product
  attr_accessible :name

  eav :code, :string
  eav :price, :float
  eav :quantity, :integer
  eav :active, :boolean
end

使用法:

SimpleProduct.create(code: '#1', price: 2.75, quantity: 5, active: true)
product = SimpleProduct.find(1)
product.code     # "#1"
product.price    # 2.75
product.quantity # 5
product.active?  # true

product.price_changed? # false
product.price = 3.50
product.code_changed?  # true
product.code_was       # 2.75

実行時に属性を作成したり、クエリメソッドを使用してデータを取得したりできる、より複雑なソリューションが必要な場合は、active_recordモデル のEAVを実装する私のgemhydra_attributeを確認できます。

于 2012-08-01T09:03:39.810 に答える
2

method_missingこれは、部門で進行中の非常に集中的なプログラミングです。あなたが持っているべきものは、次のようなものです:

def method_missing(name, *args)
  if (method_name = dynamic_attribute_method_for(name))
    method_name.send(*args)
  else
    super
  end
end

次に、これを 2 つの部分に分解してみることができます。1 つ目は、指定された名前 here の呼び出しを処理できるかどうかを決定するメソッドを作成することですdynamic_attribute_method_for。2 つ目は、問題の実際のメソッドです。前者の仕事は、後者が呼び出されるまでに確実に機能するようにすることです。おそらくdefine_method、次に同じメソッド名にアクセスするときに、これらすべてを再度実行する必要がないようにするために使用します。

そのメソッドは次のようになります。

def dynamic_attribute_method_for(name)
  dynamic_attributes = ...

  type = :reader

  attribute_name = name.to_s.sub(/=$/) do 
    type = :writer
    ''
  end

  unless (dynamic_attributes.include?(attribute_name))
    return
  end

  case (type)
  when :writer
    define_method(name) do |value|
      # Whatever you need
    end
  else
    define_method(name) do
      # Whatever you need
    end
  end

  name
end

構造が明確ではなく、アプリケーションのコンテキストに大きく依存しているように見えるため、メソッドで何が起こっているのかわかりません。

設計の観点からは、このすべての機能をカプセル化する専用のラッパー クラスを作成する方が簡単であることがわかる場合があります。を呼び出す代わりに、この場合は必要に応じて作成される場所を呼び出しobject.attribute_nameます。object.dynamic_attributes.attribute_namedynamic_attributes

def dynamic_attributes
  @dynamic_attributes ||= DynamicAccessor.new(self)
end

そのオブジェクトが初期化されると、必要なメソッドで事前に構成され、このメソッドの欠落に対処する必要がなくなります。

于 2012-04-05T02:57:16.267 に答える
0

同じ種類のことをしようとしているが問題を抱えている他の人にとって、私が理解できる限りの問題/解決策は次のとおりです。

1)次のコードが機能すると思いましたが:

self.project_category_field_value.send("field_#{field.id}".to_sym, *args)

これにより、関連するモデルの新しいインスタンスが毎回返されるため、失われていました。

2) 関連モデルは保存されないため、関連オブジェクトを手動で保存する必要があります。モデルにフラグを設定し、フラグが存在する場合は関連モデルを保存するためのコールバックを追加しました。

case(type)

when :writer
    self.update_dynamic_attributes=(true)
    etc......

そしてコールバック、

  before_update :update_dynamic_attributes, :if => :update_dynamic_attributes?

  def update_dynamic_attributes?
    instance_variable_get("@update_dynamic_attributes")
  end

  def update_dynamic_attributes=(val)
    instance_variable_set("@update_dynamic_attributes",val)
  end

  def update_dynamic_attributes
    self.project_category_field_value.save
  end

3) 1 に戻ると、最大の問題は、新しいオブジェクト インスタンスが毎回返されることでした。この質問をする前に define_method メソッドを使用してみましたが、うまくいきませんでした。-- この解決策は私をかなりばかげているように感じさせましたが、他の人も同じように遭遇すると確信しているので、アクティブなレコード クラス内で define_method を直接使用している場合は、

self.class.send(:define_method, name)

それ以外の

self.send(:define_method, name)

またはあなたは:(それがうまくいかないとき

于 2012-04-07T00:02:10.747 に答える