Направляющие Полиморфная Связь с несколькими ассоциациями на той же модели

Моим вопросом является по существу то же как этот: Полиморфная Связь с несколькими ассоциациями на той же модели

Однако предложил/принял, чтобы решение не работало, как проиллюстрировано комментатором позже.

У меня есть фото класс, который используется на всем протяжении моего приложения. Сообщение может иметь единственную фотографию. Однако я хочу снова использовать полиморфные отношения для добавления вторичной фотографии.

Прежде:

class Photo 
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo, :as => :attachable, :dependent => :destroy
end

Желаемый:

class Photo 
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo,           :as => :attachable, :dependent => :destroy
   has_one :secondary_photo, :as => :attachable, :dependent => :destroy
end

Однако это перестало работать, поскольку это не может найти класс "SecondaryPhoto". На основе того, что я мог сказать от того другого потока, я захочу сделать:

   has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy

Кроме вызова Post#secondary_photo просто возвращает ту же фотографию, которая присоединяется через фото ассоциацию, например, Post#photo === Post#secondary_photo. Смотря на SQL, это делает, ГДЕ тип = "фотография" вместо, скажем, "SecondaryPhoto", как я хотел бы...

Мысли?Спасибо!

54
задан Community 23 May 2017 в 02:18
поделиться

4 ответа

Я так и сделал в своем проекте.

Фокус в том, что фотографиям нужен столбец, который будет использоваться в условии has_one для различения первичных и вторичных фотографий. Обратите внимание на то, что происходит в :conditions здесь.

has_one :photo, :as => 'attachable', 
        :conditions => {:photo_type => 'primary_photo'}, :dependent => :destroy

has_one :secondary_photo, :class_name => 'Photo', :as => 'attachable',
        :conditions => {:photo_type => 'secondary_photo'}, :dependent => :destroy

Красота этого подхода заключается в том, что когда вы создаете фотографии с помощью @post.build_photo, тип_фотографии будет автоматически заполнен соответствующим типом, например 'primary_photo'. ActiveRecord достаточно умна, чтобы сделать это.

70
ответ дан 7 November 2019 в 07:53
поделиться

Я не использовал это, но я погуглил и заглянул в источники Rails и думаю, что то, что вы ищете, это :foreign_type. Попробуйте и скажите, работает ли это :)

has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy, :foreign_type => 'SecondaryPost'

Я думаю, что тип в вашем вопросе должен быть Post вместо Photo и, соответственно, лучше использовать SecondaryPost, так как он присвоен модели Post.

EDIT:

Вышеприведенный ответ полностью неверен. :foreign_type доступен в полиморфной модели в ассоциации belongs_to для указания имени столбца, содержащего тип ассоциированной модели.

Как я смотрю в исходниках Rails, эта строка задает этот тип для ассоциации:

dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]

Как вы можете видеть, она использует base_class.name для получения имени типа. Насколько я знаю, вы ничего не можете с этим сделать.

Поэтому я предлагаю добавить один столбец в модель Photo, например: photo_type. И установить его в 0, если это первая фотография, или в 1, если это вторая фотография. В ваших ассоциациях добавьте :conditions => {:photo_type => 0} и :conditions => {:photo_type => 1}, соответственно. Я знаю, что это не то решение, которое вы ищете, но я не могу найти ничего лучше. Кстати, может быть лучше просто использовать ассоциацию has_many?

3
ответ дан 7 November 2019 в 07:53
поделиться

Можете ли вы добавить модель SecondaryPhoto, например:

class SecondaryPhoto < Photo
end

, а затем пропустить: class_name из has_one: secondary_photo?

0
ответ дан 7 November 2019 в 07:53
поделиться

Вам придется изменить понятие foreign_type в отношении has_one. Вот что я сделал для has_many. В новом файле .rb в папке initializers я назвал свой add_foreign_type_support.rb. В нем вы можете указать, каким должен быть ваш прикрепляемый_тип. Пример: has_many photo, :class_name => "Picture", :as => attachable, :foreign_type => 'Pic'

module ActiveRecord
  module Associations
    class HasManyAssociation < AssociationCollection #:nodoc:
      protected
        def construct_sql
          case
            when @reflection.options[:finder_sql]
              @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
           when @reflection.options[:as]
              resource_type = @reflection.options[:foreign_type].to_s.camelize || @owner.class.base_class.name.to_s
              @finder_sql =  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND "
              @finder_sql += "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(resource_type)}"
              else
                @finder_sql += ")"
              end
              @finder_sql << " AND (#{conditions})" if conditions

            else
              @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
              @finder_sql << " AND (#{conditions})" if conditions
          end

          if @reflection.options[:counter_sql]
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          elsif @reflection.options[:finder_sql]
            # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
            @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          else
            @counter_sql = @finder_sql
          end
        end
    end
  end
end
# Add foreign_type to options list
module ActiveRecord
  module Associations # :nodoc:
     module ClassMethods
      private
        mattr_accessor :valid_keys_for_has_many_association
        @@valid_keys_for_has_many_association = [
          :class_name, :table_name, :foreign_key, :primary_key, 
          :dependent,
          :select, :conditions, :include, :order, :group, :having, :limit, :offset,
          :as, :foreign_type, :through, :source, :source_type,
          :uniq,
          :finder_sql, :counter_sql,
          :before_add, :after_add, :before_remove, :after_remove,
          :extend, :readonly,
          :validate, :inverse_of
        ]

    end
  end
2
ответ дан 7 November 2019 в 07:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: