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 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