active_flag 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/kenn/active_flag.svg)](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
|