Rails association with delegate methods. possible

2019-07-26 01:25发布

问题:

Isolated this question into it's own rails app: and added a git repo as an example

Module:

module SeoMeta

  def self.included(base)
    base.extend(ClassMethods)
    base.send :include, InstanceMethods
  end

  module ClassMethods

    def is_seo_meta

      has_one :meta,
        class_name: SeoMetum,
        as:         :metumable,
        dependent:  :destroy,
        autosave:   true

      delegate :browser_title,     :meta_description, :meta_author,
               :meta_keywords,     :browser_title=,   :meta_keywords=,
               :meta_description=, :meta_author=,
               to: :meta

      after_save :save_meta_tags!

      attr_accessible :browser_title,    :meta_keywords,
                      :meta_description, :meta_author

    end

  end

  module InstanceMethods

    class << self
      def included(base)
        base.module_eval do

          alias :original_meta_method :meta

        end
      end
    end

    def meta
      find_meta || build_meta
    end

    def find_meta
      @meta ||= ::SeoMetum.where(metumable_type: self.class.name, metumable_id: self.id).first
    end

    def build_meta
      @meta ||= ::SeoMetum.new(metumable_type: self.class.name, metumable_id: self.id)
    end  

    def save_meta_tags!
      meta.metumable_id ||= self.id
      meta.save
    end

  end
end

Models:

class User < ActiveRecord::Base
  include SeoMeta
  is_seo_meta

  has_many :collections
  accepts_nested_attributes_for :collections

  def collection
    default_collection = self.collections.first
    default_collection ||= self.collections.create
    default_collection
  end
end

class Collection < ActiveRecord::Base
  include SeoMeta
  is_seo_meta
  belongs_to :user

end

class SeoMetum < ActiveRecord::Base
  attr_accessible :browser_title, :meta_author, :meta_description, :meta_keywords,
                  :metumable, :metumable_id, :metumable_type
  belongs_to :metumable, polymorphic: true
end

Rspec Tests:

 context "user and collection" do
    context 'responds to' do
      it 'meta_description' do
        user.collection.respond_to?(:meta_description).should be_true
      end

      it 'browser_title' do
        user.collection.respond_to?(:browser_title).should be_true
      end
    end

    context 'individual allows us to assign to' do
      it 'meta_description' do
        the_collection = user.collection
        the_collection.meta_description = 'This is my description of the user for search results.'
        the_collection.meta_description.should == 'This is my description of the user for search results.'
      end

      it 'browser_title' do
        the_collection = user.collection
        the_collection.browser_title = 'An awesome browser title for SEO'
        the_collection.browser_title.should == 'An awesome browser title for SEO'
      end
    end


    context 'allows us to assign to' do
      it 'meta_description' do
        user.collection.meta_description = 'This is my description of the user for search results.'
        user.collection.meta_description.should == 'This is my description of the user for search results.'
      end

      it 'browser_title' do
        user.collection.browser_title = 'An awesome browser title for SEO'
        user.collection.browser_title.should == 'An awesome browser title for SEO'
      end
    end

    context 'allows us to update' do
      it 'meta_description' do
        user.collection.meta_description = 'This is my description of the user for search results.'
        user.collection.save

        user.collection.reload
        user.collection.meta_description.should == 'This is my description of the user for search results.'
      end

      it 'browser_title' do
        user.collection.browser_title = 'An awesome browser title for SEO'
        user.collection.save

        user.collection.reload
        user.collection.browser_title.should == 'An awesome browser title for SEO'
      end
    end
  end

The first four tests pass and the second four fail. I think it might be a bug with rails polymorphic associations but I'm not sure how to isolate it further. Comments on my module design are also appreciated.

Best, Scott

回答1:

The problem in your code is in this place:

class User
  #Other stuff

  #HERE!
  def collection
    default_collection = self.collections.first
    default_collection ||= self.collections.create
    default_collection
  end
end

Every time you call collection method you look up the first collection in the database. Therefore even if you set some values by user.collection.meta_description = "abc" then later when you call user.collection it's not the same collection object cause it's been new lookup from the database. Therefore all the attributes not saved to the database are gone. You can see this by looking at logs - every time you call user.collection you get new hit to db and also every time you call user.collection.object_id you get a different value.

You can fix it by doing something like

def collection
  return @collection if @collection
  default_collection = self.collections.first
  default_collection ||= self.collections.create
  @collection = default_collection
end