Skip to content

Instantly share code, notes, and snippets.

@k-rudy
Last active September 6, 2016 13:02
Show Gist options
  • Save k-rudy/7580106 to your computer and use it in GitHub Desktop.
Save k-rudy/7580106 to your computer and use it in GitHub Desktop.
Multiparameter Attributes support in Mongoid 4 (Rails 4.0.1) Code is taken from https://github.com/mongoid/mongoid/issues/2954 with minor amendments to work with Rails 4.0.1
# This class is needed since Mongoid doesn't support Multi-parameter
# attributes in version 4.0 before it's moved to active model in rails 4
#
# https://github.com/mongoid/mongoid/issues/2954
#
require "spec_helper"
describe Mongoid::MultiParameterAttributes do
describe "#process" do
class Multi
include Mongoid::Document
include Mongoid::MultiParameterAttributes
field :created_at, type: Time
field :dob, type: Date
field :checked_at, as: :last_user_checked_at, type: Time
end
context "creating a multi" do
let(:multi) do
Multi.new(
"created_at(1i)" => "2010",
"created_at(2i)" => "8",
"created_at(3i)" => "12",
"created_at(4i)" => "15",
"created_at(5i)" => "45"
)
end
it "sets a multi-parameter Time attribute correctly" do
multi.created_at.should eq(Time.zone.local(2010, 8, 12, 15, 45))
end
it "does not leave ugly attributes on the model" do
multi.attributes.should_not have_key("created_at(1i)")
end
end
context "creating a multi" do
context "with a valid DOB" do
let(:multi) do
Multi.new({
"dob(1i)" => "1980",
"dob(2i)" => "7",
"dob(3i)" => "27"
})
end
it "sets a multi-parameter Date attribute correctly" do
multi.dob.should eq(Date.civil(1980, 7, 27))
end
end
context "with an invalid DOB" do
let(:invalid_multi) do
Multi.new({
"dob(1i)" => "1980",
"dob(2i)" => "2",
"dob(3i)" => "31"
})
end
it "uses Time's logic to convert the invalid date to valid" do
invalid_multi.dob.should eq(Time.new(1980, 2, 31).to_date)
end
end
end
context "with a blank DOB" do
let(:multi) do
Multi.new(
"dob(1i)" => "",
"dob(2i)" => "",
"dob(3i)" => ""
)
end
it "generates a nil date" do
multi.dob.should be_nil
end
end
context "with a partially blank DOB" do
let(:multi) do
Multi.new(
"dob(1i)" => "1980",
"dob(2i)" => "",
"dob(3i)" => ""
)
end
it "sets empty date's year" do
multi.dob.year.should eq(1980)
end
it "sets empty date's month" do
multi.dob.month.should eq(1)
end
it "sets empty date's day" do
multi.dob.day.should eq(1)
end
end
context "with aliased field" do
let(:multi) do
Multi.new(
"last_user_checked_at(1i)" => "2010",
"last_user_checked_at(2i)" => "8",
"last_user_checked_at(3i)" => "12",
"last_user_checked_at(4i)" => "15",
"last_user_checked_at(5i)" => "45"
)
end
it "sets a multi-parameter Time attribute correctly" do
multi.last_user_checked_at.should eq(Time.zone.local(2010, 8, 12, 15, 45))
end
it "does not leave ugly attributes on the model" do
multi.attributes.should_not have_key("created_at(1i)")
end
end
end
end
# This class is needed since Mongoid doesn't support Multi-parameter
# attributes in version 4.0 before it's moved to active model in rails 4
#
# https://github.com/mongoid/mongoid/issues/2954
#
# encoding: utf-8
module Mongoid
# Adds Rails' multi-parameter attribute support to Mongoid.
#
# @todo: Durran: This module needs an overhaul.
module MultiParameterAttributes
module Errors
# Raised when an error occurred while doing a mass assignment to an
# attribute through the <tt>attributes=</tt> method. The exception
# has an +attribute+ property that is the name of the offending attribute.
class AttributeAssignmentError < Mongoid::Errors::MongoidError
attr_reader :exception, :attribute
def initialize(message, exception, attribute)
@exception = exception
@attribute = attribute
@message = message
end
end
# Raised when there are multiple errors while doing a mass assignment
# through the +attributes+ method. The exception has an +errors+
# property that contains an array of AttributeAssignmentError
# objects, each corresponding to the error while assigning to an
# attribute.
class MultiparameterAssignmentErrors < Mongoid::Errors::MongoidError
attr_reader :errors
def initialize(errors)
@errors = errors
end
end
end
# Process the provided attributes casting them to their proper values if a
# field exists for them on the document. This will be limited to only the
# attributes provided in the suppied +Hash+ so that no extra nil values get
# put into the document's attributes.
#
# @example Process the attributes.
# person.process_attributes(:title => "sir", :age => 40)
#
# @param [ Hash ] attrs The attributes to set.
#
# @since 2.0.0.rc.7
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
end
class Project
include Mongoid::Document
include Mongoid::MultiParameterAttributes
field :organization_uid, type: Integer
field :owner_id, type: Integer
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment