ActiveRecord: лучший способ добавить «поддельный» класс модели? - PullRequest
0 голосов
/ 09 апреля 2020

В нашем приложении Rails ресурс Post может быть сделан либо User, либо Admin.

Таким образом, у нас есть класс модели ActiveRecord, называемый Post, с belongs_to :author, polymorphic: true.

Однако при определенных условиях сама система должна создавать сообщения. Поэтому я ищу способ добавить, например, System как author.

Очевидно, что когда-либо будет только один System, поэтому он не сохраняется в базе данных.

Наивная попытка просто добавить экземпляр (например, экземпляр синглтона) class System; end, поскольку автор возвращает ошибки, подобные NoMethodError: undefined method `primary_key' for System:Class.

Каков самый чистый способ решения этой проблемы?

Есть ли способ написать «поддельную» модель ActiveRecord, которая на самом деле не является частью базы данных?

Ответы [ 2 ]

1 голос
/ 09 апреля 2020

Есть два способа, которые я вижу наиболее подходящими:

Вариант A: Добавить 'системную' Author запись в БД

Это не ужасная идея, это просто перекладывает бремя на вас, обеспечивая наличие определенных записей в каждой среде. Но вы всегда можете создать эти записи в начальных файлах, если хотите убедиться, что они всегда создаются.

Преимущество по сравнению с вариантом B состоит в том, что вы можете просто использовать стандартные ActiveRecord запросы, чтобы найти все системные Post s.

Вариант B: Оставьте ассоциацию ноль и добавьте новый флаг для :created_by_system

Это то, что я бы выбрал. Если система сделала Post, просто оставьте ссылку на автора незаполненной и установите специальный флаг, чтобы указать, что эта модель была создана внутри.

У вас все еще может быть метод для быстрого получения списка всех просто сделав область видимости:

  scope :from_system, -> { where(created_by_system: :true) }

Какой из них вы выберете, я думаю, зависит от того, хотите ли вы иметь возможность запросить Post.author и получить информацию о Системе. В этом случае вам нужно выбрать вариант A. В противном случае я бы использовал вариант B. Я уверен, что есть и другие способы сделать это, но я думаю, что это имеет смысл.

0 голосов
/ 16 апреля 2020

Наконец, я закончил с созданием следующего «поддельного» класса модели, который не требует каких-либо изменений в схеме базы данных.

Он использует немного метапрограммирования:

# For the cases in which the System itself needs to be given an identity.
# (such as when it does an action normally performed by a User or Admin, etc.)
class System
  include ActiveModel::Model
  class << self
    # The most beautiful kind of meta-singleton
    def class
      self
    end

    def instance
      self
    end

    # Calling`System.new` is a programmer mistake; 
    # they should use plain `System` instead.
    private :new

    def primary_key
      :id
    end

    def id
      1
    end

    def readonly?
      true
    end

    def persisted?
      true
    end

    def _read_attribute(attr)
      return self.id if attr == :id

      nil
    end

    def polymorphic_name
      self.name
    end

    def destroyed?
      false
    end

    def new_record?
      false
    end
  end
end

Главное замечание здесь заключается в том, что System - это и собственный класс, и собственный экземпляр. Это имеет следующие преимущества:

  • Мы можем просто передать Post.new(creator: System) вместо System.new или System.instance
  • В любой точке существует только одна система.
  • Мы можем определить методы класса, которые требуются ActiveRecord (polymorphic_name) для самого System, а не для Class.

Конечно, нравится ли вам этот тип метапрограммирования или вы найдете его слишком запутанный - очень субъективный.

Что менее субъективно, так это то, что переопределение ActiveRecord _read_attribute нехорошо; мы зависим от деталей реализации ActiveRecord. К сожалению, насколько мне известно, не существует открытого API-интерфейса c, который можно было бы использовать для более чистой работы. (В нашем проекте у нас есть некоторые спецификации, чтобы немедленно уведомить нас, когда ActiveRecord может изменить это.)

...