2013-03-19 2 views
1

저는 Ruby와 Rails 프레임 워크에서 초보자입니다. 프레임 워크 규칙을 어기는 것을하기 전에 도움을 청하기로 결심했습니다.레일을 사용하여 구조화하기 ActiveRecord

프로그래밍 배경이 상당히 견고하며, 초급 - 중급 SQL 쿼리에 익숙합니다. 그러나 레일즈가 제공하는 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_id 항목이 중복 될 수 있으므로 content 테이블이 두 번 이상 존재할 수 있습니다. 그래도 문제의 범위.

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_* 연관에 반영됩니다.

  1. 자체가 (즉이 심하게 비효율적이고 잘못이다 corrent 스키마가 :

    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 
    

    그래서 나는 다음과 같이 제 질문은 추측 다음과 같이

    ActiveRecord 클래스의 나머지 부분은 정의 레일과 함께 사용하도록 설계 되었습니까? (그렇다면 올바른 방법에 대한 제안은 환상적 일 것입니다.)

  2. Around Save은 연관을 파싱하고 업데이트 할 수있는 적절한 장소입니까?
  3. 내 ActiveRecord가 현재 스키마 구조를 기반으로 올바르게 설정되어 있습니까? (특히 내가 polymorphic 속성을 올바르게 사용하고 있는지 잘 모르겠다.)
  4. 설문 조사의 전체 내용을 다시 저장하지 않고 Poll 인스턴스에 옵션/답변을 추가하는 방법은 무엇입니까? 콘텐츠)이 여전히 OOP의 접근 방식을 유지하고 있습니까? Rails, RubyActiveRecord 정말 편안 누군가의 고속 복사 날을 실행할 수 있다면

그것은 정말 좋은 것 (예 : 옵션/답변이 Poll 모델 공개 API를 통해 만들어지는) 그들이 어떻게 것 이것의 베어 본을 구현하십시오. 내가 말했듯이 ActiveRecord 클래스를 사용한 적이 없기 때문에이 간단한 코드가 단일 save() 호출에서 얼마나 많은 원시 SQL 쿼리를 트리거하는지조차 알지 못합니다.

+1

이 질문은 너무 크다고 생각합니다. 앱에서 컨설팅을 거의 요구합니다. 나는 레일스로 시작한다면, 그냥 가서 시도해보고, 실수를하고 배울 때 점진적으로 돌아가고 향상시킬 수 있다고 말하고 싶습니다. 그러나, 한 가지 팁은'around_save'와'yield' 대신'after_save'를 사용하는 것입니다. –

+0

상당히 큰 문제라는 것을 알고 있습니다 만, 나는 단지 사소한 양의 코드 일뿐입니다. 대부분은 중첩 된 조인 테이블에서 ActiveRecord를 올바르게 사용하여 레코드를 중복 업데이트하지 않아도된다는 조언입니다. 응용 프로그램의 해시 태그/언급/주석 측면은 "추상"테이블 구조에 대한 일반적인 질문 이외의 질문과 관련이 없으므로 기본적으로 ActiveRecords를 사용하여 '투표'를 구현하는 방법을 묻는 것입니다. 'after_save' 메서드에 관해서는, 내부에서 롤백 할 수 있는지 여부에 대한 명확한 문서를 찾을 수 없었습니다. –

+0

당신은 아마 사소한 양의 코드일지도 모르겠지만 누군가가 사소한 코드를 제공 할 수 있도록 많은 시간을 투자해야하는 상황을 이해하고있는 것은 틀림 없습니다. 가능한 경우 작은 질문으로 나누는 것이 낫습니다. –

답변

2

다음은 설문 조사/설문 조사 응용 프로그램 구현 측면을 다루는 두 부분으로 구성된 railscast입니다. 그것은 모델 관련 의심의 대부분을 다룹니다.

http://railscasts.com/episodes/196-nested-model-form-part-1

http://railscasts.com/episodes/197-nested-model-form-part-2

나는 text_body에 대한 세터를 타고 오버에 의해 할당시 종속 개체를 만드는 것입니다.

예는 :

def text_body=(val) 
    write_attribute(:text_body, val).tap do |v| 
    append_new_tags_and_mentions 
    end 
end 

def append_new_tags_and_mentions 
    tag_list, mention_list = extract_tags_and_mentions 
    new_mentions = mention_list - mentions.where(name => mention_list).pluck(:name)  
    mentions.concat(*new_mentions) if new_mentions.present? 
    new_tags = tag_list - hashtags.where(tag => tag_list).pluck(:tag) 
    hashtags.concat(*new_tags) if new_tags.present? 
end 

def extract_tags_and_mentions 
    tag_list = [] 
    mention_list = [] 
    text_body.scan(ENTITY_PATTERN) do |boundary, token, value| 
    if token == "@" 
     mention_list << value 
    else 
     tag_list << value 
    end 
    end 
    [tag_list, mention_list] 
end 

는 종속성을 확인하는 유효성 검사기를 추가합니다.

일반 지침 Java/C++/SQL에서 오랫동안 작업 한 후에 레일 프로그래밍을 시작하기 전에 알고 싶습니다.

  • 는 테이블 생성 SQL을하지 손 코드를

  • 를 사용하여 DB를 수행합니다 테이블

  • 레일 foregin 키를 지원하지 않습니다를 만드는 레이크 작업을 만듭니다. 유효성 검사기를 통해 시행 할 수 있습니다.

  • 줄을 끝내려면 세미콜론을 사용하지 마십시오. 루비의 즐거움 중 하나는 종결 선이 없다는 것입니다.

  • DSL API 매개 변수에 명시 적 해시를 사용하지 마십시오.

    사용이 관용구 대신

    belongs_to :content, :polymorphic => true 
    

    : 코드 재 사용에 대한

    belongs_to :content, { :polymorphic => true }; 
    
  • 사용 모듈 대신 상속.

  • 사용 each 대신 for

  • 배열에 map, reduce (즉 inject) 기능을 학습.

+0

답장을 보내 주셔서 감사합니다. 집에 돌아와서 스크린 캐스트를 보러갑니다. 그러나 나중에 작성한 몇 가지 사항에 대해 몇 가지 문제가 있습니다. 이것은 다소 다소 "엄격한"언어 배경 때문일 수 있습니다. 'DSL API 매개 변수에 명시적인 해시를 사용하지 마십시오. '왜? 내가 루비에 대해 싫어했던 한 가지는 메소드 인자 주위에 괄호를 감싸는 것을 강제하지 않는다는 것과 괄호없이 해쉬를 게으르게 쓸 수 있다는 것이다. 적어도 저 유행을 따르는 논쟁을 써서 한눈에 해독하는 것이 훨씬 더 어려워집니다. 내가 루비를 처음 접했기 때문일 수도 있지만, 나는 그 자세한 말을 좋아한다. –

+0

명시 적 라인 종결 자에 관해서는; 나는 C/C++, PHP, C#, Javascript ... 등의 과정에서 너무 오래 쓰는 데 보냈습니다. 그들을 퇴출시키지 않으려면 육체적 인 노력이 필요하다고 생각했습니다. :) –

+1

나는 세미콜론으로 줄을 끝내고 명시 적으로 해시를 중괄호로 묶어야합니다. 사실 Ruby에서 코딩을 시작할 때 사용했던 것입니다. 대회는 그들을 사용하지 않는다. 나는 지금 당장 당신이 이것에 대해 다르게 느낄 것임을 확신합니다. Dave Thomas http://vimeo.com/61255738#t=2246에 대한 좋은 토론이 있습니다. 한 섹션에서 그는 비 루비 개발자가 작성한 코드의 진행에 대해 이야기합니다. 나는 그것이 적당한 것을 알았다. –

관련 문제