我幾乎是Ruby和Rails框架的初學者,這就是爲什麼我決定在做一些打破框架約定的事情之前尋求幫助。使用rails構造ActiveRecord
我有一個相當穩定的OO
編程背景,對於初學者 - >中級SQL查詢我非常滿意。然而,我一直在圍繞着Rails提供的ActiveRecord
類包裝我的頭。我的直覺是完全拋棄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;
由於架構將表明,這是一個非常基本的網絡民意調查中的應用。每個投票都有多個選項,每個選項可以由不同的用戶提供多個答案。現在,看起來很奇怪的部分是content_*
表。我可以解釋這一點的最好方法可能是將其描述爲abstract
表格。我從來沒有真正做過這樣的事情,通常這些關係是在兩個或多個顯式表之間,我會根據需要添加外鍵。然而,在這種情況下,我可能會得到多種不同類型的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
,並且從原始中刪除了一些hashtags /提及,這些更改將反映在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
所以我想我的問題如下:
- 是模式本身corrent(即是非常低效和不良設計用於軌道?(如果是這樣,關於如何糾正它的建議將是太棒了)
- 是
Around Save
解析和更新關聯的正確位置? - 我的ActiveRecords是否基於當前模式結構正確設置? (具體而言,我不確定我是否正確使用
polymorphic
屬性) - 如何在不重新保存輪詢的全部內容的情況下將選項/答案添加到
Poll
實例中(從而觸發另一個冗餘分析內容),同時仍然保留OOP
的方法呢? (即選項/答案通過公共API從Poll
模型創建)
它會非常巨大的,如果有人誰與Rails
,Ruby
和ActiveRecord
真的很舒服可以運行了我的快速複製他們將如何執行這個的基本框架。正如我所說,我從來沒有使用過ActiveRecord
類,所以我甚至不知道這個簡單的代碼將在單個save()
調用上觸發多少個原始SQL查詢。
我覺得這個問題有點太大 - 它幾乎要求您的應用程序諮詢。我會說,如果你從Rails入手,那就試試吧,當你犯錯並學習時,你可以回過頭來逐步改進。但是,一個技巧是使用'after_save'而不是'around_save'和'yield'。 – 2013-03-19 10:58:29
我明白這是一個相當大的問題,但我所要求的僅僅是一小撮代碼,其中大部分僅僅是關於在嵌套連接表中正確使用ActiveRecords的一些建議,避免了冗餘更新記錄的需要。應用程序的hashtag /提及/註釋方面與問題無關(除了關於構造「抽象」表的一般問題之外),所以本質上它只是詢問如何使用ActiveRecords實現「投票」,而不是「大」。至於'after_save'方法,我無法找到一個明確的文檔,以確定是否可以回滾它。 – 2013-03-19 11:24:25
你可能是對的,它只是一小撮代碼,但它是對上下文的理解,需要大量的時間投入才能夠提供那些微不足道的代碼。在可能的情況下將其分解成更小的問題會更好。 – 2013-03-19 11:29:34