bitmasker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Amiel Martin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ Bitmasker
2
+ =========
3
+
4
+ Bitmasker allows you to store many boolean values as one integer field in the database.
5
+
6
+ Synopsis
7
+ --------
8
+
9
+
10
+ ```ruby
11
+ class User < ActiveRecord::Base
12
+ has_bitmask_attributes :notifications do |config|
13
+ config.attribute :send_weekly_newsletter, 0b0001
14
+ config.attribute :send_monthly_newsletter, 0b0010, true
15
+ end
16
+ end
17
+ ```
18
+
19
+ Examples
20
+ --------
21
+
22
+ ```ruby
23
+ # in migration
24
+ t.integer :notifications_mask
25
+
26
+ # in app/models/user.rb
27
+ class User < ActiveRecord::Base
28
+ has_bitmask_attributes :notifications do |config|
29
+ config.attribute :send_weekly_newsletter, 0b0001
30
+ config.attribute :send_monthly_newsletter, 0b0010, true
31
+ config.accessible
32
+ # config.field_name :notifications_mask # <- default functionality
33
+ end
34
+ end
35
+ ```
36
+
37
+ this will define the following methods:
38
+ * `User#notifications` -- returns a BitmaskAttributes object representing all values
39
+ * `User#send_weekly_newsletter?` -- predicate
40
+ * `User#send_weekly_newsletter` -- works just like the predicate, makes it easy to use actionview form helpers
41
+ * `User#send_weekly_newsletter=(value)` -- just give it a boolean value (also takes "0" and "1" or "t" and "f" just like activerecord does for boolean fields)
42
+ * `User#send_monthly_newsletter?`
43
+ * `User#send_monthly_newsletter`
44
+ * `User#send_monthly_newsletter=(value)`
45
+
46
+ the call to `config.accessible` calls `attr_accessible :send_weekly_newsletter, :send_monthly_newsletter` in your model
47
+
48
+
49
+
50
+ View Example
51
+ ------------
52
+
53
+ ```erb
54
+ # in your view
55
+ <% form_for @user do |f| %>
56
+ Monthly Newsletter: <%= f.check_box :send_monthly_newsletter? %>
57
+ or
58
+ Monthly Newsletter
59
+ Yes: <%= f.radio_button :send_monthly_newsletter, 'true' %>
60
+ No: <%= f.radio_button :send_monthly_newsletter, 'false' %>
61
+ <% end %>
62
+ ```
63
+
64
+
65
+ Config Options
66
+ --------------
67
+
68
+ `config.attribute(name, mask, default = false)`
69
+
70
+ Sets up a binary attribute. Defines three functions: name, name?, and name=(true|false)
71
+ * `name` a symbol, Bitmasker will define
72
+ * `mask` example: 0b0000001, must be a power of 2
73
+ * `default` set to true if you want the attribute to default to true
74
+
75
+ `config.accessible`
76
+ if you are using attr_accessible in your model and you want to mass-assign your bitmask attributes, you will want to call this
77
+
78
+ `config.field_name(name)`
79
+ * `name` name of the field name in the database where all this info is stored, should be an integer
80
+
81
+
82
+ Copyright (c) 2012 Amiel Martin, released under the MIT license
83
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+
5
+ task default: :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ t.verbose = true
11
+ # t.warning = true
12
+ end
@@ -0,0 +1,73 @@
1
+ module Bitmasker
2
+ class BitmaskAttributes
3
+ include ActiveModel::AttributeMethods
4
+ attribute_method_suffix '?'
5
+ attribute_method_suffix '='
6
+ attribute_method_suffix '_was'
7
+
8
+ class_attribute :bitmask_attributes
9
+ class_attribute :defaults
10
+ class_attribute :model_class
11
+ class_attribute :field_name
12
+
13
+
14
+ def self.make(model_class, field_name, bitmask_attributes, defaults = {})
15
+ klass = Class.new(self) do
16
+ define_attribute_methods bitmask_attributes.keys
17
+ end
18
+
19
+ klass.model_class = model_class
20
+ klass.bitmask_attributes = bitmask_attributes.stringify_keys
21
+ klass.defaults = defaults.stringify_keys
22
+ klass.field_name = field_name
23
+
24
+ def klass.to_s
25
+ "#{superclass}(#{model_class}##{field_name})"
26
+ end
27
+
28
+ klass
29
+ end
30
+
31
+ def self.value_to_boolean(value)
32
+ model_class.value_to_boolean(value)
33
+ end
34
+
35
+ attr_reader :model, :bitmask
36
+ def initialize(model)
37
+ @model = model
38
+ @bitmask = Bitmask.new(bitmask_attributes, read || defaults)
39
+ end
40
+
41
+ def attribute(attribute)
42
+ bitmask.get attribute
43
+ end
44
+ alias_method :attribute?, :attribute
45
+
46
+ def attribute=(attribute, value)
47
+ bitmask.set attribute, self.class.value_to_boolean(value)
48
+ write
49
+ end
50
+
51
+ def attribute_was(attribute)
52
+ Bitmask.new(bitmask_attributes, was).get attribute
53
+ end
54
+
55
+ def to_a
56
+ bitmask.to_a
57
+ end
58
+
59
+ # Methods for the model
60
+
61
+ def read
62
+ model[field_name]
63
+ end
64
+
65
+ def write
66
+ model[field_name] = bitmask.to_i
67
+ end
68
+
69
+ def was
70
+ model.attribute_was field_name
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,51 @@
1
+ module Bitmasker
2
+ class Generator
3
+ def initialize(mask_name, model)
4
+ @bitmask_attributes = {}
5
+ @bitmask_defaults = {}
6
+
7
+ @model = model
8
+ @mask_name = mask_name
9
+ @field_name = mask_name.to_s + '_mask'
10
+
11
+ @use_attr_accessible = false
12
+ end
13
+
14
+
15
+ attr_writer :method_format
16
+ # makes the config dsl more consistent, allowing `config.method_format '%s'`
17
+ # instead of `config.method_format = '%s'`
18
+ alias_method :method_format, :method_format=
19
+
20
+
21
+ attr_writer :field_name
22
+ # makes the config dsl more consistent
23
+ alias_method :field_name, :field_name=
24
+
25
+ def attribute(name, mask, default = false)
26
+ @bitmask_attributes[name] = mask
27
+ @bitmask_defaults[name] = default
28
+ end
29
+
30
+ def accessible
31
+ @use_attr_accessible = true
32
+ end
33
+
34
+ def generate
35
+ klass = BitmaskAttributes.make(@model, @field_name, @bitmask_attributes, @bitmask_defaults)
36
+
37
+ @model.send :define_method, @mask_name do
38
+ klass.new(self)
39
+ end
40
+
41
+ @bitmask_attributes.each do |attribute, mask|
42
+ @model.delegate attribute, "#{attribute}?", "#{attribute}=",
43
+ "#{attribute}_was",
44
+ to: @mask_name
45
+
46
+ @model.attr_accessible attribute if @use_attr_accessible
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,18 @@
1
+ module Bitmasker
2
+ module Model
3
+ def has_bitmask_attributes(name)
4
+ raise ArgumentError, "You must pass has_bitmask_attributes a block and define attributes." unless block_given?
5
+ config = Generator.new(name, self)
6
+ yield config
7
+ config.generate
8
+ end
9
+
10
+ def value_to_boolean(value)
11
+ if defined? ::ActiveRecord::ConnectionAdapters::Column
12
+ ::ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
13
+ else
14
+ ['1', 1, 't', 'true', true].include? value
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module Bitmasker
2
+ class Railtie < ::Rails::Railtie
3
+ initializer 'bitmasker.activerecord_extensions' do |app|
4
+ ActiveRecord::Base.send :extend, Bitmasker::Model
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Bitmasker
2
+ VERSION = "0.1.0"
3
+ end
data/lib/bitmasker.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'bitmask'
2
+ require 'bitmasker/model'
3
+ require 'bitmasker/bitmask_attributes'
4
+ require 'bitmasker/generator'
5
+
6
+ require 'bitmasker/rails' if defined? ::Rails
7
+
@@ -0,0 +1,66 @@
1
+ require 'test_helper'
2
+
3
+ class Bitmasker::BitmaskAttributesTest < MiniTest::Unit::TestCase
4
+
5
+ MockModel = Class.new do
6
+ def self.value_to_boolean(value)
7
+ !!value
8
+ end
9
+
10
+ def [](attribute)
11
+ 0
12
+ end
13
+ end
14
+
15
+ def model_instance
16
+ @model_instance ||= MockModel.new
17
+ end
18
+
19
+ def setup
20
+ @klass = Bitmasker::BitmaskAttributes.make(
21
+ MockModel, 'email_mask',
22
+ send_weekly_email: 0b0001,
23
+ send_monthly_newsletter: 0b0010,
24
+ )
25
+ end
26
+
27
+ def subject
28
+ @subject ||= @klass.new(model_instance)
29
+ end
30
+
31
+ def test_klass_to_s
32
+ assert_equal "Bitmasker::BitmaskAttributes(Bitmasker::BitmaskAttributesTest::MockModel#email_mask)", @klass.to_s
33
+ end
34
+
35
+ def test_set_attribute
36
+ model_instance.expects(:[]=).with('email_mask', 1)
37
+ subject.send_weekly_email = true
38
+ assert_equal true, subject.send_weekly_email
39
+ end
40
+
41
+ def test_read_attribute
42
+ model_instance.expects(:[]).with('email_mask').returns(0)
43
+ assert_equal false, subject.send_weekly_email
44
+ end
45
+
46
+ def test_read_attribute_with_true_value
47
+ model_instance.expects(:[]).with('email_mask').returns(2)
48
+ assert_equal true, subject.send_monthly_newsletter
49
+ end
50
+
51
+ def test_predicate
52
+ model_instance.expects(:[]).with('email_mask').returns(2)
53
+ assert_equal true, subject.send_monthly_newsletter?
54
+ end
55
+
56
+ def test_was
57
+ model_instance.expects(:attribute_was).twice.with('email_mask').returns(1)
58
+ assert_equal false, subject.send_monthly_newsletter_was
59
+ assert_equal true, subject.send_weekly_email_was
60
+ end
61
+
62
+ def test_to_a
63
+ model_instance.expects(:[]).with('email_mask').returns(2)
64
+ assert_equal ['send_monthly_newsletter'], subject.to_a
65
+ end
66
+ end
@@ -0,0 +1,111 @@
1
+ require 'test_helper'
2
+
3
+ class MockModel
4
+
5
+ attr_accessor :dummy_mask
6
+ attr_accessor :another_dummy_mask
7
+ extend Bitmasker::Model
8
+
9
+ def []=(sym, value)
10
+ send "#{ sym }=", value
11
+ end
12
+
13
+ def [](sym)
14
+ send sym
15
+ end
16
+
17
+ def self.accessible_attrs
18
+ @@accessible_attrs
19
+ end
20
+
21
+ def accessible_attrs
22
+ @@accessible_attrs
23
+ end
24
+
25
+ def self.attr_accessible(*args)
26
+ @@accessible_attrs ||= []
27
+ @@accessible_attrs += args
28
+ end
29
+
30
+
31
+ has_bitmask_attributes :dummy do |config|
32
+ config.attribute :does_stuff, 0b0001
33
+ config.attribute :with_default, 0b0010, true
34
+ end
35
+
36
+ has_bitmask_attributes :another_dummy do |config|
37
+ config.attribute :an_accessible_attribute, 0b001
38
+ config.accessible
39
+ end
40
+
41
+ end
42
+
43
+
44
+ class BitmaskAttributesTest < MiniTest::Unit::TestCase
45
+
46
+ def test_does_stuff_attribute
47
+ mock = MockModel.new
48
+ assert mock.dummy
49
+ assert !mock.does_stuff?
50
+ mock.does_stuff = true
51
+ assert mock.does_stuff?
52
+ end
53
+
54
+ def test_default
55
+ mock = MockModel.new
56
+ assert mock.with_default?, "should have a default: mock.dummy_mask is #{ mock.dummy_mask.inspect } mock.dummy is #{ mock.dummy.inspect }"
57
+ mock.with_default = false
58
+ assert !mock.with_default?, 'setting method after default failed'
59
+ end
60
+
61
+ def test_predicate_without_?
62
+ mock = MockModel.new
63
+ mock.does_stuff = true
64
+ assert mock.does_stuff
65
+ end
66
+
67
+ def test_accessible
68
+ mock = MockModel.new
69
+ assert_equal [:an_accessible_attribute], mock.accessible_attrs
70
+ end
71
+
72
+
73
+ # def test_array_assignment
74
+ # mock = MockModel.new
75
+ # mock.dummy = ['does_stuff']
76
+ # assert mock.does_stuff
77
+ # assert !mock.with_default
78
+ # mock.dummy = ['does_stuff', 'with_default'] # should accept strings
79
+ # assert mock.does_stuff
80
+ # assert mock.with_default
81
+ # end
82
+
83
+ # def test_empty_array_assignment
84
+ # mock = MockModel.new
85
+ # mock.dummy = []
86
+ # assert !mock.does_stuff
87
+ # assert !mock.with_default
88
+ # end
89
+
90
+ # def test_array_assignment_with_empty_strings
91
+ # mock = MockModel.new
92
+ # mock.dummy = ['', 'does_stuff']
93
+ # assert mock.does_stuff
94
+ # assert !mock.with_default
95
+ # end
96
+
97
+ # not throwing exception because you can't run migrations when it does
98
+ # def test_raises_without_field
99
+ # assert_raise ArgumentError do
100
+ # eval '
101
+ # class MockModel
102
+ # extend ::BitmaskAttributes
103
+ #
104
+ # has_bitmask_attributes :without_field do |config|
105
+ # config.attribute :none, 0b001
106
+ # end
107
+ # end
108
+ # '
109
+ # end
110
+ # end
111
+ end
@@ -0,0 +1,9 @@
1
+ require 'minitest/autorun'
2
+ require 'turn/autorun'
3
+
4
+ require 'mocha/setup'
5
+
6
+ require 'active_support/all'
7
+ require 'active_record'
8
+
9
+ require 'bitmasker'
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitmasker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Amiel Martin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bitmask
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.1.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: activerecord
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '3.0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activesupport
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: activemodel
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: i18n
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: ! "\n Bitmasker allows you to store many boolean values as one integer
95
+ field in the database.\n "
96
+ email:
97
+ - amiel@carnesmedia.com
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - lib/bitmasker/bitmask_attributes.rb
103
+ - lib/bitmasker/generator.rb
104
+ - lib/bitmasker/model.rb
105
+ - lib/bitmasker/rails.rb
106
+ - lib/bitmasker/version.rb
107
+ - lib/bitmasker.rb
108
+ - MIT-LICENSE
109
+ - Rakefile
110
+ - README.md
111
+ - test/bitmasker/bitmask_attributes_test.rb
112
+ - test/integration_test.rb
113
+ - test/test_helper.rb
114
+ homepage: https://github.com/amiel/bitmasker
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 1.8.23
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Bitmasker allows you to store many boolean values as one integer field in
138
+ the database.
139
+ test_files:
140
+ - test/bitmasker/bitmask_attributes_test.rb
141
+ - test/integration_test.rb
142
+ - test/test_helper.rb