【问题标题】:ActiveRecord::Base.store automatic typecastingActiveRecord::Base.store 自动类型转换
【发布时间】:2012-07-20 15:13:34
【问题描述】:

有没有办法自动对使用 ActiveRecord::Base.store 存储的值进行类型转换?

举这个完全不切实际的例子:

class User < ActiveRecord::Base
  store :settings, accessors: [ :age ]
end

user = User.new(age: '10')
user.age # => '10'

我知道我可以重写年龄的 reader 方法以将其转换为整数,但我很好奇是否存在未记录的方法。

尽量避免这种情况:

class User < ActiveRecord::Base
  store :settings, accessors: [ :age ]

  def age
    settings[:age].to_i
  end
end

user = User.new(age: '10')
user.age # => 10

更新

寻找类似的东西:

class User < ActiveRecord::Base
  store :settings, accessors: {:age => :to_i}
end

或者:

class User < ActiveRecord::Base
  store :settings, accessors: {:age => Integer}
end

【问题讨论】:

    标签: ruby-on-rails ruby casting ruby-on-rails-3.2


    【解决方案1】:

    从 Rails 3.2.7 开始,没有自动类型转换值的方法。如果我遇到一种方法,我会更新这个问题:/

    【讨论】:

      【解决方案2】:

      我知道有两种方法可以做到这一点。其中之一是您每次设置时都会转换它。另一个只有在将其保存到数据库时才进行转换。

      选项一:自定义设置器

      class User < ActiveRecord::Base
      
        ...
      
        # public method
        def age=(age)
          self.settings[:age] = age.to_i
        end
      
        ...
      
      end
      

      在控制台中:

      $ user.age = '12'     # => "12"
      $ user.age            # => 12
      $ user.age.class      # => Fixnum
      $ user = User.new age: '5'
      $ user.age.class      # => Fixnum
      

      选项二: before_save 调用(或不同的调用前)

      class User < ActiveRecord::Base
        before_save :age_to_int
      
        ...
      
        private
      
          def age_to_int
            # uncomment the if statement to avoid age being set to 0
            # if you create a user without an age
            self.age = self.age.to_i # if self.age 
          end
      
      end
      

      在控制台中

      $ user = User.new(age: '10')
      $ user.save
      $ user.age            # => 10
      $ user.age.class      # => Fixnum
      

      选项二的不足:

      $ user.age = '12'
      $ user.age            # => "12"
      

      如果我是你,我会使用自定义设置器。如果您想要一个独立于数据库列(它是一个字符串)的默认值,请在设置器之外使用 before_save。

      【讨论】:

      • 感谢您的回复,但这并不是我想要的。我希望 DSL 方法中内置了一些东西,可以让我设置默认值。
      • 我认为这与你可以放入任何东西的哈希值背道而驰......但是环顾四周,祝你好运
      • 同意,这完全是对 ActiveRecord 和关系数据库的滥用。我猜这更像是一件好奇的事情。
      • 是的,我喜欢这个想法——我不知道它存在。如果您需要一个类型化的列,您可能应该只创建一个整数的年龄列。再说 3 行 setter 的代码也不算太可怕
      • 希望有人能来启发我们:)
      【解决方案3】:

      StorextActiveRecord::Store.store_accessor 之上添加了类型转换和其他功能。

      【讨论】:

        【解决方案4】:

        最好链接存储访问器方法而不是覆盖它们,因为这些魔法咒语创建的方法从来没有你想象的那么简单:

        define_method(key) do
          send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
          send(store_attribute)[key]
        end
        

        所以,以整数为例,我会这样做:

        def age_with_typecasting
          ActiveRecord::ConnectionAdapters::Mysql2Adapter::Column.value_to_integer(age_without_typecasting)
        end
        
        alias_method_chain :age, :typecasting
        

        同样,它不是内置的,但可以解决问题。它还利用您的数据库连接适配器将存储在数据库中的字符串转换为您想要的值类型。根据您使用的数据库更改适配器。

        【讨论】:

          【解决方案5】:

          我们没有找到任何未记录的方法,但即使您找到了,也不建议您将代码依赖于 Rails 私有方法。

          这是我们在 Rails 5.2 中使用支持的 API 的方式。假设您有一个费率模型,并且希望将每日费率存储在名为 :rate_per_day 的单个数据库列中(我们使用的是 MySQL,因此该列的类型是 TEXT):

          class Rate < ApplicationRecord
          
            DAILY_RATES = [
              :monday_rate,
              :tuesday_rate,
              :wednesday_rate,
              :thursday_rate,
              :friday_rate,
              :saturday_rate,
              :sunday_rate,
            ]
          
            store :rate_per_day, accessors: DAILY_RATES, coder: JSON
          
            DAILY_RATES.each do |method|
          
              define_method(method) do
                super().try(:to_i)
              end
          
              define_method("#{method}=") do |value|
                super(value.try(:to_i))
              end
          
            end
          

          这是官方支持的。阅读https://api.rubyonrails.org/classes/ActiveRecord/Store.html 中的覆盖默认访问器。

          顺便说一句,我们使用新的 Attributes API 进行了调查,因为它支持自动类型转换,但除非我们创建自定义类型对象,否则无法找到将所有数据存储在单个列中的方法。

          【讨论】:

            【解决方案6】:

            activerecord-typedstore 可以很好地解决这个问题,并允许您防止 null 和空白值:

            https://github.com/byroot/activerecord-typedstore

            【讨论】:

              猜你喜欢
              • 2017-10-23
              • 2016-10-16
              • 2010-12-01
              • 1970-01-01
              • 1970-01-01
              • 2011-06-13
              • 2011-12-17
              • 2011-01-25
              • 1970-01-01
              相关资源
              最近更新 更多