enum_ish 1.3.2 → 1.5.1

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
  SHA256:
3
- metadata.gz: 921a0ee2d3e08bb28cf086d507c9ea128486424448dba584f93f59a77493ac01
4
- data.tar.gz: 22f9a8a7fe215a9ee064ed7653946984c99c745570af54b0572dd73145e9c1a7
3
+ metadata.gz: cb60bdd7ea33e3c85bee0ed814e651bd00736970de81cc3c6e0b47b384f42017
4
+ data.tar.gz: 1a206653955294e0bd6784f7e34098df04ff588fc7c16f0417d3da9c3425ee32
5
5
  SHA512:
6
- metadata.gz: 8db2398aff5a719ed934f38e98c4e749a1842642a944f19ae4b90d0a7bb395f6224ef5e5466c6495697a22dfcd8cfaf76930ea5e94e439be8d087d725557d64d
7
- data.tar.gz: '0845c01fed83443d41e0773dc6cfaab96ca9d91a25427a2d90b99e27090a767489dc17fbd23c3e896526a7d7e707e957bff824127bc5f60b1aedeeb2be24b8a4'
6
+ metadata.gz: 825669f678a2d0eb6dd23a656bf00d929920f41bd6b44daeda81a1a257c544219b0e57c0c0e9ef392f53c7b79ec4980a6e976b40f533d71d34496e7546302cef
7
+ data.tar.gz: 737648557aa906080bf70f1f3f2eb9cbf007490853a1c0f61a604d15b8201178083eeb9c503aec297d347f87555131f8765e7b0b9b9bd262dad6f9db3942ca27
@@ -0,0 +1,47 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-18.04
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
12
+ gemfile: ['rails50', 'rails51', 'rails52', 'rails60', 'rails61']
13
+ exclude:
14
+ - ruby: 2.3
15
+ gemfile: rails60
16
+ - ruby: 2.3
17
+ gemfile: rails61
18
+ - ruby: 2.4
19
+ gemfile: rails60
20
+ - ruby: 2.4
21
+ gemfile: rails61
22
+ - ruby: 3.0
23
+ gemfile: rails50
24
+ - ruby: 3.0
25
+ gemfile: rails51
26
+ - ruby: 3.0
27
+ gemfile: rails52
28
+
29
+ name: ruby ${{ matrix.ruby }}, ${{ matrix.gemfile }}
30
+
31
+ env:
32
+ BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
33
+
34
+ steps:
35
+ - uses: actions/checkout@v2
36
+ - uses: ruby/setup-ruby@v1
37
+ with:
38
+ ruby-version: ${{ matrix.ruby }}
39
+ bundler-cache: true
40
+ - name: Prepare test
41
+ run: |
42
+ cd spec/dummy
43
+ BUNDLE_GEMFILE=../../${{ env.BUNDLE_GEMFILE }} RAILS_ENV=test bundle exec rake db:create db:migrate db:seed
44
+ cd ../..
45
+ - name: Run test
46
+ run: |
47
+ bundle exec rspec
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.5.1
4
+
5
+ * Fix wrong cache lookup.
6
+
7
+ ## 1.5.0
8
+
9
+ * Add dictionary cache feature.
10
+ * Change module inclusion. Use 'include EnumIsh::Base' instead of 'extend EnumIsh'.
11
+ * Move configuration method to `EnumIsh`. Use `EnumIsh.configure` instead of 'EnumIsh::Config.configure'.
12
+
13
+ ## 1.4.1
14
+
15
+ * Avoid overwrite of superclass's `_enum_ish_enums`.
16
+ * Filter ancestors for dictionary lookup.
17
+
18
+ ## 1.4.0
19
+
20
+ * Generate `not` scope for activerecord class.
21
+ * Add prefix option to define short predicate method.
22
+ * Search translation including ancestor classes.
23
+ * Support multiple arguments for scope.
24
+ * Fix predicate and validation bugs for field with `accessor: true`.
25
+
26
+ ## 1.3.3
27
+
28
+ * Support rails 6.1.
29
+
3
30
  ## 1.3.2
4
31
 
5
32
  * Fix deprecation warning for ruby 2.7.
data/README.md CHANGED
@@ -26,7 +26,7 @@ Extend your class using EnumIsh and define an enum-like field:
26
26
 
27
27
  ```ruby
28
28
  class User
29
- extend EnumIsh
29
+ include EnumIsh::Base
30
30
  attr_accessor :status # status is a string field
31
31
  enum_ish :status, ['enable', 'disable'] # status has 'enable' or 'disable'
32
32
  end
@@ -82,7 +82,7 @@ Set default value:
82
82
 
83
83
  ```ruby
84
84
  class User
85
- extend EnumIsh
85
+ include EnumIsh::Base
86
86
  attr_accessor :status
87
87
  enum_ish :status, ['enable', 'disable'], default: 'enable'
88
88
  end
@@ -95,7 +95,7 @@ Use default value with block:
95
95
 
96
96
  ```ruby
97
97
  class User
98
- extend EnumIsh
98
+ include EnumIsh::Base
99
99
  attr_accessor :status, :flag
100
100
  enum_ish :status, ['enable', 'disable'], default: -> { flag ? 'enable' : 'disable' }
101
101
  end
@@ -110,7 +110,7 @@ Generate predicate methods:
110
110
 
111
111
  ```ruby
112
112
  class User
113
- extend EnumIsh
113
+ include EnumIsh::Base
114
114
  attr_accessor :status
115
115
  enum_ish :status, ['enable', 'disable'], predicate: true
116
116
  end
@@ -121,13 +121,28 @@ user.status_enable? #=> true
121
121
  user.status_disable? #=> false
122
122
  ```
123
123
 
124
+ Without prefix:
125
+
126
+ ```ruby
127
+ class User
128
+ include EnumIsh::Base
129
+ attr_accessor :status
130
+ enum_ish :status, ['enable', 'disable'], predicate: { prefix: false }
131
+ end
132
+
133
+ user = User.new
134
+ user.status = 'enable'
135
+ user.enable? #=> true
136
+ user.disable? #=> false
137
+ ```
138
+
124
139
  ### Accessor
125
140
 
126
141
  Generate getter and setter for aliased symbols instead of raw values:
127
142
 
128
143
  ```ruby
129
144
  class User
130
- extend EnumIsh
145
+ include EnumIsh::Base
131
146
  attr_accessor :status
132
147
  enum_ish :status, { _enable: 'enable', _disable: 'disable' }, accessor: true
133
148
  end
@@ -146,7 +161,7 @@ Generate accessor:
146
161
 
147
162
  ```ruby
148
163
  class User < ActiveRecord::Base
149
- extend EnumIsh
164
+ include EnumIsh::Base
150
165
  enum_ish :status, { _enable: 'enable', _disable: 'disable' }, accessor: true
151
166
  end
152
167
 
@@ -159,11 +174,12 @@ Generate scope:
159
174
 
160
175
  ```ruby
161
176
  class User < ActiveRecord::Base
162
- extend EnumIsh
177
+ include EnumIsh::Base
163
178
  enum_ish :status, ['enable', 'disable'], scope: true
164
179
  end
165
180
 
166
181
  User.with_status(:enable) #=> SELECT "users".* FROM "users" WHERE "users"."status" = "enable"
182
+ User.with_status_not(:enable) #=> SELECT "users".* FROM "users" WHERE "users"."status" != 'enable'
167
183
  ```
168
184
 
169
185
  #### Validation
@@ -172,7 +188,7 @@ Generate validation:
172
188
 
173
189
  ```ruby
174
190
  class User < ActiveRecord::Base
175
- extend EnumIsh
191
+ include EnumIsh::Base
176
192
  enum_ish :status, ['enable', 'disable'], validate: true
177
193
  end
178
194
 
@@ -181,6 +197,17 @@ user.status = 'INVALID'
181
197
  user.valid? #=> false
182
198
  ```
183
199
 
200
+ ### Dictionary cache
201
+
202
+ You can enable dictionary cache using rack middleware:
203
+
204
+ ```ruby
205
+ Rails.application.config.middleware.use EnumIsh::DictionaryCache
206
+ ```
207
+
208
+ This middleware enables dictionary cache for each request.
209
+ Prformance of dictionary lookup will be improved in case one dictionary is used repeatedly in the same request.
210
+
184
211
  ## Contributing
185
212
 
186
213
  Bug reports and pull requests are welcome at https://github.com/kanety/enum_ish.
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rails", "~> 6.1.0"
4
+
5
+ gemspec path: "../"
data/lib/enum_ish.rb CHANGED
@@ -1,27 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support'
2
4
  require 'enum_ish/version'
3
- require 'enum_ish/errors'
4
5
  require 'enum_ish/config'
5
- require 'enum_ish/enum'
6
- require 'enum_ish/dictionary'
7
- require 'enum_ish/definer'
8
- require 'enum_ish/active_record_definer' if defined?(ActiveRecord::Base)
6
+ require 'enum_ish/base'
7
+ require 'enum_ish/dictionary_cache'
9
8
 
10
9
  module EnumIsh
11
- def enum_ish(name, map, config = {})
12
- enum = Enum.new(name, map, config)
13
-
14
- self._enum_ish_enums ||= {}
15
- self._enum_ish_enums[name.to_sym] = enum
16
-
17
- if defined?(ActiveRecord::Base) && self.ancestors.include?(ActiveRecord::Base)
18
- ActiveRecordDefiner.new(self).define(enum)
19
- else
20
- Definer.new(self).define(enum)
10
+ class << self
11
+ def configure
12
+ yield Config
21
13
  end
22
- end
23
14
 
24
- def self.extended(klass)
25
- klass.class_attribute :_enum_ish_enums
15
+ def config
16
+ Config
17
+ end
26
18
  end
27
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'active_record_enum_type'
2
4
 
3
5
  module EnumIsh
@@ -24,7 +26,13 @@ module EnumIsh
24
26
  enum.mapping.fetch(value, value)
25
27
  end
26
28
 
27
- decorate_attribute_type(enum.name, :enum) do |subtype|
29
+ args =
30
+ if ActiveRecord.version > Gem::Version.new('6.1.0.a')
31
+ [enum.name]
32
+ else
33
+ [enum.name, :enum]
34
+ end
35
+ decorate_attribute_type(*args) do |subtype|
28
36
  EnumIsh::ActiveRecordEnumType.new(enum.name, enum.mapping, subtype)
29
37
  end
30
38
  end
@@ -32,9 +40,12 @@ module EnumIsh
32
40
 
33
41
  def define_scope(enum)
34
42
  @klass.class_eval do
35
- scope "#{Config.scope_prefix}#{enum.name}#{Config.scope_suffix}", ->(value) {
43
+ scope "#{Config.scope_prefix}#{enum.name}#{Config.scope_suffix}", ->(*value) {
36
44
  where(enum.name => enum.mapping.fetch(value, value))
37
45
  }
46
+ scope "#{Config.scope_prefix}#{enum.name}_not#{Config.scope_suffix}", ->(*value) {
47
+ where.not(enum.name => enum.mapping.fetch(value, value))
48
+ }
38
49
  end
39
50
  end
40
51
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EnumIsh
2
4
  class ActiveRecordEnumType < ActiveRecord::Type::Value
3
5
  def initialize(name, mapping, subtype)
@@ -0,0 +1,29 @@
1
+ require_relative 'enum'
2
+ require_relative 'dictionary'
3
+ require_relative 'errors'
4
+ require_relative 'definer'
5
+ require_relative 'active_record_definer' if defined?(ActiveRecord::Base)
6
+
7
+ module EnumIsh
8
+ module Base
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ class_attribute :_enum_ish_enums
13
+ self._enum_ish_enums = {}
14
+ end
15
+
16
+ class_methods do
17
+ def enum_ish(name, map, config = {})
18
+ enum = Enum.new(name, map, config)
19
+ self._enum_ish_enums = _enum_ish_enums.merge(name.to_sym => enum)
20
+
21
+ if defined?(ActiveRecord::Base) && self.ancestors.include?(ActiveRecord::Base)
22
+ ActiveRecordDefiner.new(self).define(enum)
23
+ else
24
+ Definer.new(self).define(enum)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EnumIsh
2
4
  class Config
3
- @@options = {
5
+ class_attribute :data
6
+
7
+ self.data = {
4
8
  text_prefix: '',
5
9
  text_suffix: '_text',
6
10
  options_prefix: '',
@@ -11,19 +15,13 @@ module EnumIsh
11
15
  scope_suffix: ''
12
16
  }
13
17
 
14
- @@options.keys.each do |key|
18
+ data.keys.each do |key|
15
19
  define_singleton_method "#{key}" do
16
- @@options[key]
20
+ data[key]
17
21
  end
18
22
 
19
23
  define_singleton_method "#{key}=" do |val|
20
- @@options[key] = val
21
- end
22
- end
23
-
24
- class << self
25
- def configure
26
- yield self
24
+ data[key] = val
27
25
  end
28
26
  end
29
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EnumIsh
2
4
  class Definer
3
5
  def initialize(klass)
@@ -32,13 +34,23 @@ module EnumIsh
32
34
 
33
35
  def define_predicate(enum)
34
36
  enum.mapping.each do |enum_key, enum_value|
37
+ method_name = if enum.setting[:predicate].is_a?(Hash) && enum.setting[:predicate][:prefix] == false
38
+ "#{enum_key}?"
39
+ else
40
+ "#{enum.name}_#{enum_key}?"
41
+ end
42
+ target_value = if enum.setting[:accessor]
43
+ enum_key
44
+ else
45
+ enum_value
46
+ end
35
47
  @klass.class_eval do
36
- define_method "#{enum.name}_#{enum_key}?".tr('.', '_') do
48
+ define_method method_name.tr('.', '_') do
37
49
  value = public_send(enum.name)
38
50
  if value.is_a?(Array)
39
- value == [enum_value]
51
+ value == [target_value]
40
52
  else
41
- value == enum_value
53
+ value == target_value
42
54
  end
43
55
  end
44
56
  end
@@ -75,8 +87,13 @@ module EnumIsh
75
87
  end
76
88
 
77
89
  def define_validate(enum)
90
+ targets = if enum.setting[:accessor]
91
+ enum.mapping.keys
92
+ else
93
+ enum.mapping.values
94
+ end
78
95
  @klass.class_eval do
79
- validates enum.name, inclusion: { in: enum.mapping.values }, allow_nil: true
96
+ validates enum.name, inclusion: { in: targets }, allow_nil: true
80
97
  end
81
98
  end
82
99
 
@@ -1,8 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EnumIsh
2
4
  class Dictionary
5
+ CACHE_KEY = :_enum_ish_dictionary_cache
6
+
3
7
  def initialize(klass, enum, options = {})
4
8
  @klass = klass
5
- @dict = load(enum, options)
9
+ @enum = enum
10
+ @options = options
11
+ @dict = cache { Lookup.new(@klass, @enum, @options).call }
6
12
  end
7
13
 
8
14
  def translate_value(value)
@@ -11,7 +17,7 @@ module EnumIsh
11
17
  else
12
18
  @dict[value] || value
13
19
  end
14
- end
20
+ end
15
21
 
16
22
  def translate_options
17
23
  @dict.to_a.map { |value, label| [label, value] }
@@ -19,44 +25,72 @@ module EnumIsh
19
25
 
20
26
  private
21
27
 
22
- def load(enum, options)
23
- dict = translate_dict(enum, options)
24
- filter(dict, options)
28
+ def cache
29
+ if (cache = Thread.current[CACHE_KEY]) != nil
30
+ cache[I18n.locale] ||= {}
31
+ cache[I18n.locale][@klass] ||= {}
32
+ cache[I18n.locale][@klass][@enum] ||= {}
33
+ cache[I18n.locale][@klass][@enum][@options] ||= yield
34
+ else
35
+ yield
36
+ end
25
37
  end
26
38
 
27
- def translate_dict(enum, options)
28
- dict = load_dict(enum, options)
29
- translated = enum.mapping.map { |k, v| dict[k] ? [k, dict[k]] : [k, v.to_s] }.to_h
30
- translated = translated.map { |k, v| [enum.mapping[k], v] }.to_h unless enum.setting[:accessor]
31
- translated
39
+ class << self
40
+ def cache
41
+ Thread.current[CACHE_KEY] = {}
42
+ yield
43
+ ensure
44
+ Thread.current[CACHE_KEY] = nil
45
+ end
32
46
  end
47
+ end
33
48
 
34
- def load_dict(enum, options)
35
- key = i18n_key(enum, options)
36
- dict = I18n.t("enum_ish.#{@klass.name.underscore}.#{key}", **i18n_options(enum, options))
37
- dict.map { |k, v| [k.to_s.to_sym, v.to_s] }.to_h
49
+ class Lookup
50
+ def initialize(klass, enum, options = {})
51
+ @klass = klass
52
+ @enum = enum
53
+ @options = options
38
54
  end
39
55
 
40
- def i18n_key(enum, options)
41
- [enum.name, options[:format]].compact.join('/')
56
+ def call
57
+ i18n = lookup_for(@klass).transform_keys { |k| k.to_s.to_sym }
58
+
59
+ dict = {}
60
+ if @enum.setting[:accessor]
61
+ @enum.mapping.each { |k, v| dict[k] = i18n[k].to_s }
62
+ else
63
+ @enum.mapping.each { |k, v| dict[v] = i18n[k].to_s }
64
+ end
65
+
66
+ filter(dict)
42
67
  end
43
68
 
44
- def i18n_options(enum, options)
45
- key = i18n_key(enum, options)
46
- opts = options[:i18n_options] || {}
47
- opts.merge(default: [:"enum_ish.defaults.#{key}", enum.mapping.invert])
69
+ private
70
+
71
+ def lookup_for(klass)
72
+ key = [@enum.name, @options[:format]].compact.join('/')
73
+ options = (@options[:i18n_options] || {}).merge(default: nil)
74
+
75
+ if klass.name.to_s.in?(['ActiveRecord::Base', 'Object'])
76
+ I18n.t(:"enum_ish.defaults.#{key}", **options) || @enum.mapping.invert
77
+ elsif klass.name.blank? || !klass.is_a?(Class)
78
+ resolve(klass.superclass)
79
+ else
80
+ I18n.t(:"enum_ish.#{klass.name.underscore}.#{key}", **options) || lookup_for(klass.superclass)
81
+ end
48
82
  end
49
83
 
50
- def filter(translated, options)
51
- if options[:except]
52
- except = Array(options[:except])
53
- translated.reject! { |k, v| except.include?(k) }
84
+ def filter(dict)
85
+ if @options[:except]
86
+ except = Array(@options[:except])
87
+ dict.reject! { |k, v| except.include?(k) }
54
88
  end
55
- if options[:only]
56
- only = Array(options[:only])
57
- translated.select! { |k, v| only.include?(k) }
89
+ if @options[:only]
90
+ only = Array(@options[:only])
91
+ dict.select! { |k, v| only.include?(k) }
58
92
  end
59
- translated
93
+ dict
60
94
  end
61
95
  end
62
96
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EnumIsh
4
+ class DictionaryCache
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ EnumIsh::Dictionary.cache do
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/enum_ish/enum.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EnumIsh
2
4
  class Enum
3
5
  attr_accessor :name
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EnumIsh
2
4
  class Error < StandardError
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EnumIsh
2
- VERSION = '1.3.2'
4
+ VERSION = '1.5.1'
3
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enum_ish
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yoshikazu Kaneta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-24 00:00:00.000000000 Z
11
+ date: 2021-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -101,9 +101,9 @@ executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
+ - ".github/workflows/ci.yml"
104
105
  - ".gitignore"
105
106
  - ".rspec"
106
- - ".travis.yml"
107
107
  - CHANGELOG.md
108
108
  - Gemfile
109
109
  - LICENSE
@@ -114,12 +114,15 @@ files:
114
114
  - gemfiles/rails51.gemfile
115
115
  - gemfiles/rails52.gemfile
116
116
  - gemfiles/rails60.gemfile
117
+ - gemfiles/rails61.gemfile
117
118
  - lib/enum_ish.rb
118
119
  - lib/enum_ish/active_record_definer.rb
119
120
  - lib/enum_ish/active_record_enum_type.rb
121
+ - lib/enum_ish/base.rb
120
122
  - lib/enum_ish/config.rb
121
123
  - lib/enum_ish/definer.rb
122
124
  - lib/enum_ish/dictionary.rb
125
+ - lib/enum_ish/dictionary_cache.rb
123
126
  - lib/enum_ish/enum.rb
124
127
  - lib/enum_ish/errors.rb
125
128
  - lib/enum_ish/version.rb
data/.travis.yml DELETED
@@ -1,24 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3
4
- - 2.4
5
- - 2.5
6
- - 2.6
7
- - 2.7
8
- gemfile:
9
- - gemfiles/rails50.gemfile
10
- - gemfiles/rails51.gemfile
11
- - gemfiles/rails52.gemfile
12
- - gemfiles/rails60.gemfile
13
- matrix:
14
- exclude:
15
- - rvm: 2.3
16
- gemfile: gemfiles/rails60.gemfile
17
- - rvm: 2.4
18
- gemfile: gemfiles/rails60.gemfile
19
- before_script:
20
- - cd spec/dummy
21
- - bundle exec rake db:create db:migrate db:seed RAILS_ENV=test
22
- - cd ../..
23
- script:
24
- - bundle exec rspec