不能将datetime_select与Mongoid一起使用

时间:2013-06-03 23:36:39

标签: ruby-on-rails ruby mongodb mongoid

每当我尝试在视图中使用Datetime_select时,应用程序都会抛出属性错误。

Mongoid::Errors::UnknownAttribute:

   Problem:
     Attempted to set a value for 'fromtime(1i)' which is not allowed on the model Event.
   Summary:
     Without including Mongoid::Attributes::Dynamic in your model and the attribute does not already exist in the attributes hash, attempting to call Event#fromtime(1i)= for it is not allowed. This is also triggered by passing the attribute to any method that accepts an attributes hash, and is raised instead of getting a NoMethodError.
   Resolution:
     You can include Mongoid::Attributes::Dynamic if you expect to be writing values for undefined fields often.

我经常遇到的解决方案是在模型中包含Mongoid :: MultiParameterAttributes。不幸的是,该模块已被删除! https://github.com/mongoid/mongoid/issues/2954

我尝试过分配gem并重新添加MultiparameterAttributes模块,但gem不会从lib文件中读取代码。有没有办法将DateTime_select与Mongoid一起使用?

2 个答案:

答案 0 :(得分:4)

<击> 您需要在Mongoid模型中包含include Mongoid::MultiParameterAttributes

请参阅this GitHub issue on the problem

我无法在任何地方找到它.-

这会教我不正确阅读! This gem seems to be the solution though

答案 1 :(得分:0)

不幸的是,多参数分配是ActiveRecord中的实现,而不是ActiveModel中的实现。因此,Mongoid必须具有自己的实现,但是他们放弃了对该功能的支持,而将其留给ActiveSupport和ActiveModel来解决。好吧,看一下Rails源代码,它仍然保留在ActiveRecord中。

幸运的是,您可以在process_attributes方法中挂钩自己的实现,该方法在对Mongoid对象分配属性时(例如在创建或更新操作期间)调用。

要对其进行测试,只需创建一个config / initializer / multi_parameter_attributes.rb并添加以下代码,即可在Mongoid :: Document模块中添加必要的功能:

module Mongoid

 module MultiParameterAttributes

  module Errors

  class AttributeAssignmentError < Mongoid::Errors::MongoidError
    attr_reader :exception, :attribute

    def initialize(message, exception, attribute)
      @exception = exception
      @attribute = attribute
      @message = message
    end
  end

  class MultiparameterAssignmentErrors < Mongoid::Errors::MongoidError
    attr_reader :errors

    def initialize(errors)
      @errors = errors
    end
  end
end

def process_attributes(attrs = nil)
  if attrs
    errors = []
    attributes = attrs.class.new
    attributes.permit! if attrs.respond_to?(:permitted?) && attrs.permitted?
    multi_parameter_attributes = {}
    attrs.each_pair do |key, value|
      if key =~ /\A([^\(]+)\((\d+)([if])\)$/
        key, index = $1, $2.to_i
        (multi_parameter_attributes[key] ||= {})[index] = value.empty? ? nil : value.send("to_#{$3}")
      else
        attributes[key] = value
      end
    end

    multi_parameter_attributes.each_pair do |key, values|
      begin
        values = (values.keys.min..values.keys.max).map { |i| values[i] }
        field = self.class.fields[database_field_name(key)]
        attributes[key] = instantiate_object(field, values)
      rescue => e
        errors << Errors::AttributeAssignmentError.new(
            "error on assignment #{values.inspect} to #{key}", e, key
        )
      end
    end

    unless errors.empty?
      raise Errors::MultiparameterAssignmentErrors.new(errors),
            "#{errors.size} error(s) on assignment of multiparameter attributes"
    end

    super attributes
  else
    super
  end
end

protected

def instantiate_object(field, values_with_empty_parameters)
  return nil if values_with_empty_parameters.all? { |v| v.nil? }
  values = values_with_empty_parameters.collect { |v| v.nil? ? 1 : v }
  klass = field.type
  if klass == DateTime || klass == Date || klass == Time
    field.mongoize(values)
  elsif klass
    klass.new(*values)
  else
    values
  end
end
  end
  module Document
    include MultiParameterAttributes
  end
end

那么这段代码做什么?我们创建一个数据结构multi_parameter_attributes,它将存储与以下正则表达式模式匹配的所有属性:/ \ A([^(] +)((\ d +)([if]))$ /。\ A匹配字符串的开头通常,您习惯于看到^以匹配字符串的开头,但是\ A及其对应的\ Z不管换行符都是匹配的。我们有3个捕获组。第一个捕获组([[^(] +),将匹配所有字符在字符串'starttime(1i)'中将捕获'starttime',第二个捕获组(\ d +)将捕获数字,因此'starttime(1i)'中的'1' 。第三个捕获组([if])将捕获i或f。i表示整数值。

现在,典型的日期时间字段包含许多部分,如下所示:

starttime(1i) => 2019
starttime(2i) => 6
starttime(3i) => 28
starttime(4i) => 19
starttime(5i) => 18

因此,我们正在遍历属性哈希,以便将数据结构构建为multi_parameter_attributes:

attrs.each_pair do |key, value|
  ...
end

请记住,我们在正则表达式中使用了捕获组。以后我们可以使用Ruby的$ 1,$ 2等全局变量来引用捕获的组。键是属性的名称,例如开始时间。 index是指日期时间中属性的一部分,例如年,月,日等。$ 3将i保留在捕获的第三组中,因为我们要获取字符串值并将其转换为整数:

key, index = $1, $2.to_i
(multi_parameter_attributes[key] ||= {})[index] = value.empty? ? nil : value.send("to_#{$3}")

最终,我们最终得到了一个很好的数据结构,如下所示:

{ starttime: { 1 => 2019, 2 => 6, 3 => 28, 4 => 19, 5 => 18 } }

现在我们做一些聪明的事情来获取实际的日期部分:

 values = (values.keys.min..values.keys.max).map { |i| values[i] }

这将给我们:

[2019, 6, 28, 19, 18] 

好吧,现在我们有了想要的日期。剩下的就是使用Mongoid API生成用于存储日期的字段对象。