active_flag 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.
- checksums.yaml +4 -4
- data/README.md +13 -6
- data/bin/console +2 -0
- data/lib/active_flag/definition.rb +12 -18
- data/lib/active_flag/version.rb +1 -1
- data/lib/active_flag.rb +9 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d91b4e83e18b7837784f5a39b0c9273b8e819a9b
|
4
|
+
data.tar.gz: 730b10b8a4af614ad6bc4a5f7c19fb535f486f77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2149ba75280a6dbd098b4116e37513a6972ead377578fd2edebbb3f5d59513acc15f753c8fdd219f92dae71b5d9714685abfb5a4c8c6c15bf0c4b0190d367631
|
7
|
+
data.tar.gz: 1fc01029a9115d7769ad8d99954d58e60f0590a57bf126676982a852488c625568fff6815081bd3f73ad61d84e09fc01c58aba7c44942a122bbbe8152c896c86
|
data/README.md
CHANGED
@@ -2,7 +2,12 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/kenn/active_flag)
|
4
4
|
|
5
|
-
Store up to 64 multiple flags ([bit array](https://en.wikipedia.org/wiki/Bit_array)) in a single integer column with ActiveRecord.
|
5
|
+
Store up to 64 multiple flags ([bit array](https://en.wikipedia.org/wiki/Bit_array)) in a single integer column with ActiveRecord. From a UI standpoint, it can be used as a multi-select checkbox storage.
|
6
|
+
|
7
|
+
Perfect solution to store multiple boolean values such as preferences, notification settings, achievement status, profile options, etc. in a single column.
|
8
|
+
|
9
|
+
* **Single column to group multiple values.** You don't need to have many separate columns. You don't even need a migration when you add a new flag item to the list.
|
10
|
+
* **Fast bitwise operations.** `WHERE languages & 3 > 0` is faster than `WHERE (english = true) OR (spanish = true) OR ...`
|
6
11
|
|
7
12
|
## Usage
|
8
13
|
|
@@ -22,8 +27,10 @@ profile.languages.to_a #=> [:english, :spanish]
|
|
22
27
|
profile.languages = [:spanish, :japanese] # Direct assignment that works with forms
|
23
28
|
|
24
29
|
# Class methods
|
25
|
-
Profile.
|
26
|
-
Profile.
|
30
|
+
Profile.where_languages(:french, :spanish) #=> SELECT * FROM profiles WHERE languages & 10 > 0
|
31
|
+
Profile.languages.maps #=> {:english=>1, :spanish=>2, :chinese=>4, :french=>8, :japanese=>16 }
|
32
|
+
Profile.languages.set_all!(:chinese) #=> UPDATE "profiles" SET languages = COALESCE(languages, 0) | 4
|
33
|
+
Profile.languages.unset_all!(:chinese) #=> UPDATE "profiles" SET languages = COALESCE(languages, 0) & ~4
|
27
34
|
```
|
28
35
|
|
29
36
|
## Install
|
@@ -49,13 +56,13 @@ add_column :users, :languages, :integer, null: false, default: 0, limit: 8
|
|
49
56
|
For a querying purpose, use `where_[column]` scope.
|
50
57
|
|
51
58
|
```ruby
|
52
|
-
Profile.where_languages(:french)
|
59
|
+
Profile.where_languages(:french) #=> SELECT * FROM profiles WHERE languages = 8
|
53
60
|
```
|
54
61
|
|
55
62
|
Also takes multiple values.
|
56
63
|
|
57
64
|
```ruby
|
58
|
-
Profile.where_languages(:french, :spanish)
|
65
|
+
Profile.where_languages(:french, :spanish) #=> SELECT * FROM profiles WHERE languages & 10 > 0
|
59
66
|
```
|
60
67
|
|
61
68
|
By default, it searches with `or` operation, so the query above returns profiles that have either French or Spanish.
|
@@ -84,7 +91,7 @@ ja:
|
|
84
91
|
japanese: 日本語
|
85
92
|
```
|
86
93
|
|
87
|
-
and now `
|
94
|
+
and now `to_human` method returns a translated string.
|
88
95
|
|
89
96
|
```ruby
|
90
97
|
I18n.locale = :ja
|
data/bin/console
CHANGED
@@ -1,23 +1,14 @@
|
|
1
1
|
module ActiveFlag
|
2
2
|
class Definition
|
3
|
-
|
3
|
+
attr_reader :keys, :maps, :column
|
4
4
|
|
5
|
-
def initialize(column, keys,
|
5
|
+
def initialize(column, keys, klass)
|
6
6
|
@column = column
|
7
7
|
@keys = keys.freeze
|
8
8
|
@maps = Hash[keys.map.with_index{|key, i| [key, 2**i] }].freeze
|
9
|
-
@options = options
|
10
9
|
@klass = klass
|
11
10
|
end
|
12
11
|
|
13
|
-
def get(instance, integer)
|
14
|
-
to_value(instance, integer)
|
15
|
-
end
|
16
|
-
|
17
|
-
def set(instance, arg)
|
18
|
-
instance.send :write_attribute, @column, to_i(arg)
|
19
|
-
end
|
20
|
-
|
21
12
|
def humans
|
22
13
|
@humans ||= {}
|
23
14
|
@humans[I18n.locale] ||= begin
|
@@ -34,25 +25,28 @@ module ActiveFlag
|
|
34
25
|
# http://stackoverflow.com/a/12928899/157384
|
35
26
|
|
36
27
|
def set_all!(key)
|
37
|
-
@klass.update_all("#{@column} = COALESCE(#{@column},0) | #{@maps[key]}")
|
28
|
+
@klass.update_all("#{@column} = COALESCE(#{@column}, 0) | #{@maps[key]}")
|
38
29
|
end
|
39
30
|
|
40
31
|
def unset_all!(key)
|
41
|
-
@klass.update_all("#{@column} = COALESCE(#{@column},0) & ~#{@maps[key]}")
|
32
|
+
@klass.update_all("#{@column} = COALESCE(#{@column}, 0) & ~#{@maps[key]}")
|
42
33
|
end
|
43
34
|
|
44
35
|
def to_i(arg)
|
45
|
-
return 0 if arg.blank?
|
46
36
|
arg = [arg] unless arg.is_a?(Enumerable)
|
47
|
-
arg.map{|
|
37
|
+
arg.map{|s| s && @maps[s.to_s.to_sym] || 0 }.sum
|
48
38
|
end
|
49
39
|
|
50
|
-
private
|
51
|
-
|
52
40
|
def to_value(instance, integer)
|
53
|
-
Value.new(
|
41
|
+
Value.new(to_array(integer)).with(instance, self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_array(integer)
|
45
|
+
@maps.map{|key, mask| (integer & mask > 0) ? key : nil }.compact
|
54
46
|
end
|
55
47
|
|
48
|
+
private
|
49
|
+
|
56
50
|
# Human-friendly print on class level
|
57
51
|
def human(key, options={})
|
58
52
|
return if key.nil? # otherwise, key.to_s.humanize will return ""
|
data/lib/active_flag/version.rb
CHANGED
data/lib/active_flag.rb
CHANGED
@@ -8,21 +8,21 @@ module ActiveFlag
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
-
def flag(column, keys
|
11
|
+
def flag(column, keys)
|
12
12
|
class << self
|
13
13
|
attr_reader :active_flags
|
14
14
|
end
|
15
15
|
@active_flags ||= {}
|
16
|
-
@active_flags[column] = Definition.new(column, keys,
|
16
|
+
@active_flags[column] = Definition.new(column, keys, self)
|
17
17
|
|
18
18
|
# Getter
|
19
19
|
define_method column do
|
20
|
-
self.class.active_flags[column].
|
20
|
+
self.class.active_flags[column].to_value(self, read_attribute(column))
|
21
21
|
end
|
22
22
|
|
23
23
|
# Setter
|
24
24
|
define_method "#{column}=" do |arg|
|
25
|
-
self.class.active_flags[column].
|
25
|
+
write_attribute column, self.class.active_flags[column].to_i(arg)
|
26
26
|
end
|
27
27
|
|
28
28
|
# Reference to definition
|
@@ -38,7 +38,11 @@ module ActiveFlag
|
|
38
38
|
if options[:op] == :and
|
39
39
|
where("#{column_name} & #{integer} = #{integer}")
|
40
40
|
else
|
41
|
-
|
41
|
+
if integer & (integer - 1) == 0 # Power of 2, single bit
|
42
|
+
where("#{column_name} = #{integer}")
|
43
|
+
else
|
44
|
+
where("#{column_name} & #{integer} > 0")
|
45
|
+
end
|
42
46
|
end
|
43
47
|
end
|
44
48
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_flag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenn Ejima
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|