私は事実上、Ruby と Rails フレームワークの両方の初心者です。そのため、フレームワークの慣習を破るようなことをする前に、助けを求めることにしました。
私はかなり堅実なOO
プログラミングのバックグラウンドを持っており、初級から中級レベルの SQL クエリにかなり慣れています。ActiveRecord
しかし、 Rails が提供するクラスについて頭を悩ませていました。私の当面の本能は、ActiveRecord クラスを完全に廃棄し、独自の SQL クエリを手作業で書き、それらをモデルにラップすることです。ただし、ActiveRecords が Rails フレームワークのかなり重要な部分であることはわかっており、それらを避けることは、将来的に苦痛になるだけです。
以下は私のMySQL
スキーマです(後で書きますRails Migration
)。この質問はできるだけ簡潔にしようと思いますが、スキーマをモデル化した理由を説明するために、少し背景を説明する必要があるかもしれません。私はそれに過度に執着していないので、人々が構造についてより良いアイデアを持っているなら、それは素晴らしいことです.
-- Users table is a minimalized version of what it probably will be, but contains all pertinent information
CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) UNIQUE NOT NULL
) Engine=InnoDB;
CREATE TABLE IF NOT EXISTS hashtags (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tag VARCHAR(30) UNIQUE NOT NULL
) Engine=InnoDB;
CREATE TABLE IF NOT EXISTS content_mentions (
content_id INT UNSIGNED NOT NULL,
user_id INT UNSIGNED NOT NULL,
INDEX(content_id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
) Engine=InnoDB;
CREATE TABLE IF NOT EXISTS content_hashtags (
content_id INT UNSIGNED NOT NULL,
hashtag_id INT UNSIGNED NOT NULL,
INDEX(content_id),
FOREIGN KEY(hashtag_id) REFERENCES hashtags(id) ON DELETE CASCADE
) Engine=InnoDB;
CREATE TABLE IF NOT EXISTS content_comments (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
content_id INT UNSIGNED NOT NULL,
text_body VARCHAR(1000) NOT NULL,
date_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX(content_id)
) Engine=InnoDB;
CREATE TABLE IF NOT EXISTS polls (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
question VARCHAR(100) NOT NULL,
text_body VARCHAR(1000) NOT NULL,
date_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
) Engine=InnoDB;
CREATE TABLE IF NOT EXISTS poll_options (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
poll_id INT UNSIGNED NOT NULL,
content VARCHAR(150) NOT NULL,
active VARCHAR(1) NOT NULL DEFAULT 'Y',
FOREIGN KEY(poll_id) REFERENCES polls(id) ON DELETE CASCADE
) Engine=InnoDB;
CREATE TABLE IF NOT EXISTS poll_answers (
poll_option_id INT UNSIGNED NOT NULL,
user_id INT UNSIGNED NOT NULL,
FOREIGN KEY(poll_option_id) REFERENCES poll_options(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY(poll_option_id,user_id)
) Engine=InnoDB;
スキーマが示すように、これは本当に基本的な Web 投票アプリケーションです。各投票には複数のオプションがあり、各オプションには異なるユーザーによる複数の回答が含まれる場合があります。さて、おそらくそれを見ている奇妙な部分はcontent_*
テーブルです。これを説明する最善の方法は、おそらくabstract
表として説明することです。通常、関係は 2 つ以上の明示的なテーブル間にあり、必要に応じて外部キーを追加します。ただし、この場合、content
ハッシュタグ/メンション/コメントが必要な複数の異なるタイプの . どのテーブルが参照されているかは事前にわかりませんcontent_id
(コードは受け取ったデータを適切に処理します) indexed
。content_*
type
複数のcontent
テーブルが存在すると、ある段階で列が表示されますがcontent_id
、両方のテーブルが自動インクリメント主キーを使用している場合、エントリが重複する可能性がありますが、それは質問の範囲外だと思います.
ActiveRecord クラスの構造化に進みます。最初の部分は、メンション/ハッシュタグの解析を処理しています。Content
テーブルの「抽象」側を処理する抽象クラスを作成しました。これは次のようになります (簡潔にするために一部の解析は削除されています)。
class Content < ActiveRecord::Base
self.abstract_class = true;
# relationships
belongs_to :user
has_many :content_mentions;
has_many :content_hashtags;
has_many :mentions, { :through => :content_mentions, :source => :user, :as => :content };
has_many :hashtags, { :through => :content_hashtags, :as => :content };
# available columns (in the abstract side of things)
attr_accessible :text_body, :date_created;
# database hooks
around_save :around_save_hook
# parsing
ENTITY_PATTERN = /removed_for_brevity/iox;
def render_html()
# parsing of the text_body field for hashtags and mentions and replacing them with HTML
# goes in here, but unrelated to the data so removed.
end
protected
# Is this the best way to do this?
def around_save_hook()
# save the main record first (so we have a content_id to pass to the join tables)
yield
# parse the content and build associations, raise a rollback if anything fails
text_body.scan(ENTITY_PATTERN) do |boundary,token,value|
m = $~;
if m[:token] == '@'
# mention
unless mentions.where(:name => m[:value]).first
mention = User::where(:name => m[:value]).first;
next unless mention;
raise ActiveRecord::Rollback unless content_mentions.create({ :content_id => id, :user_id => mention.id });
end
else
# hashtag
unless hashtags.where(:tag => m[:value]).first
hashtag = Hashtag.where(:tag => m[:value]).first;
unless hashtag
hashtag = Hashtag.new({ :tag => m[:value] });
raise ActiveRecord::Rollback unless hashtag.save();
end
raise ActiveRecord::Rollback unless content_hashtags.create({ :content_id => id, :hashtag_id => hashtag.id });
end
end
end
end
end
ここで私が抱えている主な問題はaround_save_hook
、関連付けを解析して保存するのに最適な場所ですか? が更新され、一部のハッシュタグ/メンションがオリジナルから削除された場合、これらの変更は、削除をチェックせずに追加された新しいハッシュタグ/メンションだけでなくtext_body
、関連付けに反映されるようにするにはどうすればよいですか?content_*
残りのActiveRecord
クラスは次のように定義されています。
class Poll < Content
has_many :poll_options;
has_many :poll_answers, { :through => :poll_options }
attr_accessible :user_id, :question;
validates :text_body, :presence => true, :length => { :maximum => 1000 };
end
class PollOption < ActiveRecord::Base
belongs_to :poll;
has_many :poll_answers;
attr_accessible :content, :active, :poll_id;
end
class PollAnswer < ActiveRecord::Base
belongs_to :poll_option;
belongs_to :user;
attr_accessible :user_id, :poll_option_id;
end
class User < ActiveRecord::Base
attr_accessible :name;
validates :name, :presence => true, :length => { :maximum => 20 };
end
class Hashtag < ActiveRecord::Base
attr_accessible :tag;
validates :tag, :presence => true, :length => { :maximum => 30 };
end
# Join table for content->users
class ContentMention < ActiveRecord::Base
belongs_to :user;
belongs_to :content, { :polymorphic => true };
attr_accessible :content_id, :user_id;
end
# Join table for content->hashtags
class ContentHashtag < ActiveRecord::Base
belongs_to :hashtag;
belongs_to :content, { :polymorphic => true };
attr_accessible :content_id, :hashtag_id;
end
したがって、私の質問は次のようになると思います。
- スキーマ自体は正しいですか (つまり、非常に非効率的であり、レールで使用するように設計されていませんか? (そうであれば、それを修正する方法についての提案は素晴らしいでしょう)
Around Save
アソシエーションを解析および更新する適切な場所はありますか?- ActiveRecords は現在のスキーマ構造に基づいて正しく設定されていますか? (具体的には、
polymorphic
属性を正しく使用しているかどうかわかりません) - これに対するアプローチを
Poll
維持しながら、投票のコンテンツ全体を再保存することなく (したがって、コンテンツの別の冗長な解析をトリガーすることなく) 、インスタンスにオプション/回答を追加するにはどうすればよいですか? (つまり、オプション/回答は、モデルOOP
からパブリック API を介して作成されます)Poll
に本当に慣れている人がいて、これの最低限の機能を実装する方法の簡単なコピーを私に実行できれば、それは本当に素晴らしいRails
ことですRuby
. ActiveRecord
私が言ったように、私は以前にこのクラスを使用したことがないので、この単純なコードが 1 回の呼び出しActiveRecord
でトリガーされる未加工の SQL クエリの数さえわかりません。save()