bitmasker 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -16,6 +16,10 @@ Synopsis
16
16
  end
17
17
  ```
18
18
 
19
+ [![Code Climate](https://codeclimate.com/github/amiel/bitmasker.png)](https://codeclimate.com/github/amiel/bitmasker)
20
+ [![Build Status](https://travis-ci.org/amiel/bitmasker.png)](https://travis-ci.org/amiel/bitmasker)
21
+
22
+
19
23
  Examples
20
24
  --------
21
25
 
@@ -34,7 +38,8 @@ Examples
34
38
  end
35
39
  ```
36
40
 
37
- this will define the following methods:
41
+ This will define the following methods:
42
+
38
43
  * `User#notifications` -- returns a BitmaskAttributes object representing all values
39
44
  * `User#send_weekly_newsletter?` -- predicate
40
45
  * `User#send_weekly_newsletter` -- works just like the predicate, makes it easy to use actionview form helpers
@@ -43,8 +48,28 @@ this will define the following methods:
43
48
  * `User#send_monthly_newsletter`
44
49
  * `User#send_monthly_newsletter=(value)`
45
50
 
46
- the call to `config.accessible` calls `attr_accessible :send_weekly_newsletter, :send_monthly_newsletter` in your model
51
+ the call to `config.accessible` calls `attr_accessible :send_weekly_newsletter, :send_monthly_newsletter` in your model.
52
+
53
+ ### Scopes
54
+
55
+ Bitmasker also sets up a few useful scopes:
47
56
 
57
+ * `User#with_notifications`
58
+ * `User#without_notifications`
59
+ * `User#with_any_notifications`
60
+
61
+ #### Scopes Examples
62
+
63
+ ```ruby
64
+ # Users that want weekly newsletter
65
+ User.with_notifications(:send_weekly_newsletter)
66
+
67
+ # Users that want monthly AND weekly newsletters
68
+ User.with_notifications(:send_monthly_newsletter, :send_weekly_newsletter)
69
+
70
+ # Users that want monthly OR weekly newsletters
71
+ User.with_any_notifications(:send_monthly_newsletter, :send_weekly_newsletter)
72
+ ```
48
73
 
49
74
 
50
75
  View Example
@@ -79,5 +104,37 @@ if you are using attr_accessible in your model and you want to mass-assign your
79
104
  * `name` name of the field name in the database where all this info is stored, should be an integer
80
105
 
81
106
 
107
+ Updating from `has_bitmask_attributes`
108
+ --------------------------------------
109
+
110
+ If you used the `method_format` feature from `has_bitmask_attributes`, you will need to change
111
+ your configuration as `method_format` has been removed.
112
+
113
+ ### Before
114
+
115
+ ```ruby
116
+ # in app/models/user.rb
117
+ class User < ActiveRecord::Base
118
+ has_bitmask_attributes :notifications do |config|
119
+ config.attribute :weekly_newsletter, 0b0001
120
+ config.attribute :monthly_newsletter, 0b0010, true
121
+ config.method_format 'send_%s'
122
+ end
123
+ end
124
+ ```
125
+
126
+ ### After
127
+
128
+ ```ruby
129
+ # in app/models/user.rb
130
+ class User < ActiveRecord::Base
131
+ has_bitmask_attributes :notifications do |config|
132
+ config.attribute :send_weekly_newsletter, 0b0001
133
+ config.attribute :send_monthly_newsletter, 0b0010, true
134
+ end
135
+ end
136
+ ```
137
+
138
+
82
139
  Copyright (c) 2012 Amiel Martin, released under the MIT license
83
140
 
@@ -0,0 +1,61 @@
1
+ module Bitmasker
2
+ class BitmaskScope
3
+ include ActiveModel::AttributeMethods
4
+ attribute_method_prefix 'with_'
5
+ attribute_method_prefix 'with_any_'
6
+ attribute_method_prefix 'without_'
7
+
8
+ class_attribute :model_class
9
+ class_attribute :field_name
10
+ class_attribute :mask_name
11
+ class_attribute :bitmask_attributes
12
+
13
+ def self.make(model_class, field_name, mask_name, bitmask_attributes)
14
+ klass = Class.new(self) do
15
+ define_attribute_methods [mask_name]
16
+
17
+ def self.to_s
18
+ "#{superclass}(#{model_class}##{field_name})"
19
+ end
20
+ end
21
+
22
+ klass.model_class = model_class
23
+ klass.field_name = field_name
24
+ klass.mask_name = mask_name
25
+ klass.bitmask_attributes = bitmask_attributes.stringify_keys
26
+
27
+ klass
28
+ end
29
+
30
+ def bitmask
31
+ Bitmask.new(bitmask_attributes, 0)
32
+ end
33
+
34
+ # REVIEW: This (the unused _ attribute) tells me I have the design wrong
35
+ def with_attribute(_, *attributes)
36
+ # TODO: Test lots of databases
37
+ bitmask_query attributes, "#{field_name} & :mask = :mask"
38
+ end
39
+
40
+ def with_any_attribute(_, *attributes)
41
+ # TODO: Test lots of databases
42
+ bitmask_query attributes, "#{field_name} & :mask <> 0"
43
+ end
44
+
45
+ def without_attribute(_, *attributes)
46
+ # TODO: Test lots of databases
47
+ bitmask_query attributes, "#{field_name} & :mask = 0 OR #{field_name} IS NULL"
48
+ end
49
+
50
+ private
51
+
52
+ def bitmask_query(attributes, query)
53
+ mask = bitmask
54
+ mask.set_array(Array.wrap(attributes).flatten.map(&:to_s))
55
+
56
+ # TODO: Test lots of databases
57
+ model_class.where(query, mask: mask.to_i)
58
+ end
59
+
60
+ end
61
+ end
@@ -8,6 +8,8 @@ module Bitmasker
8
8
  @mask_name = mask_name
9
9
  @field_name = mask_name.to_s + '_mask'
10
10
 
11
+ @scope_name = mask_name.to_s + '_scope'
12
+
11
13
  @use_attr_accessible = false
12
14
  end
13
15
 
@@ -33,14 +35,22 @@ module Bitmasker
33
35
 
34
36
  def generate
35
37
  klass = BitmaskAttributes.make(@model, @field_name, @bitmask_attributes, @bitmask_defaults)
38
+ scope_klass = BitmaskScope.make(@model, @field_name, @mask_name, @bitmask_attributes)
36
39
 
37
40
  @model.send :define_method, @mask_name do
38
41
  klass.new(self)
39
42
  end
40
43
 
44
+ @model.singleton_class.send :define_method, @scope_name do
45
+ scope_klass.new
46
+ end
47
+
48
+ @model.singleton_class.delegate "with_#{@mask_name}",
49
+ "without_#{@mask_name}", "with_any_#{@mask_name}",
50
+ to: @scope_name
51
+
41
52
  @bitmask_attributes.each do |attribute, mask|
42
- @model.delegate attribute, "#{attribute}?", "#{attribute}=",
43
- "#{attribute}_was",
53
+ @model.delegate attribute, "#{attribute}?", "#{attribute}=", "#{attribute}_was",
44
54
  to: @mask_name
45
55
 
46
56
  @model.attr_accessible attribute if @use_attr_accessible
@@ -1,3 +1,3 @@
1
1
  module Bitmasker
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/bitmasker.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'bitmask'
2
2
  require 'bitmasker/model'
3
3
  require 'bitmasker/bitmask_attributes'
4
+ require 'bitmasker/bitmask_scope'
4
5
  require 'bitmasker/generator'
5
6
 
6
7
  require 'bitmasker/rails' if defined? ::Rails
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class Bitmasker::BitmaskAttributesTest < MiniTest::Unit::TestCase
3
+ class Bitmasker::BitmaskAttributesTest < MiniTest::Test
4
4
 
5
5
  MockModel = Class.new do
6
6
  def self.value_to_boolean(value)
@@ -0,0 +1,53 @@
1
+ require 'test_helper'
2
+
3
+ class Bitmasker::BitmaskScopeTest < MiniTest::Test
4
+
5
+ MockModel = Class.new
6
+
7
+ def model_instance
8
+ @model_instance ||= MockModel.new
9
+ end
10
+
11
+ def setup
12
+ @klass = Bitmasker::BitmaskScope.make(
13
+ MockModel, 'email_mask', 'emails',
14
+ send_weekly_email: 0b0001,
15
+ send_monthly_newsletter: 0b0010,
16
+ send_daily_spam: 0b0100,
17
+ )
18
+ end
19
+
20
+ def subject
21
+ @subject ||= @klass.new
22
+ end
23
+
24
+ def test_klass_to_s
25
+ assert_equal "Bitmasker::BitmaskScope(Bitmasker::BitmaskScopeTest::MockModel#email_mask)", @klass.to_s
26
+ end
27
+
28
+
29
+ def test_with_attribute
30
+ MockModel.expects(:where).with("email_mask & :mask = :mask", mask: 1)
31
+ subject.with_emails(:send_weekly_email)
32
+ end
33
+
34
+ def test_with_attributes_array
35
+ MockModel.expects(:where).with("email_mask & :mask = :mask", mask: 6)
36
+ subject.with_emails([:send_monthly_newsletter, :send_daily_spam])
37
+ end
38
+
39
+ def test_with_attributes
40
+ MockModel.expects(:where).with("email_mask & :mask = :mask", mask: 6)
41
+ subject.with_emails(:send_monthly_newsletter, :send_daily_spam)
42
+ end
43
+
44
+ def test_without_attribute
45
+ MockModel.expects(:where).with("email_mask & :mask = 0 OR email_mask IS NULL", mask: 2)
46
+ subject.without_emails(:send_monthly_newsletter)
47
+ end
48
+
49
+ def test_with_any_attribute
50
+ MockModel.expects(:where).with("email_mask & :mask <> 0", mask: 3)
51
+ subject.with_any_emails([:send_weekly_email, :send_monthly_newsletter])
52
+ end
53
+ end
@@ -41,7 +41,7 @@ class MockModel
41
41
  end
42
42
 
43
43
 
44
- class BitmaskAttributesTest < MiniTest::Unit::TestCase
44
+ class BitmaskAttributesTest < MiniTest::Test
45
45
 
46
46
  def test_does_stuff_attribute
47
47
  mock = MockModel.new
data/test/test_helper.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'minitest/autorun'
2
- require 'turn/autorun'
2
+ # require 'turn/autorun'
3
3
 
4
4
  require 'mocha/setup'
5
5
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitmasker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-21 00:00:00.000000000 Z
12
+ date: 2013-10-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bitmask
@@ -100,6 +100,7 @@ extensions: []
100
100
  extra_rdoc_files: []
101
101
  files:
102
102
  - lib/bitmasker/bitmask_attributes.rb
103
+ - lib/bitmasker/bitmask_scope.rb
103
104
  - lib/bitmasker/generator.rb
104
105
  - lib/bitmasker/model.rb
105
106
  - lib/bitmasker/rails.rb
@@ -109,6 +110,7 @@ files:
109
110
  - Rakefile
110
111
  - README.md
111
112
  - test/bitmasker/bitmask_attributes_test.rb
113
+ - test/bitmasker/bitmask_scope_test.rb
112
114
  - test/integration_test.rb
113
115
  - test/test_helper.rb
114
116
  homepage: https://github.com/amiel/bitmasker
@@ -123,20 +125,27 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
125
  - - ! '>='
124
126
  - !ruby/object:Gem::Version
125
127
  version: '0'
128
+ segments:
129
+ - 0
130
+ hash: -2321849182877328955
126
131
  required_rubygems_version: !ruby/object:Gem::Requirement
127
132
  none: false
128
133
  requirements:
129
134
  - - ! '>='
130
135
  - !ruby/object:Gem::Version
131
136
  version: '0'
137
+ segments:
138
+ - 0
139
+ hash: -2321849182877328955
132
140
  requirements: []
133
141
  rubyforge_project:
134
- rubygems_version: 1.8.23
142
+ rubygems_version: 1.8.24
135
143
  signing_key:
136
144
  specification_version: 3
137
145
  summary: Bitmasker allows you to store many boolean values as one integer field in
138
146
  the database.
139
147
  test_files:
140
148
  - test/bitmasker/bitmask_attributes_test.rb
149
+ - test/bitmasker/bitmask_scope_test.rb
141
150
  - test/integration_test.rb
142
151
  - test/test_helper.rb