Skip to content

Instantly share code, notes, and snippets.

@sj26
Last active March 31, 2017 17:21
Show Gist options
  • Save sj26/767b29f11fed8a4d13c5 to your computer and use it in GitHub Desktop.
Save sj26/767b29f11fed8a4d13c5 to your computer and use it in GitHub Desktop.
Virtus::MultiparameterAttributes (extracted as gem: https://github.com/sj26/virtus-multiparams)
# Rails datetime_select and similar use multiparameter attributes which are
# these dawful things from the bowels of activerecord and actionpack. This
# module extends virtus models to coerce multiparameter attributes back together
# before assigning attributes.
#
# Here's the implementation for ActiveRecord:
#
# https://github.com/rails/rails/blob/11fd052aa815ae0255ea5b2463e88138fb3fec61/activerecord/lib/active_record/attribute_assignment.rb#L113-L218
#
# and DataMapper:
#
# https://github.com/datamapper/dm-rails/blob/5f3fb01a9d5f5adb17665a3bdabffd396d722e61/lib/dm-rails/multiparameter_attributes.rb
#
module Virtus::MultiparameterAttributes
extend ActiveSupport::Concern
included do
attribute_set.singleton_class.send(:include, AttributeSetExtension)
end
module AttributeSetExtension
extend ActiveSupport::Concern
included do
alias_method_chain :coerce, :multiparameter
end
def coerce_with_multiparameter(attributes)
coerce_without_multiparameter(attributes).tap do |attributes|
multiparams = Hash.new { |hash, key| hash[key] = {} }
attributes.each do |(key, value)|
if /\A(?<name>.+)\((?<offset>[0-9]+)(?<cast>[if])?\)\Z/ =~ key
attributes.delete(key)
unless attributes.include? name
offset = offset.to_i
value = value.send("to_#{cast}") if cast
multiparams[name][offset] = value
end
end
end
multiparams.each do |name, values|
array = Array.new(values.keys.max)
values.each do |offset, value|
array[offset - 1] = value
end
# Virus::Attribute has a primitive type which might be Date, Time, DateTime, etc.
attribute = self[name]
attributes[name] = if attribute.primitive <= Date || attribute.primitive <= Time
# Basic convesion is enough, Virtus invokes `to_date[time]`
# Also, lololol timezones
Time.new(*array[0...6]) unless array[0...3].any?(&:blank?)
else
array
end
end
end
end
end
end
@dgilperez
Copy link

Thanks for sharing this! How do you use it, after including the module in the virtus-powered class? Thanks!

@sj26
Copy link
Author

sj26 commented May 7, 2015

I've release this as a gem with usage example:

https://github.com/sj26/virtus-multiparams

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment