module Effective class Attribute attr_accessor :name, :type, :klass # This parses the written attributes def self.parse_written(input) input = input.to_s if (scanned = input.scan(/^\W*(\w+)\W*:(\w+)/).first).present? new(*scanned) end end # This kind of follows the rails GeneratedAttribute method. # In that case it will be initialized with a name and a type. # We also use this class to do value parsing in Datatables. # In that case it will be initialized with just a 'name' def initialize(obj, type = nil, klass: nil) @klass = klass if obj.present? && type.present? @name = obj.to_s @type = type.to_sym end @type ||= ( case obj when :boolean ; :boolean when :currency ; :currency when :date ; :date when :datetime ; :datetime when :decimal ; :decimal when :duration ; :duration when :email ; :email when :integer ; :integer when :percentage ; :percentage when :phone ; :phone when :price ; :price when :nil ; :nil when :resource ; :resource when :string ; :string when :text ; :text when :time ; :time when FalseClass ; :boolean when Fixnum ; :integer when Float ; :decimal when NilClass ; :nil when String ; :string when TrueClass ; :boolean when ActiveSupport::TimeWithZone ; :datetime when Date ; :date when ActiveRecord::Base ; :resource when :belongs_to ; :belongs_to when :belongs_to_polymorphic ; :belongs_to_polymorphic when :has_many ; :has_many when :has_and_belongs_to_many ; :has_and_belongs_to_many when :has_one ; :has_one when :effective_addresses ; :effective_addresses when :effective_obfuscation ; :effective_obfuscation when :effective_roles ; :effective_roles else raise "unsupported type for #{obj}" end ) end def parse(value, name: nil) case type when :boolean [true, 'true', 't', '1'].include?(value) when :date, :datetime if (digits = value.to_s.scan(/(\d+)/).flatten).present? date = Time.zone.local(*digits) name.to_s.start_with?('end_') ? date.end_of_day : date end when :time if (digits = value.to_s.scan(/(\d+)/).flatten).present? now = Time.zone.now Time.zone.local(now.year, now.month, now.day, *digits) end when :decimal, :currency (value.kind_of?(String) ? value.gsub(/[^0-9|\-|\.]/, '') : value).to_f when :duration if value.to_s.include?('h') (hours, minutes) = (value.to_s.gsub(/[^0-9|\-|h]/, '').split('h')) (hours.to_i * 60) + ((hours.to_i < 0) ? -(minutes.to_i) : minutes.to_i) else value.to_s.gsub(/[^0-9|\-|h]/, '').to_i end when :effective_obfuscation klass.respond_to?(:deobfuscate) ? klass.deobfuscate(value) : value.to_s when :effective_roles EffectiveRoles.roles.include?(value.to_sym) ? value : EffectiveRoles.roles_for(value) when :integer (value.kind_of?(String) ? value.gsub(/\D/, '') : value).to_i when :percentage # We want this to return 0.81 when we type 81% or 0.81 or 81.5 if value.to_s.include?('.') value = value.to_s.gsub(/[^0-9|\-|\.]/, '').to_f value >= 1.0 ? (value / 100.0) : value else (value.to_s.gsub(/\D/, '').to_f / 100.0) end when :phone digits = value.to_s.gsub(/\D/, '').chars digits = (digits.first == '1' ? digits[1..10] : digits[0..9]) # Throw away a leading 1 digits += ('0' * (10 - digits.length)).chars "(#{digits[0..2].join}) #{digits[3..5].join}-#{digits[6..10].join}" when :integer (value.kind_of?(String) ? value.gsub(/\D/, '') : value).to_i when :nil value.presence when :price (value.kind_of?(Integer) ? value : (value.to_s.gsub(/[^0-9|\-|\.]/, '').to_f * 100.0)).to_i when :string, :text, :email value.to_s when :belongs_to_polymorphic value.to_s when :belongs_to, :has_many, :has_and_belongs_to_many, :has_one, :resource, :effective_addresses # Returns an Array of ints, an Int or String if value.kind_of?(Integer) || value.kind_of?(Array) value else digits = value.to_s.gsub(/[^0-9|,]/, '') # '87' or '87,254,300' or 'something' if digits == value || digits.length == 10 if klass.respond_to?(:deobfuscate) digits.split(',').map { |str| klass.deobfuscate(str).to_i } else digits.split(',').map { |str| str.to_i } end else value.to_s end end else raise "unsupported type #{type}" end end def to_s name end def present? name.present? || type.present? end def human_name name.humanize end def <=>(other) name <=> other.name end def ==(other) name == other.name && type == other.type end end end