Skip to content
This repository has been archived by the owner on Jun 16, 2020. It is now read-only.

Commit

Permalink
Merge pull request #3 from joelmoss/master
Browse files Browse the repository at this point in the history
catch up with joel
  • Loading branch information
spemmons committed Mar 12, 2013
2 parents de8b942 + 84f8be5 commit e35ba35
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 89 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Gemfile.lock
*.sw?
.DS_Store
coverage
Expand All @@ -7,4 +8,4 @@ pkg
.#*
*.gem
/nbproject
.idea/
.idea/
2 changes: 1 addition & 1 deletion .rvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rvm use 1.9.3@bitmask_attributes --create --install
rvm 2.0.0
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ script: 'bundle exec rake test'
language: ruby
rvm:
- 1.9.3
- 2.0.0
57 changes: 57 additions & 0 deletions CHANGELOG.rdoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,58 @@
=== Version 0.4.0 - 2012-12-14 19:37:49 +0000

Adrian Gomez (1):
* Fixing issue #22

Brad Dunbar (1):
* Add default option.

Dominik Bamberger (5):
* Introduce raw bitmask setter.
* Ensure unsupported values for raw bitmasks raise an exception
* First reset instance variable then write attribute.
* Introduce inverse convenience class method
* Allow only valid entries when setting raw bitmask values directly

François DELFORT (2):
* Handle single table inheritance

Tee Parham (2):
* add table name scope to or_is_null_condition

alexanderd (1):
* Include table name in scope definitions


=== Version 0.3.0 - 2012-04-30 21:36:18 +0100

Aaron Hamid (1):
* wrap two scopes in procs to fix migration/startup issues

Andre Duffeck (1):
* fix values_for

Ivan Buiko (1):
* Adding retrieval by exact value

Sebastian Borrazas (1):
* Not raising exception when the bitmask column is not found, since its adding it on a migration

spemmons (22):
* detect whether or not nulls are possible with an attribute and remove OR conditions checking if it is null or not
* check for model not being ready when creating scopes
* support nulls with an explicit attribute, not by inspecting the model due to preloading issues
* support multiple values in the "without_..." scope that excludes match for of any of the bits; previously 2nd and following values were ignored
* DRY-up scope expressions, standardizing on "no_" and "bitmask_for_...(*values)" instead of repeating more complex calculations; also found another example of zykadelic's complaint with "with_any_" that is fixed and has tests
* add travis configuration file
* add :zero_value option and tests
* rename :zero to :zero_value for clarity

steve emmons (3):
* Merge pull request #1 from numerex/master
* Merge pull request #2 from numerex/master
* Merge pull request #15 from spemmons/master


=== Version 0.2.4 - 2011-11-23 09:37:50 +0000

Ivan Buiko (3):
Expand All @@ -9,3 +64,5 @@ Joel Moss (3):





48 changes: 0 additions & 48 deletions Gemfile.lock

This file was deleted.

2 changes: 1 addition & 1 deletion README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ will not change the attribute. For example:
In controller...
def update
@some_model = SomeModel.find(params[:id])
params[:some_attribute] ||= []
params[:some_model][:some_attribute] ||= []
@some_model.update_attributes(params)
end

Expand Down
34 changes: 28 additions & 6 deletions lib/bitmask_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,40 @@ def bitmask(attribute, options={}, &extension)
unless options[:as] && options[:as].kind_of?(Array)
raise ArgumentError, "Must provide an Array :as option"
end
bitmask_definitions[attribute] = Definition.new(attribute, options[:as].to_a,options[:null].nil? || options[:null],options[:zero_value],&extension)

if default = options[:default]
after_initialize do
send("#{attribute}=", default) unless send("#{attribute}?") || persisted?
end
end

bitmask_definitions[attribute] = Definition.new(attribute,
options[:as].to_a,
options[:null].nil? || options[:null],
options[:zero_value],
&extension)

bitmask_definitions[attribute].install_on(self)
end

def bitmask_definitions
@bitmask_definitions ||= {}
base_class.base_class_bitmask_definitions
end

def bitmasks
base_class.base_class_bitmasks
end

protected

def base_class_bitmask_definitions
@bitmask_definitions ||= {}
end

def base_class_bitmasks
@bitmasks ||= {}
end
end
end
end

ActiveRecord::Base.send :include, BitmaskAttributes
ActiveRecord::Base.send :include, BitmaskAttributes
78 changes: 54 additions & 24 deletions lib/bitmask_attributes/definition.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
module BitmaskAttributes
class Definition
attr_reader :attribute, :values, :allow_null, :zero_value, :extension
def initialize(attribute, values=[],allow_null = true,zero_value = nil, &extension)

def initialize(attribute, values=[], allow_null = true, zero_value = nil, &extension)
@attribute = attribute
@values = values
@extension = extension
@allow_null = allow_null
@zero_value = zero_value
end

def install_on(model)
validate_for model
generate_bitmasks_on model
Expand All @@ -29,40 +29,59 @@ def validate_for(model)
return if defined?(Rails) && Rails.configuration.cache_classes || !model.table_exists?

unless model.columns.detect { |col| col.name == attribute.to_s }
Rails.logger.warn "WARNING: `#{attribute}' is not an attribute of `#{model}'. But, it's ok if it happens during migrations and your \"bitmasked\" attribute is still not created."
missing_attribute(attribute, model)
end
end

def missing_attribute(attribute, model)
message = "WARNING: `#{attribute}' is not an attribute of `#{model.class.name}'. But, it's ok if it happens during migrations and your \"bitmasked\" attribute is still not created."
if defined?(Rails)
Rails.logger.warn message
else
STDERR.puts message
end
end

def generate_bitmasks_on(model)
model.bitmasks[attribute] = HashWithIndifferentAccess.new.tap do |mapping|
values.each_with_index do |value, index|
mapping[value] = 0b1 << index
end
end
end

def override(model)
override_getter_on(model)
override_setter_on(model)
end

def override_getter_on(model)
model.class_eval %(
def #{attribute}
@#{attribute} ||= BitmaskAttributes::ValueProxy.new(self, :#{attribute}, &self.class.bitmask_definitions[:#{attribute}].extension)
end
)
end

def override_setter_on(model)
model.class_eval %(
def #{attribute}=(raw_value)
values = raw_value.kind_of?(Array) ? raw_value : [raw_value]
self.#{attribute}.replace(values.reject{|value| #{eval_string_for_zero('value')}})
end
def #{attribute}_bitmask=(entry)
unless entry.is_a? Fixnum
raise ArgumentError, "Expected a Fixnum, but got: \#{entry.inspect}"
end
unless entry.between?(0, 2 ** (self.class.bitmasks[:#{attribute}].size - 1))
raise ArgumentError, "Unsupported value for #{attribute}: \#{entry.inspect}"
end
@#{attribute} = nil
self.send(:write_attribute, :#{attribute}, entry)
end
)
end

# Returns the defined values as an Array.
def create_attribute_methods_on(model)
model.class_eval %(
Expand All @@ -71,7 +90,7 @@ def self.values_for_#{attribute} # def self.values_for_numbers
end # end
)
end

def create_convenience_class_method_on(model)
model.class_eval %(
def self.bitmask_for_#{attribute}(*values)
Expand All @@ -84,13 +103,24 @@ def self.bitmask_for_#{attribute}(*values)
bitmask | bit
end
end
def self.#{attribute}_for_bitmask(entry)
unless entry.is_a? Fixnum
raise ArgumentError, "Expected a Fixnum, but got: \#{entry.inspect}"
end
self.bitmasks[:#{attribute}].inject([]) do |values, (value, bitmask)|
values.tap do
values << value.to_sym if (entry & bitmask > 0)
end
end
end
)
end

def create_convenience_instance_methods_on(model)
values.each do |value|
model.class_eval %(
def #{attribute}_for_#{value}?
def #{attribute}_for_#{value}?
self.#{attribute}?(:#{value})
end
)
Expand All @@ -107,29 +137,29 @@ def #{attribute}?(*values)
end
)
end

def create_scopes_on(model)
or_is_null_condition = " OR #{attribute} IS NULL" if allow_null
or_is_null_condition = " OR #{model.table_name}.#{attribute} IS NULL" if allow_null

model.class_eval %(
scope :with_#{attribute},
proc { |*values|
if values.blank?
where('#{attribute} > 0')
where('#{model.table_name}.#{attribute} > 0')
else
sets = values.map do |value|
mask = ::#{model}.bitmask_for_#{attribute}(value)
"#{attribute} & \#{mask} <> 0"
"#{model.table_name}.#{attribute} & \#{mask} <> 0"
end
where(sets.join(' AND '))
end
}
scope :without_#{attribute},
scope :without_#{attribute},
proc { |*values|
if values.blank?
no_#{attribute}
else
where("#{attribute} & ? = 0#{or_is_null_condition}", ::#{model}.bitmask_for_#{attribute}(*values))
where("#{model.table_name}.#{attribute} & ? = 0#{or_is_null_condition}", ::#{model}.bitmask_for_#{attribute}(*values))
end
}
Expand All @@ -138,27 +168,27 @@ def create_scopes_on(model)
if values.blank?
no_#{attribute}
else
where("#{attribute} = ?", ::#{model}.bitmask_for_#{attribute}(*values))
where("#{model.table_name}.#{attribute} = ?", ::#{model}.bitmask_for_#{attribute}(*values))
end
}
scope :no_#{attribute}, proc { where("#{attribute} = 0#{or_is_null_condition}") }
scope :no_#{attribute}, proc { where("#{model.table_name}.#{attribute} = 0#{or_is_null_condition}") }
scope :with_any_#{attribute},
proc { |*values|
if values.blank?
where('#{attribute} > 0')
where('#{model.table_name}.#{attribute} > 0')
else
where("#{attribute} & ? <> 0", ::#{model}.bitmask_for_#{attribute}(*values))
where("#{model.table_name}.#{attribute} & ? <> 0", ::#{model}.bitmask_for_#{attribute}(*values))
end
}
)
values.each do |value|
model.class_eval %(
scope :#{attribute}_for_#{value},
proc { where('#{attribute} & ? <> 0', ::#{model}.bitmask_for_#{attribute}(:#{value})) }
proc { where('#{model.table_name}.#{attribute} & ? <> 0', ::#{model}.bitmask_for_#{attribute}(:#{value})) }
)
end
end
end

def eval_string_for_zero(value_string)
Expand Down
2 changes: 1 addition & 1 deletion lib/bitmask_attributes/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module BitmaskAttributes
VERSION = "0.2.4".freeze
VERSION = "0.4.0".freeze
end
Loading

0 comments on commit e35ba35

Please sign in to comment.