0

簡単な調査・アンケートアプリを作ろうとしています。アンケートにはQuestions; ほとんどの質問は 1 つのコンテンツ フィールド (質問自体) で構成されており、調査回答者はそれに対して自由テキストの回答を記述します。(この議論に関係のないフィールドが他にもいくつかあります。)ただし、ユーザーはMultipleChoiceQuestionsorを作成することもできますLikertQuestions(たとえば、1 ~ 5 のスケールでの回答)。( の場合、 has_manyなどとMultipleChoiceQuestions呼ばれる別のモデルがあります)。私が知る限り、ここに私のデザインの選択があります:AnswerMultipleChoiceQuestionAnswers

1) 質問から継承:

class Question < ActiveRecord::Base
   attr_accessible :id, :content
end

class MultipleChoiceQuestion < Question
   attr_accessible :type
end

class LikertQuestion < Question
   attr_accessible :type, :min, :max, :label_min, label_max
end

2) 共有の属性とメソッドでモジュール/ミックスインを使用します。

module Question
  @content, @id
  def method1
  end
end

class MultipleChoiceQuestion < ActiveRecord::Base
  include Question
end

class LikertQuestion < ActiveRecord::Base
  include Question
  attr_accessible :type, :min, :max, :label_min, label_max
end

これは継承の明確なケースのように思えるので、オプション 1 を使用しました。それ以来、それを機能させることができません。単一テーブルの継承は単純に思えたので、スキーマにMultipleChoiceQuestionand LikertQuestioneachを指定しました。type:stringそれぞれのスキーマは次のとおりです (db/schema.rb から):

  create_table "questions", :force => true do |t|
    t.integer  "parent"
    t.string   "type"
    t.string   "content"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.integer  "survey_id"
  end

 create_table "multiple_choice_questions", :force => true do |t|
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.string   "type"
  end

  create_table "likert_questions", :force => true do |t|
    t.integer  "min"
    t.integer  "max"
    t.string   "label_min"
    t.string   "label_max"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.string   "type"
  end

上記のオプション 1 を実装した場合、MultipleChoiceQuestion と LikertQuestion には、schema.rb で指定されている一意のフィールドが実際には含まれません。代わりに、Question から継承されたフィールドのみがあります。コンソール出力を参照してください。

1.9.3p392 :001 > Question
 => Question(id: integer, parent: integer, content: string, created_at: datetime, updated_at: datetime, survey_id: integer)
1.9.3p392 :002 > LikertQuestion
 => LikertQuestion(id: integer, parent: integer, content: string, created_at: datetime, updated_at: datetime, survey_id: integer)
1.9.3p392 :003 > MultipleChoiceQuestion
 => MultipleChoiceQuestion(id: integer, parent: integer, content: string, created_at: datetime, updated_at: datetime, survey_id: integer)
1.9.3p392 :004 > LikertQuestion.new(:min => 3)
ActiveRecord::UnknownAttributeError: unknown attribute: min

StackOverflow の誰かが、Question は抽象クラスであるべきだと言いました。しかし、 self.abstract_class = trueQuestion.rb に追加すると、次のようになります。

1.9.3p392 :001 > Question
 => Question(abstract)
1.9.3p392 :002 > LikertQuestion
 => LikertQuestion(id: integer, min: integer, max: integer, label_min: string, label_mid: string, label_max: string, created_at: datetime, updated_at: datetime, type: string)
1.9.3p392 :003 > MultipleChoiceQuestion
 => MultipleChoiceQuestion(id: integer, created_at: datetime, updated_at: datetime, type: string)
1.9.3p392 :004 > LikertQuestion.new(:content => "foo")
ActiveRecord::UnknownAttributeError: unknown attribute: content

LikertQuestion固有のフィールドのみMultipleChoiceQuestion表示し、親からフィールドを継承しません。

1) ここで何が欠けていますか? 継承が最適な解決策であるかどうかに関係なく、明らかなことを見落としているに違いありません。

2) 継承の代わりにモジュール アプローチを使用する必要がありますか? 先に述べたように、継承は簡単なことのようLikertQuestionに思えました。モジュール アプローチを使用すると、 、、およびおそらくその他の便利なことを言うことができなくなります。この状況でRailsホットショットは何をしますか? 私はそれが何であれします。MultipleChoiceQuestionQuestionssurvey.questions()survey.questions.build()

サブクラス化とミックスインの長所と短所について非常に包括的な議論を提供する StackOverflow の投稿はありません。

Ruby 1.9.3 (2.0 に切り替えることも考えています)、Rails 3.2.3 を使用しています。

4

1 に答える 1

3

あなたは確かに明らかな何かを見逃しています。STIが何の略か知っていますか?単一テーブルの継承。いくつかのテーブルを作成してから、STI を使用しようとしています。

テーブルが同一または非常に類似している場合 (おそらく 1 つのフィールドが異なる場合) にのみ、STI を使用する必要があります。これは主に、サブクラス化し、動作を区別するメソッドを提供する場合に使用されます。たとえば、すべてのユーザーが同じ属性を共有していても、一部のユーザーは管理者である場合があります。users テーブルにtypeフィールドを作成すると、次のようになります。

class Admin < User
  def admin?
    true
  end
end

class NormalUser < User
  def admin?
    false
  end
end

(これは明らかに非常に単純な例であり、おそらくそれ自体で STI を保証するものではありません)。

抽象クラスに関する限り、すべてスーパー クラスから動作を継承する複数のテーブルがある場合、これは適切な決定です。あなたの場合は理にかなっているようです。ただし、抽象クラスにはテーブルがないことに注意することが重要です。true として宣言することの要点は、abstract_class存在しないテーブルを検索しようとしたときに ActiveRecord が混乱しないようにすることです。これがないと、ActiveRecord は STI を使用していると見なし、質問テーブルを探します。あなたの場合、質問テーブルがあるので、それを抽象クラスとして宣言しても意味がありません。

もう1つ、「継承ではなくモジュールアプローチを使用する必要がありますか?」と尋ねます。モジュールの使用は、実際には Ruby における継承の一種です。モジュールをインクルードすると、スーパークラスと同じようにクラスの祖先チェーンに挿入されます (ただし、モジュールはスーパークラスの前に挿入されます)。何らかの形の継承が正しいアプローチだと思います。この場合、どちらも質問のタイプであるため、抽象的な Question スーパークラスを作成することは理にかなっています。質問は属性の多くを共有していないため、私の考えでは、質問を別々のテーブルに格納するのが最善の解決策です。STI は、いくつかの異なるフィールドがある場合、実際には良い方法ではありませんnull

そして、モジュールについて明確にするために、他の点では関係のないいくつかのモデルが何らかの形の共通の動作を共有する場合に最適だと思います。私が何度も使用した 1 つの例は、Commentableモジュールのアイデアです (ActiveSupport::Concern を使用)。複数のモデルにコメントを付けることができるからといって、モデルが関連していないため、必ずしもスーパークラスが保証されるとは限りません。実際には、ある種の親オブジェクトから派生するわけではありません。これが、モジュールが意味を持つところです。あなたの場合、両方のモデルが質問のタイプであるため、スーパークラスは理にかなっています。そのため、両方が一般的なQuestion基本クラスから派生することが適切と思われます。

于 2013-05-11T20:22:49.243 に答える