Добавление побитового виртуального столбца в модель - PullRequest
2 голосов
/ 26 февраля 2012

Я строю этот сайт RoR на основе существующей базы данных.Модель пользователя в базе данных имеет столбец с именем «секрет», который представляет собой побитовое целое число, в котором хранится информация о столбцах, которые пользователь установил как секрет (имя, фамилия и т. Д.).

Переменные имеют значениеиз двух, например: фамилия = 1 << 1 = 2, имя = 1 << 2 = 4, электронная почта == 1 << 3 = 8 и т. д. Так что, если пользователь установил имя и адрес электронной почты как секретные,значение столбца становится 4 + 8 = 12. </p>

Теперь я пытаюсь найти обобщенный способ реализации этих виртуальных столбцов в модели Rails.Так что я мог бы сделать (просто фиктивный пример, суть в том, что я хочу получить и сохранить статус):

if user.secret_email?
  user.secret_name_last = true
  user.secret_name_first = false
end

Как аккуратно реализовать эти виртуальные столбцы в модели (без изменения существующихбаза данных)?Тока у меня есть следующие.Это работает, но это не аккуратно.Поскольку у меня есть 20 секретных столбцов, код выглядит очень безобразно .

  SECRET_NAME_LAST    = (1 << 1) # 2
  attr_accessible :secret_name_last
  def secret_name_last; secret & SECRET_NAME_LAST > 0 unless secret.nil?; end
  def secret_name_last=(value); secret_set_value(SECRET_NAME_LAST, value); end

  SECRET_NAME_FIRST   = (1 << 2) # 4
  attr_accessible :secret_name_first
  def secret_name_first; secret & SECRET_NAME_FIRST > 0 unless secret.nil?; end
  def secret_name_first=(value); secret_set_value(SECRET_NAME_FIRST, value); end

  SECRET_EMAIL        = (1 << 3) # 8
  attr_accessible :secret_email
  def secret_email; secret & SECRET_EMAIL > 0 unless secret.nil?; end
  def secret_email=(value); secret_set_value(SECRET_EMAIL, value); end

  ***snip (17 more)***

  private
  def secret_set_value(item, value)
    if self.secret.nil?
      self.secret = 0
    end

    if value == "1" || value == true || value == 1
      # Add item to secret column (if it doesn't exist)
      if self.secret & item == 0
        self.secret += item
      end
    else
      # Remove item from secret column (if it exists)
      if self.secret & item > 0
        self.secret -= item
      end
    end
  end

Было бы замечательно , я мог бы просто сделать что-то вроде:

as_bitwise :secret_name_first, :column=>'secret', :value=>4
as_bitwise :secret_name_last,  :column=>'secret', :value=>2

Или даже,

as_bitwise :secret, { :secret_name_last=>4, :secret_name_first=>2 }

РЕДАКТИРОВАТЬ

Исходя из превосходного ответа Брендана, вот что я получил в настоящее время:

  module BitwiseColumn
    extend ActiveSupport::Concern

    module ClassMethods
      def bitwise_column(*args)
        mapping = args.extract_options!
        column_name = args.shift
        real_column_name = args.shift

        logger.debug "Initializing bitwisecolumn, column: " + column_name.to_s

        mapping.each_pair do |attribute, offset|
          logger.debug "\tSetting a pair: offset: " + offset.to_s + ", " + attribute.to_s
          mask = 2 ** offset

          class_eval %{
            attr_accessible :#{column_name}_#{attribute}
            def #{column_name}_#{attribute}?
              #{real_column_name} & #{mask} > 0 unless #{real_column_name}.nil?
            end

            def #{column_name}_#{attribute}=(value)
              if self.#{real_column_name}.nil?
                self.#{real_column_name} = 0
              end

              if value == "1" || value == true || value == 1
                if self.#{real_column_name} & #{mask} == 0
                  self.#{real_column_name} += #{mask}
                end
              else
                if self.#{real_column_name} & #{mask} > 0
                  self.#{real_column_name} -= #{mask}
                end
              end
            end
          }
        end
      end
    end
  end

Это позволяет мне использовать:

  bitwise_column :secret, :realsecretcolumnatdatabase, :name_last=>1, :name_first=>2, :email=>3, :picture=>5, :dob=>6, :place=>12

После этого я могу позвонить User.first.secret_name_last?и т.д.

1 Ответ

3 голосов
/ 26 февраля 2012

Вы можете использовать class_eval, чтобы немного высушить ваш код. Я бы также предложил включить это поведение в какой-то модуль, отдельный от вашего User класса, чтобы вы могли тщательно и отдельно протестировать его с другими User -специфичными поведениями.

Как и вы, я склонен запускать подобные задачи с нужным API и работать в обратном направлении. Я начал с этого в моей модели:

class User < ActiveRecord::Base
  include BitwiseColumn

  bitwise_column :secret, :first_name => 1, :last_name => 2
end

Хэш, переданный bitwise_column, отображает имена виртуальных атрибутов в их значения маски в качестве показателя степени. Я чувствовал, что этим легче управлять, чем самому помнить о силах двух: -)

Затем я создал миксин:

module BitwiseColumn
  extend ActiveSupport::Concern

  module ClassMethods
    def bitwise_column(*args)
      mapping = args.extract_options!
      column_name = args.shift

      mapping.each_pair do |attribute, offset|
        mask = 2 ** offset

        class_eval %{
          def secret_#{attribute}?
            #{column_name} & #{mask} > 0 unless #{column_name}.nil?
          end

          def secret_#{attribute}=(value)
            if self.#{column_name}.nil?
              self.#{column_name} = 0
            end

            if value == "1" || value == true || value == 1
              if self.#{column_name} & #{mask} == 0
                self.#{column_name} += #{mask}
              end
            else
              if self.#{column_name} & #{mask} > 0
                self.#{column_name} -= #{mask}
              end
            end
          end
        }
      end
    end
  end
end

Этот миксин создает два метода экземпляра для каждого виртуального атрибута, один с ? и один с =, так как это то, что вам нужно. Я использовал вашу существующую логику для побитовых операций, которая, кажется, прекрасно работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...