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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1eab2c1d74e4f219e5fa9c3518fdc65efb97cb76
4
- data.tar.gz: 9e694fdbf84d951e190d5986738f49d3adfb2c8e
3
+ metadata.gz: d91b4e83e18b7837784f5a39b0c9273b8e819a9b
4
+ data.tar.gz: 730b10b8a4af614ad6bc4a5f7c19fb535f486f77
5
5
  SHA512:
6
- metadata.gz: 2cf6ca0d48b366dc48de2278225827a8ada2fa797cacaa62514d14d180f4c4948f11741eee3e7fc5fac3944090ff0fe6532f565b60539e6ae47576b8f9474d40
7
- data.tar.gz: 3b802d4cc801ad8ad3b95dc55bca376712f06cf2c1776a51643b0f5092555901f8344f5be17e16da9b101d4b4296de838108ad52ec34d37819a51e668565aa5a
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.languages.maps #=> {:english=>1, :spanish=>2, :chinese=>4, :french=>8, :japanese=>16 }
26
- Profile.where_languages(:french) #=> SELECT * FROM profiles WHERE languages & 8 > 0
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) #=> SELECT * FROM profiles WHERE languages & 8 > 0
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 `profile.languages.to_human` returns a translated string.
94
+ and now `to_human` method returns a translated string.
88
95
 
89
96
  ```ruby
90
97
  I18n.locale = :ja
data/bin/console CHANGED
@@ -5,5 +5,7 @@ require 'active_flag'
5
5
 
6
6
  require_relative '../test/load_fixtures'
7
7
 
8
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
9
+
8
10
  require 'irb'
9
11
  IRB.start
@@ -1,23 +1,14 @@
1
1
  module ActiveFlag
2
2
  class Definition
3
- attr_accessor :keys, :maps, :options, :column
3
+ attr_reader :keys, :maps, :column
4
4
 
5
- def initialize(column, keys, options, klass)
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{|i| i && @maps[i.to_sym] || 0 }.sum
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(@maps.map{|key, mask| (integer & mask > 0) ? key : nil }.compact).with(instance, self)
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 ""
@@ -1,3 +1,3 @@
1
1
  module ActiveFlag
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
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, options = {})
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, options, self)
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].get(self, read_attribute(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].set(self, arg)
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
- where("#{column_name} & #{integer} > 0")
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.1.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-22 00:00:00.000000000 Z
11
+ date: 2016-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord