flagpole 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0d780ac84b47a9d2fdd5ee20b34bb7ebb5ef88b6
4
+ data.tar.gz: 6fb5247148b3bcf18d786037aa51f9750ed888ed
5
+ SHA512:
6
+ metadata.gz: 7eebec9b05c2460a278f338723c45be41861827970c61da69f1fb6079b6754f629ddeb6ed2ae3b7470d60b49bed90cba87d8a8d938a866e63f9b5ef9144d27c6
7
+ data.tar.gz: 0cca77e4b8f07aa081144c52920cc2a2454f5b67d013d401bc3769bfe900a5f0d7710f23487669115262c11256dad127f8784807f813a78036b3d4d810741c12
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Nicolas Sanguinetti <hi@nicolassanguinetti.info>
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Flagpole: Simple bitmap for storing flags
2
+
3
+ ![](https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Expo_2010_Shanghai_flags_and_China_Pavilion.jpg/1024px-Expo_2010_Shanghai_flags_and_China_Pavilion.jpg)
4
+
5
+ Flagpole allows bundling a bunch of flags into a single integer field, by
6
+ storing each flag as a bit.
7
+
8
+ ``` ruby
9
+ notifications_via = Flagpole.new([:email, :sms, :phone_push])
10
+ notifications_via.to_h #=> { email: false, sms: false, phone_push: false }
11
+
12
+ notifications_via[:email] = true
13
+ notifications_via[:sms] = true
14
+
15
+ notifications_via.to_h #=> { email: true, sms: true, phone_push: false }
16
+ notifications_via.to_i #=> 3
17
+ ```
18
+
19
+ Now you can just store that `3` in your database. In order to get that back,
20
+ just pass it to the constructor:
21
+
22
+ ``` ruby
23
+ notifications_via = Flagpole.new(3, [:email, :sms, :phone_push])
24
+ notifications_via.to_h #=> { email: true, sms: true, phone_push: false }
25
+ ```
26
+
27
+ ## Install
28
+
29
+ gem install flagpole
30
+
31
+ ## What kind of sorcery is this?!
32
+
33
+ If you consider each flag as a "bit" (1 when true, 0 when false), then the above
34
+ example could be represented as `[1, 1, 0]`. If you reverse this, you get the
35
+ binary representation of the number `3` (`11`).
36
+
37
+ All Flagpole does is give you a nice API to treat sets of named binary settings
38
+ ("flags") as an integer, by doing the marshaling for you.
39
+
40
+ [Read more about bitmaps on Wikipedia](https://en.wikipedia.org/wiki/Bit_field).
41
+
42
+ ## I want to add a new flag
43
+
44
+ Easy! Just add a new name **at the end** of the array of flag names. So, In the
45
+ example above, if you later add support for twitter notifications, just do this:
46
+
47
+ ``` ruby
48
+ notifications_via = Flagpole.new(3, [:email, :sms, :phone_push, :twitter])
49
+ notifications_via.to_h #=> { email: true, sms: true, phone_push: false, twitter: false }
50
+ ```
51
+
52
+ As you see, the values are maintained.
53
+
54
+ If you wanted the new flag to default to `true`, you just need to increment all
55
+ the values. You do this by using `#value_of`:
56
+
57
+ ``` ruby
58
+ flags = Flagpole([:email, :sms, :phone_push, :twitter])
59
+ flags.value_of(:twitter) #=> 8
60
+ ```
61
+
62
+ Now all you have to do is add `8` to all your stored values, and voila, everyone
63
+ has twitter notifications enabled.
64
+
65
+ ## I want to remove an existing flag
66
+
67
+ In order to remove a flag, you will need to modify the integer values from your
68
+ storage. Assuming the example from above, let's say you no longer wish to
69
+ support SMS notifications.
70
+
71
+ In order to find out the value to substract, you can use `#value_of`:
72
+
73
+ ``` ruby
74
+ flags = Flagpole([:email, :sms, :phone_push, :twitter])
75
+ flags.value_of(:sms) #=> 2
76
+ ```
77
+
78
+ Now you need to substract 2 from all the values that _have the SMS flag set_. In
79
+ order to do this, you need to do a _bitwise and_ between the value and `2`.
80
+ Those that are non-zero, are the ones that have it set. In SQL this would be:
81
+
82
+ ``` sql
83
+ UPDATE users
84
+ SET notification_settings = notification_settings - 2
85
+ WHERE notification_settings & 2 <> 0
86
+ ```
87
+
88
+ ## License
89
+
90
+ This project is shared under the MIT license. See the attached [LICENSE][] file
91
+ for details.
92
+
93
+ Photo credit: [_Cesarexpo via Wikimedia Commons_](https://commons.wikimedia.org/wiki/File%3AExpo_2010_Shanghai_flags_and_China_Pavilion.jpg).
94
+
95
+ [LICENSE]: ./LICENSE
data/lib/flagpole.rb ADDED
@@ -0,0 +1,64 @@
1
+ class Flagpole
2
+ # Public: Initialize the set of flags.
3
+ #
4
+ # value - An Integer with the initial value of the set.
5
+ # flags - The name of the flags you're using.
6
+ def initialize(value = 0, flags)
7
+ @flags = flags.zip(bitmap(value, flags.size)).to_h
8
+ end
9
+
10
+ # Public: Get a specific flag by name.
11
+ #
12
+ # key - One of the flag names passed to the initializer.
13
+ #
14
+ # Returns Boolean.
15
+ def [](key)
16
+ @flags.fetch(key)
17
+ end
18
+
19
+ # Public: Set a specific flag by name.
20
+ #
21
+ # flag - One of the flag names passed to the initializer.
22
+ # value - A Boolean.
23
+ #
24
+ # Returns nothing.
25
+ # Raises ArgumentError if passed a flag that isn't in the set.
26
+ def []=(flag, val)
27
+ fail ArgumentError, "#{flag} isn't a valid flag" unless @flags.key?(flag)
28
+ @flags[flag] = val
29
+ end
30
+
31
+ # Public: Returns the current value of the flag set as an Integer.
32
+ def to_i
33
+ @flags.each_value.with_index.inject(0) do |flag, (bit, power)|
34
+ flag | (bit ? 1 : 0) * 2**power
35
+ end
36
+ end
37
+
38
+ # Public: Returns a Hash with all the flags and their current values.
39
+ def to_h
40
+ @flags
41
+ end
42
+
43
+ # Public: Find the integer value of a single flag, regardless of whether it is
44
+ # set or not. This value can then be added/substracted from the integer value
45
+ # of this set for low-level manipulation of the flag set.
46
+ #
47
+ # flag - The name of a flag.
48
+ #
49
+ # Returns an Integer.
50
+ # Raises ArgumentError if passed a flag that isn't in the set.
51
+ def value_of(flag)
52
+ fail ArgumentError, "#{flag} isn't a valid flag" unless @flags.key?(flag)
53
+ 2 ** @flags.keys.index(flag)
54
+ end
55
+
56
+ # Internal: Generate the list of bits that make a given number. Each index of
57
+ # the array corresponds to the bit for 2**index.
58
+ #
59
+ # Returns an Array of Booleans.
60
+ def bitmap(num, size)
61
+ Array.new(size).map.with_index { |_, i| num[i] == 1 }
62
+ end
63
+ private :bitmap
64
+ end
@@ -0,0 +1,3 @@
1
+ class Flagpole
2
+ VERSION = "0.1.0".freeze
3
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flagpole
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nicolas Sanguinetti
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cutest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ description: Use a bitmap to represent multiple flags (e.g. user settings)
28
+ email:
29
+ - contacto@nicolassanguinetti.info
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE
35
+ - README.md
36
+ - lib/flagpole.rb
37
+ - lib/flagpole/version.rb
38
+ homepage: http://github.com/foca/flagpole
39
+ licenses:
40
+ - MIT
41
+ metadata: {}
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 2.4.8
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Handle multiple flags in a single integer
62
+ test_files: []