-
+
+
- + MODULE + BitmaskAttributes::ClassMethods + + +
- + CLASS + BitmaskAttributes::Definition + + +
- + CLASS + BitmaskAttributes::ValueProxy + + +
VERSION | += | +"0.2.3".freeze | +
+ | + |
diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc new file mode 100644 index 0000000..e69de29 diff --git a/Gemfile b/Gemfile index 80e89f5..9cccfe6 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ if RUBY_VERSION < '1.9' gem "ruby-debug", ">= 0.10.3" end +gem 'sdoc' gem 'rake' gem 'shoulda' gem 'sqlite3', '>=1.3.4' diff --git a/Gemfile.lock b/Gemfile.lock index 2c1d7cf..5c1aa01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -22,8 +22,14 @@ GEM arel (2.2.1) builder (3.0.0) i18n (0.6.0) + json (1.6.1) multi_json (1.0.3) rake (0.9.2.2) + rdoc (3.11) + json (~> 1.4) + sdoc (0.3.16) + json (>= 1.1.3) + rdoc (~> 3.10) shoulda (2.11.3) sqlite3 (1.3.4) turn (0.8.3) @@ -36,6 +42,7 @@ PLATFORMS DEPENDENCIES bitmask_attributes! rake + sdoc shoulda sqlite3 (>= 1.3.4) turn diff --git a/README.md b/README.rdoc similarity index 84% rename from README.md rename to README.rdoc index ac7ac7c..211fd0d 100644 --- a/README.md +++ b/README.rdoc @@ -1,46 +1,45 @@ -BitmaskAttributes -================= +== BitmaskAttributes Transparent manipulation of bitmask attributes for ActiveRecord, based on the bitmask-attribute gem, which has been dormant since 2009. This updated gem work with Rails 3 and up (including Rails 3.1). -Installation ------------- + +=== Installation The best way to install is with RubyGems: $ [sudo] gem install bitmask_attributes - + Or better still, just add it to your Gemfile: gem 'bitmask_attributes' -Example -------- + +=== Example Simply declare an existing integer column as a bitmask with its possible values. class User < ActiveRecord::Base - bitmask :roles, :as => [:writer, :publisher, :editor, :proofreader] + bitmask :roles, :as => [:writer, :publisher, :editor, :proofreader] end - + You can then modify the column using the declared values without resorting to manual bitmasks. - + user = User.create(:name => "Bruce", :roles => [:publisher, :editor]) user.roles # => [:publisher, :editor] user.roles << :writer user.roles # => [:publisher, :editor, :writer] - + It's easy to find out if a record has a given value: user.roles?(:editor) # => true - + You can check for multiple values (uses an `and` boolean): user.roles?(:editor, :publisher) @@ -58,8 +57,8 @@ You can get the list of values for any given attribute: User.values_for_roles # => [:writer, :publisher, :editor, :proofreader] -Named Scopes ------------- + +=== Named Scopes A couple useful named scopes are also generated when you use `bitmask`: @@ -87,8 +86,8 @@ Find records without a specific attribute. Note that "without_" only supports a single attribute argument, and the "no_" method does not support arguments. -Adding Methods --------------- + +=== Adding Methods You can add your own methods to the bitmasked attributes (similar to named scopes): @@ -104,15 +103,14 @@ named scopes): # => true -Warning: Modifying possible values ----------------------------------- +=== Warning: Modifying possible values IMPORTANT: Once you have data using a bitmask, don't change the order of the values, remove any values, or insert any new values in the `:as` array anywhere except at the end. You won't like the results. -Contributing ------------- + +=== Contributing 1. Fork it. 2. Create a branch (`git checkout -b new-feature`) @@ -120,19 +118,20 @@ Contributing 4. Run the tests (`bundle install` then `bundle exec rake`) 5. Commit your changes (`git commit -am "Created new feature"`) 6. Push to the branch (`git push origin new-feature`) -7. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch. +7. Create a {pull request}[http://help.github.com/send-pull-requests/] from your branch. 8. Promote it. Get others to drop in and +1 it. -Credits -------- -Thanks to [Bruce Williams](https://github.com/bruce) and the following contributors +=== Credits + +Thanks to {Bruce Williams}[https://github.com/bruce] and the following contributors of the bitmask-attribute plugin: -* [Jason L Perry](http://github.com/ambethia) -* [Nicolas Fouché](http://github.com/nfo) +* {Jason L Perry}[http://github.com/ambethia] +* {Nicolas Fouché}[http://github.com/nfo] +* {Ivan Buiko}[http://github.com/IvanBuiko] + -Copyright ---------- +=== Copyright Copyright (c) 2007-2009 Bruce Williams & 2011 Joel Moss. See LICENSE for details. diff --git a/Rakefile b/Rakefile index f1e7b29..bfeeede 100644 --- a/Rakefile +++ b/Rakefile @@ -35,6 +35,20 @@ def gem_file "#{name}-#{version}.gem" end +require 'rdoc/task' +require 'sdoc' +Rake::RDocTask.new do |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = "#{name} #{version}" + rdoc.options << '-f' << 'sdoc' + rdoc.options << '-a' + rdoc.options << '--markup' << 'tomdoc' + rdoc.options << '-m' << 'README.rdoc' + rdoc.rdoc_files.include('CHANGELOG*') + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end + ############################################################################# # @@ -98,7 +112,6 @@ EOF load "lib/#{name}/version.rb" file = "CHANGELOG.rdoc" old = File.read(file) - version = Deals::VERSION message = "Bumping to version #{version}" File.open(file, "w") do |f| @@ -110,9 +123,9 @@ EOF EOF end - exec "#{ENV["EDITOR"]} #{file}" Rake::Task['build'].invoke - exec ["git commit -aqm '#{message}'", + exec ["#{ENV["EDITOR"]} #{file}", + "git commit -aqm '#{message}'", "git tag -a -m '#{message}' v#{version}", "git push origin master", "git push origin $(git tag | tail -1)", diff --git a/doc/apple-touch-icon.png b/doc/apple-touch-icon.png new file mode 100644 index 0000000..50f98b0 Binary files /dev/null and b/doc/apple-touch-icon.png differ diff --git a/doc/classes/BitmaskAttributes.html b/doc/classes/BitmaskAttributes.html new file mode 100644 index 0000000..3105b67 --- /dev/null +++ b/doc/classes/BitmaskAttributes.html @@ -0,0 +1,114 @@ + + + +
+VERSION | += | +"0.2.3".freeze | +
+ | + |
+ Source: + show + +
+# File lib/bitmask_attributes.rb, line 8 +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, &extension) + bitmask_definitions[attribute].install_on(self) +end+
+ Source: + show + +
+# File lib/bitmask_attributes.rb, line 16 +def bitmask_definitions + @bitmask_definitions ||= {} +end+
+ Source: + show + +
+# File lib/bitmask_attributes.rb, line 20 +def bitmasks + @bitmasks ||= {} +end+
+ [R] + | +attribute | ++ |
+ [R] + | +extension | ++ |
+ [R] + | +values | ++ |
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 5 +def initialize(attribute, values=[], &extension) + @attribute = attribute + @values = values + @extension = extension +end+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 11 +def install_on(model) + validate_for model + generate_bitmasks_on model + override model + create_convenience_class_method_on model + create_convenience_instance_methods_on model + create_scopes_on model + create_attribute_methods_on model +end+
Returns the defined values as an Array.
++ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 65 +def create_attribute_methods_on(model) + model.class_eval %Q( + def self.values_for_#{attribute} # def self.values_for_numbers + #{values} # [:one, :two, :three] + end # end + ) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 73 +def create_convenience_class_method_on(model) + model.class_eval %Q( + 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}" + end + bitmask | bit + end + end + ) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 86 +def create_convenience_instance_methods_on(model) + values.each do |value| + model.class_eval %Q( + def #{attribute}_for_#{value}? + self.#{attribute}?(:#{value}) + end + ) + end + model.class_eval %Q( + def #{attribute}?(*values) + if !values.blank? + values.all? do |value| + self.#{attribute}.include?(value) + end + else + self.#{attribute}.present? + end + end + ) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 107 +def create_scopes_on(model) + model.class_eval %Q( + scope :with_#{attribute}, + proc { |*values| + if values.blank? + where('#{attribute} > 0 OR #{attribute} IS NOT NULL') + else + sets = values.map do |value| + mask = #{model}.bitmask_for_#{attribute}(value) + "#{attribute} & \#{mask} <> 0" + end + where(sets.join(' AND ')) + end + } + scope :without_#{attribute}, + proc { |value| + if value + mask = #{model}.bitmask_for_#{attribute}(value) + where("#{attribute} IS NULL OR #{attribute} & ? = 0", mask) + else + where("#{attribute} IS NULL OR #{attribute} = 0") + end + } + + scope :no_#{attribute}, where("#{attribute} = 0 OR #{attribute} IS NULL") + + scope :with_any_#{attribute}, + proc { |*values| + if values.blank? + where('#{attribute} > 0 OR #{attribute} IS NOT NULL') + else + sets = values.map do |value| + mask = #{model}.bitmask_for_#{attribute}(value) + "#{attribute} & \#{mask} <> 0" + end + where(sets.join(' OR ')) + end + } + ) + values.each do |value| + model.class_eval %Q( + scope :#{attribute}_for_#{value}, + where('#{attribute} & ? <> 0', #{model}.bitmask_for_#{attribute}(:#{value})) + ) + end +end+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 34 +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+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 42 +def override(model) + override_getter_on(model) + override_setter_on(model) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 47 +def override_getter_on(model) + model.class_eval %Q( + def #{attribute} + @#{attribute} ||= BitmaskAttributes::ValueProxy.new(self, :#{attribute}, &self.class.bitmask_definitions[:#{attribute}].extension) + end + ) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 55 +def override_setter_on(model) + model.class_eval %Q( + def #{attribute}=(raw_value) + values = raw_value.kind_of?(Array) ? raw_value : [raw_value] + self.#{attribute}.replace(values.reject(&:blank?)) + end + ) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/definition.rb, line 23 +def validate_for(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) or table doesn't exist. This usually + # occurs in the 'test' and 'production' environment or during migration. + return if defined?(Rails) && Rails.configuration.cache_classes || !model.table_exists? + + unless model.columns.detect { |col| col.name == attribute.to_s } + raise ArgumentError, "`#{attribute}' is not an attribute of `#{model}'" + end +end+
+ Source: + show + +
+# File lib/bitmask_attributes/value_proxy.rb, line 4 +def initialize(record, attribute, &extension) + @record = record + @attribute = attribute + find_mapping + instance_eval(&extension) if extension + super(extract_values) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/value_proxy.rb, line 27 +def to_i + inject(0) { |memo, value| memo | @mapping[value] } +end+
+ Source: + show + +
+# File lib/bitmask_attributes/value_proxy.rb, line 58 +def extract_values + stored = [@record.send(:read_attribute, @attribute) || 0, 0].max + @mapping.inject([]) do |values, (value, bitmask)| + values.tap do + values << value.to_sym if (stored & bitmask > 0) + end + end +end+
+ Source: + show + +
+# File lib/bitmask_attributes/value_proxy.rb, line 67 +def find_mapping + unless (@mapping = @record.class.bitmasks[@attribute]) + raise ArgumentError, "Could not find mapping for bitmask attribute :#{@attribute}" + end +end+
+ Source: + show + +
+# File lib/bitmask_attributes/value_proxy.rb, line 54 +def serialize! + @record.send(:write_attribute, @attribute, to_i) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/value_proxy.rb, line 43 +def symbolize! + orig_replace(map(&:to_sym)) +end+
+ Source: + show + +
+# File lib/bitmask_attributes/value_proxy.rb, line 47 +def updated! + validate! + symbolize! + uniq! + serialize! +end+
+ Source: + show + +
+# File lib/bitmask_attributes/value_proxy.rb, line 33 +def validate! + each do |value| + if @mapping.key? value + true + else + raise ArgumentError, "Unsupported value for `#{@attribute}': #{value.inspect}" + end + end +end+
Transparent manipulation of bitmask attributes for ActiveRecord, based on +the bitmask-attribute gem, which has been dormant since 2009. This updated +gem work with Rails 3 and up (including Rails 3.1).
+ +The best way to install is with RubyGems:
+ +$ [sudo] gem install bitmask_attributes+ +
Or better still, just add it to your Gemfile:
+ +gem 'bitmask_attributes'+ +
Simply declare an existing integer column as a bitmask with its possible +values.
+ +class User < ActiveRecord::Base + bitmask :roles, :as => [:writer, :publisher, :editor, :proofreader] +end ++ +
You can then modify the column using the declared values without resorting +to manual bitmasks.
+ +user = User.create(:name => "Bruce", :roles => [:publisher, :editor]) +user.roles +# => [:publisher, :editor] +user.roles << :writer +user.roles +# => [:publisher, :editor, :writer] ++ +
It’s easy to find out if a record has a given value:
+ +user.roles?(:editor) +# => true ++ +
You can check for multiple values (uses an `and` boolean):
+ +user.roles?(:editor, :publisher) +# => true +user.roles?(:editor, :proofreader) +# => false ++ +
Or, just check if any values are present:
+ +user.roles? +# => true ++ +
You can get the list of values for any given attribute:
+ +User.values_for_roles +# => [:writer, :publisher, :editor, :proofreader] ++ +
A couple useful named scopes are also generated when you use `bitmask`:
+ +User.with_roles +# => (all users with roles) +User.with_roles(:editor) +# => (all editors) +User.with_roles(:editor, :writer) +# => (all users who are BOTH editors and writers) +User.with_any_roles(:editor, :writer) +# => (all users who are editors OR writers) ++ +
Find records without any bitmask set:
+ +User.without_roles +# => (all users without a role) +User.no_roles +# => (all users without a role) ++ +
Find records without a specific attribute.
+ +User.without_roles(:editor) +# => (all users who are not editors) ++ +
Note that “without_” only supports a single attribute argument, and the +“no_” method does not support arguments.
+ +You can add your own methods to the bitmasked attributes (similar to named +scopes):
+ +bitmask :other_attribute, :as => [:value1, :value2] do + def worked? + true + end +end + +user = User.first +user.other_attribute.worked? +# => true ++ +
IMPORTANT: Once you have data using a bitmask, don’t change the order of +the values, remove any values, or insert any new values in the `:as` array +anywhere except at the end. You won’t like the results.
+ +Fork it.
+Create a branch (`git checkout -b new-feature`)
+Make your changes
+Run the tests (`bundle install` then `bundle exec rake`)
+Commit your changes (`git commit -am “Created new feature”`)
+Push to the branch (`git push origin new-feature`)
+Create a pull +request from your branch.
+Promote it. Get others to drop in and +1 it.
+Thanks to Bruce Williams and the +following contributors of the bitmask-attribute plugin:
+Copyright © 2007-2009 Bruce Williams & 2011 Joel Moss. See LICENSE for +details.
+ +"+w.value+"
";t=v.firstChild.firstChild;v.firstChild.cN=q.cN;q.parentNode.replaceChild(v.firstChild,q)}else{t.innerHTML=w.value}t.className=s;t.dataset={};t.dataset.result={language:w.language,kw:w.keyword_count,re:w.r};if(x&&x.language){t.dataset.second_best={language:x.language,kw:x.keyword_count,re:x.r}}}function j(){if(j.called){return}j.called=true;e();var q=document.getElementsByTagName("pre");for(var o=0;o+ + |