Last active
March 31, 2017 17:21
-
-
Save sj26/767b29f11fed8a4d13c5 to your computer and use it in GitHub Desktop.
Virtus::MultiparameterAttributes (extracted as gem: https://github.com/sj26/virtus-multiparams)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
I've release this as a gem with usage example:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for sharing this! How do you use it, after including the module in the virtus-powered class? Thanks!