lib/bitmask_attribute.rb in bitmask-attribute-1.0.0 vs lib/bitmask_attribute.rb in bitmask-attribute-1.1.0
- old
+ new
@@ -13,20 +13,27 @@
def install_on(model)
validate_for model
generate_bitmasks_on model
override model
- create_convenience_method_on model
+ create_convenience_class_method_on(model)
+ create_convenience_instance_methods_on(model)
+ create_named_scopes_on(model)
end
#######
private
#######
def validate_for(model)
- unless model.columns.detect { |col| col.name == attribute.to_s && col.type == :integer }
- raise ArgumentError, "`#{attribute}' is not an integer column of `#{model}'"
+ # The model cannot be validated if it is preloaded and the attribute/column is not in the
+ # database (the migration has not been run). This usually
+ # occurs in the 'test' and 'production' environments.
+ return if defined?(Rails) && Rails.configuration.cache_classes
+
+ unless model.columns.detect { |col| col.name == attribute.to_s }
+ raise ArgumentError, "`#{attribute}' is not an attribute of `#{model}'"
end
end
def generate_bitmasks_on(model)
model.bitmasks[attribute] = returning HashWithIndifferentAccess.new do |mapping|
@@ -51,16 +58,16 @@
def override_setter_on(model)
model.class_eval %(
def #{attribute}=(raw_value)
values = raw_value.kind_of?(Array) ? raw_value : [raw_value]
- #{attribute}.replace(values)
+ self.#{attribute}.replace(values.reject(&:blank?))
end
)
end
- def create_convenience_method_on(model)
+ def create_convenience_class_method_on(model)
model.class_eval %(
def self.bitmask_for_#{attribute}(*values)
values.inject(0) do |bitmask, value|
unless (bit = bitmasks[:#{attribute}][value])
raise ArgumentError, "Unsupported value for #{attribute}: \#{value.inspect}"
@@ -68,11 +75,58 @@
bitmask | bit
end
end
)
end
+
+
+ def create_convenience_instance_methods_on(model)
+ values.each do |value|
+ model.class_eval %(
+ def #{attribute}_for_#{value}?
+ self.#{attribute}?(:#{value})
+ end
+ )
+ end
+ model.class_eval %(
+ def #{attribute}?(*values)
+ if !values.blank?
+ values.all? do |value|
+ self.#{attribute}.include?(value)
+ end
+ else
+ self.#{attribute}.present?
+ end
+ end
+ )
+ end
+ def create_named_scopes_on(model)
+ model.class_eval %(
+ named_scope :with_#{attribute},
+ proc { |*values|
+ if values.blank?
+ {:conditions => '#{attribute} > 0 OR #{attribute} IS NOT NULL'}
+ else
+ sets = values.map do |value|
+ mask = #{model}.bitmask_for_#{attribute}(value)
+ "#{attribute} & \#{mask} <> 0"
+ end
+ {:conditions => sets.join(' AND ')}
+ end
+ }
+ named_scope :without_#{attribute}, :conditions => "#{attribute} == 0 OR #{attribute} IS NULL"
+ named_scope :no_#{attribute}, :conditions => "#{attribute} == 0 OR #{attribute} IS NULL"
+ )
+ values.each do |value|
+ model.class_eval %(
+ named_scope :#{attribute}_for_#{value},
+ :conditions => ['#{attribute} & ? <> 0', #{model}.bitmask_for_#{attribute}(:#{value})]
+ )
+ end
+ end
+
end
def self.included(model)
model.extend ClassMethods
end
@@ -95,6 +149,6 @@
@bitmasks ||= {}
end
end
-end
\ No newline at end of file
+end