nd-enum 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
  SHA256:
3
- metadata.gz: 14980decd9ccdc766c2e27fc1c224a4dc94aee5bce41069fa574a58abe29f60f
4
- data.tar.gz: 908d743ac4486cd835370ec06034661c15fc1b30dc4cf196cf02bdeb03f234ea
3
+ metadata.gz: 1319016da965b32314532d641038f67316c07087b03f8a0a195786d783307c08
4
+ data.tar.gz: e88397852614473313ba633c1d3029de0cad1466b9ddfa0787b54e5cc7133eb8
5
5
  SHA512:
6
- metadata.gz: f02d420631940aec368decfa880ba8f956b7675a40d2ce9a5f7704b838c912f62056b271ad53afb9584c6e9539f17b89371b3992f785d123fc6c4d1f16261417
7
- data.tar.gz: 6f194a9f4b8396a46b8198af254648d7bf50c5291245e42afbbfdd06729dc31d621b3b602015a1c338809fc539cc15b62b1ecdbae6466e50691b93b031c370f1
6
+ metadata.gz: 65596f68728490a34c575fac8191438d85b4490268a5938c725f5ed7c0fbe3e0616679b332971dd8991f0989c34bd41c51b89bdaf1153f822f6446a0714613fc
7
+ data.tar.gz: c583f3f1e8a655bcf9ba604ef3851cbb27dba1acc7743ea5c158a763c33792e3cd2dc826659b7e53c1ae3380ac045aef60e53e6fd1bff3b9d8e83b315bd37b5f
data/.rspec ADDED
@@ -0,0 +1,6 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
4
+ --require mocha
5
+ --require active_record
6
+ --require pry
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.2.0] - 2022-08-10
4
+
5
+ - Add initializer
6
+ - Validate translations
7
+
8
+ ## [0.1.2] - 2022-08-08
9
+
10
+ - Add specs
11
+
12
+ ## [0.1.1] - 2022-08-07
13
+
14
+ - Fix bugs
15
+
16
+ ## [0.1.0] - 2022-08-07
17
+
18
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in nd-enum.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+ gem 'rspec', '~> 3.0'
10
+ gem 'mocha', '~> 1.13'
11
+ gem 'pry', '~> 0.13'
12
+ gem 'guard', '~> 2.18'
13
+ gem 'guard-rspec', '~> 4.7', require: false
data/Gemfile.lock ADDED
@@ -0,0 +1,91 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nd-enum (0.2.0)
5
+ activerecord (>= 6.0.0)
6
+ activesupport (>= 6.0.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (7.0.3.1)
12
+ activesupport (= 7.0.3.1)
13
+ activerecord (7.0.3.1)
14
+ activemodel (= 7.0.3.1)
15
+ activesupport (= 7.0.3.1)
16
+ activesupport (7.0.3.1)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ coderay (1.1.3)
22
+ concurrent-ruby (1.1.10)
23
+ diff-lcs (1.5.0)
24
+ ffi (1.15.5)
25
+ formatador (1.1.0)
26
+ guard (2.18.0)
27
+ formatador (>= 0.2.4)
28
+ listen (>= 2.7, < 4.0)
29
+ lumberjack (>= 1.0.12, < 2.0)
30
+ nenv (~> 0.1)
31
+ notiffany (~> 0.0)
32
+ pry (>= 0.13.0)
33
+ shellany (~> 0.0)
34
+ thor (>= 0.18.1)
35
+ guard-compat (1.2.1)
36
+ guard-rspec (4.7.3)
37
+ guard (~> 2.1)
38
+ guard-compat (~> 1.1)
39
+ rspec (>= 2.99.0, < 4.0)
40
+ i18n (1.12.0)
41
+ concurrent-ruby (~> 1.0)
42
+ listen (3.7.1)
43
+ rb-fsevent (~> 0.10, >= 0.10.3)
44
+ rb-inotify (~> 0.9, >= 0.9.10)
45
+ lumberjack (1.2.8)
46
+ method_source (1.0.0)
47
+ minitest (5.16.2)
48
+ mocha (1.14.0)
49
+ nenv (0.3.0)
50
+ notiffany (0.1.3)
51
+ nenv (~> 0.1)
52
+ shellany (~> 0.0)
53
+ pry (0.14.1)
54
+ coderay (~> 1.1)
55
+ method_source (~> 1.0)
56
+ rake (13.0.6)
57
+ rb-fsevent (0.11.1)
58
+ rb-inotify (0.10.1)
59
+ ffi (~> 1.0)
60
+ rspec (3.11.0)
61
+ rspec-core (~> 3.11.0)
62
+ rspec-expectations (~> 3.11.0)
63
+ rspec-mocks (~> 3.11.0)
64
+ rspec-core (3.11.0)
65
+ rspec-support (~> 3.11.0)
66
+ rspec-expectations (3.11.0)
67
+ diff-lcs (>= 1.2.0, < 2.0)
68
+ rspec-support (~> 3.11.0)
69
+ rspec-mocks (3.11.1)
70
+ diff-lcs (>= 1.2.0, < 2.0)
71
+ rspec-support (~> 3.11.0)
72
+ rspec-support (3.11.0)
73
+ shellany (0.0.1)
74
+ thor (1.2.1)
75
+ tzinfo (2.0.5)
76
+ concurrent-ruby (~> 1.0)
77
+
78
+ PLATFORMS
79
+ x86_64-darwin-21
80
+
81
+ DEPENDENCIES
82
+ guard (~> 2.18)
83
+ guard-rspec (~> 4.7)
84
+ mocha (~> 1.13)
85
+ nd-enum!
86
+ pry (~> 0.13)
87
+ rake (~> 13.0)
88
+ rspec (~> 3.0)
89
+
90
+ BUNDLED WITH
91
+ 2.3.12
data/Guardfile ADDED
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.call("routing/#{m[1]}_routing"),
51
+ rspec.spec.call("controllers/#{m[1]}_controller"),
52
+ rspec.spec.call("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Romain Clavel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # ND::Enum
2
+
3
+ This gem allows you to create and use enums easily and quickly in your Rails project.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add nd-enum
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install nd-enum
14
+
15
+ ## Usage
16
+
17
+ - [Basic usage](#basic-usage)
18
+ - [Configuration](#configuration)
19
+ - [I18n](#i18n)
20
+ - [Validate translations presence](#validate-translations-presence)
21
+ - [Enforce translations presence](#enforce-translations-presence)
22
+ - [ActiveRecord Enum](#activerecord-enum)
23
+
24
+ ### Basic usage
25
+
26
+ Define your enum in an ActiveRecord model:
27
+
28
+ ```ruby
29
+ class User < ApplicationRecord
30
+ nd_enum(role: %i(user admin))
31
+ end
32
+ ```
33
+
34
+ It creates a module for your enum that contains one constant per enum value. Say goodbye to [magic strings](https://en.wikipedia.org/wiki/Magic_string)!
35
+
36
+ In our example, the module is `User::Role`, and the constants are `User::Role::USER` and `User::Role::ADMIN`.
37
+
38
+ ```ruby
39
+ irb(main)> User::Role::USER
40
+ => "user"
41
+
42
+ irb(main)> User::Role::ADMIN
43
+ => "admin"
44
+
45
+ irb(main)> User::Role.all
46
+ => ["user", "admin"]
47
+
48
+ irb(main)> User::Role.length
49
+ => 2
50
+
51
+ irb(main)> User::Role[1]
52
+ => "admin"
53
+
54
+ irb(main)> User::Role[:user]
55
+ => "user"
56
+
57
+ irb(main)> User::Role.include?('foobar')
58
+ => false
59
+
60
+ irb(main)> User::Role.include?('user')
61
+ => true
62
+ ```
63
+
64
+ ND::Enum inheritates from [`Enumerable`](https://ruby-doc.org/core-3.1.2/Enumerable.html), so it is possible to use all `Enumerable` methods on the enum module: `each`, `map`, `find`...
65
+
66
+ ### Configuration
67
+
68
+ Fine-tune `ND::Enum` behaviour by creating an initializer (for example: `config/initializers/nd_enum.rb`).
69
+ The values shown in the example below are the default values.
70
+
71
+ ```ruby
72
+ ND::Enum.configure do |c|
73
+ c.default_i18n_scope = :base
74
+ c.default_i18n_validation_mode = :ignore # Allowed values: ignore, log, enforce
75
+ end
76
+ ```
77
+
78
+ ### I18n
79
+
80
+ Allows to translate your enum values.
81
+ Add to your locale files:
82
+
83
+ ```yaml
84
+ en:
85
+ users: # Model.table_name
86
+ role: # attribute
87
+ base: # default scope
88
+ user: User
89
+ admin: Admin
90
+ foobar: # custom scope
91
+ user: The user
92
+ admin: The admin
93
+ ```
94
+
95
+ Then call `t` (or `translate`) method:
96
+
97
+ ```ruby
98
+ irb(main)> User::Role.t(:user) # Or `translate` method (alias)
99
+ => "translation missing: en.users.role.base.user"
100
+ ```
101
+
102
+ Use a different scope to have several translations for a single value, depending on context:
103
+
104
+ ```ruby
105
+ irb(main)> User::Role.t(:user, :foobar)
106
+ => "translation missing: en.users.role.foobar.user"
107
+ ```
108
+
109
+ Please note that the default scope (`base`) can be configured using the `default_i18n_scope` initializer option.
110
+
111
+ #### Validate translations presence
112
+
113
+ Validate that your enum are translated. By default, this feature is disabled.
114
+
115
+ ```ruby
116
+ class User < ApplicationRecord
117
+ nd_enum(role: %i(user admin), i18n: { mode: :log })
118
+ end
119
+ ```
120
+
121
+ It will log the missing translations for each scope & locale. For example:
122
+
123
+ ```
124
+ I, [2022-08-10T21:17:53.931669 #67401] INFO -- : ND::Enum: User#role scopes=[:base, :short]
125
+ I, [2022-08-10T21:17:53.931669 #67401] INFO -- : ND::Enum: User#role locale=en missing_keys=[]
126
+ I, [2022-08-10T21:17:53.931669 #67401] INFO -- : ND::Enum: User#role locale=nl missing_keys=["users.role.base.user", "users.role.short.user"]
127
+ ```
128
+
129
+ This mode can be used as default with the `c.default_i18n_validation_mode = :log` initializer option.
130
+
131
+ #### Enforce translations presence
132
+
133
+ Raise an exception when some translations are missing.
134
+
135
+ ```ruby
136
+ class User < ApplicationRecord
137
+ nd_enum(role: %i(user admin), i18n: { mode: :enforce })
138
+ end
139
+ ```
140
+
141
+ ```
142
+ (irb):1:in `<main>': One or several translations are missing (ND::Enum::MissingTranslationError)
143
+ from bin/console:15:in `<main>'
144
+ ```
145
+
146
+ This mode can be used as default with the `c.default_i18n_validation_mode = :enforce` initializer option.
147
+
148
+ ### `ActiveRecord` Enum
149
+
150
+ Add a wrapper to [`ActiveRecord` Enum](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html) by specifying the `db: true` option.
151
+
152
+ ```ruby
153
+ class User < ApplicationRecord
154
+ nd_enum(role: %i(user admin), db: true)
155
+ end
156
+
157
+ # It does exactly the same thing than below, but shorter:
158
+
159
+ class User < ApplicationRecord
160
+ nd_enum(role: %i(user admin))
161
+ enum(role: User::Role.to_h) # Or `enum(role: { user: 'user', admin: 'admin '})`
162
+ end
163
+ ```
164
+
165
+ It allows to use these methods:
166
+
167
+ ```ruby
168
+ user.admin!
169
+ user.admin? # => true
170
+ user.role # => "admin"
171
+ ```
172
+
173
+ And these scopes:
174
+
175
+ ```ruby
176
+ User.admin
177
+ User.not_admin
178
+
179
+ User.user
180
+ User.not_user
181
+
182
+ # ...
183
+ ```
184
+
185
+ Disable scope definition by setting `scopes: false` to your enum:
186
+
187
+ ```ruby
188
+ class User < ApplicationRecord
189
+ nd_enum(role: %i(user admin), db: { scopes: false })
190
+ end
191
+ ```
192
+
193
+ Set the default enum:
194
+
195
+ ```ruby
196
+ class User < ApplicationRecord
197
+ nd_enum(role: %i(user admin), db: { default: :admin })
198
+ end
199
+ ```
200
+
201
+ Add a `prefix` or `suffix` option when you need to define multiple enums with same values. If the passed value is true, the methods are prefixed/suffixed with the name of the enum. It is also possible to supply a custom value:
202
+
203
+ ```ruby
204
+ class User < ApplicationRecord
205
+ nd_enum(role: %i(user admin), db: { prefix: true })
206
+
207
+ # Scopes: `User.role_admin`, `User.role_user` ...
208
+ # Methods: `User.role_admin!`, `User.role_user!` ...
209
+
210
+ nd_enum(role: %i(user admin), db: { suffix: true })
211
+
212
+ # Scopes: `User.admin_role`, `User.user_role` ...
213
+ # Methods: `User.admin_role!`, `User.user_role!` ...
214
+
215
+ nd_enum(role: %i(user admin), db: { prefix: 'foobar' })
216
+
217
+ # Scopes: `User.foobar_admin`, `User.foobar_user` ...
218
+ # Methods: `User.foobar_admin!`, `User.foobar_user!` ...
219
+ end
220
+ ```
221
+
222
+ ## Development
223
+
224
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
225
+
226
+ Guard is also installed: `bundle exec guard`.
227
+
228
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, run `gem bump` (or manually update the version number in `version.rb`), and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
229
+
230
+ ## Contributing
231
+
232
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rclavel/nd-enum.
233
+
234
+ ## License
235
+
236
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'active_support'
5
+
6
+ module ND::Enum::Base
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class << self
11
+ include Enumerable
12
+ extend Forwardable
13
+
14
+ def_delegators :all, :size, :length, :[], :empty?, :last, :index
15
+
16
+ def each(&block)
17
+ all.each(&block)
18
+ end
19
+
20
+ def to_h
21
+ all.map { |value| [value.to_sym, value] }.to_h
22
+ end
23
+
24
+ def [](value)
25
+ value.is_a?(Integer) ? all[value] : to_h[value.to_sym]
26
+ end
27
+
28
+ def t(value, scope = nil)
29
+ scope ||= configuration.default_i18n_scope
30
+ ::I18n.t(value, scope: "#{model.table_name}.#{attribute}.#{scope}")
31
+ end
32
+ alias_method :translate, :t
33
+
34
+ private
35
+
36
+ def configuration
37
+ ND::Enum.configuration
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ND::Enum::Configuration
4
+ Configuration = Struct.new(:default_i18n_validation_mode, :default_i18n_scope)
5
+ DEFAULT_CONFIGURATION = {
6
+ default_i18n_validation_mode: :ignore,
7
+ default_i18n_scope: :base,
8
+ }
9
+
10
+ def configuration
11
+ @_configuration ||= Configuration.new(*DEFAULT_CONFIGURATION.values_at(*Configuration.members))
12
+ end
13
+
14
+ def configure
15
+ yield(configuration)
16
+ end
17
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ND::Enum::I18n
4
+ class << self
5
+ # TODO: Transform into a nd_enum
6
+ module Mode
7
+ LOG = 'log'
8
+ ENFORCE = 'enforce'
9
+ IGNORE = 'ignore'
10
+
11
+ def self.all
12
+ [LOG, ENFORCE, IGNORE]
13
+ end
14
+ end
15
+
16
+ def validate!(options)
17
+ mode = get_mode_from_options(options)
18
+ return if mode == Mode::IGNORE
19
+
20
+ i18n_scope = build_i18n_scope(options)
21
+ scopes = get_scopes_from_translations(i18n_scope)
22
+ missing_keys_by_locale = get_missing_keys_by_locale(options, i18n_scope, scopes)
23
+
24
+ log_missing_keys(options, scopes, missing_keys_by_locale)
25
+
26
+ if mode == Mode::ENFORCE && missing_keys_by_locale.values.any?(&:present?)
27
+ raise ND::Enum::MissingTranslationError
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def get_mode_from_options(options)
34
+ case options.dig(:i18n, :validate)
35
+ when 'log', :log then Mode::LOG
36
+ when 'enforce', :enforce then Mode::ENFORCE
37
+ else
38
+ default_mode = configuration.default_i18n_validation_mode&.to_s
39
+ Mode.all.include?(default_mode) ? default_mode : Mode::IGNORE
40
+ end
41
+ end
42
+
43
+ def build_i18n_scope(options)
44
+ "#{options[:model].table_name}.#{options[:attribute]}"
45
+ end
46
+
47
+ def get_scopes_from_translations(i18n_scope)
48
+ scopes = %i(base)
49
+
50
+ I18n.available_locales.each do |locale|
51
+ configuration = I18n.t(i18n_scope, locale: locale, default: {})
52
+ configuration.each_key do |scope|
53
+ scopes << scope.to_sym unless scopes.include?(scope.to_sym)
54
+ end
55
+ end
56
+
57
+ scopes
58
+ end
59
+
60
+ def get_missing_keys_by_locale(options, i18n_scope, scopes)
61
+ I18n.available_locales.each_with_object({}) do |locale, missing_keys_by_locale|
62
+ missing_keys = []
63
+
64
+ scopes.each do |scope|
65
+ options[:values].each do |value|
66
+ value_i18n_scope = "#{i18n_scope}.#{scope}.#{value}"
67
+ next if I18n.exists?(value_i18n_scope, locale: locale)
68
+
69
+ missing_keys << value_i18n_scope
70
+ end
71
+ end
72
+
73
+ missing_keys_by_locale[locale] = missing_keys
74
+ end
75
+ end
76
+
77
+ def log_missing_keys(options, scopes, missing_keys_by_locale)
78
+ prefix = "ND::Enum: #{options[:model_name]}##{options[:attribute]}"
79
+ logger.info("#{prefix} scopes=#{scopes}")
80
+
81
+ missing_keys_by_locale.each do |locale, missing_keys|
82
+ logger.info("#{prefix} locale=#{locale} missing_keys=#{missing_keys}")
83
+ end
84
+ end
85
+
86
+ def logger
87
+ @_logger ||= Logger.new(STDOUT)
88
+ end
89
+
90
+ def configuration
91
+ ND::Enum.configuration
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ND
4
+ module Enum
5
+ VERSION = '0.2.0'
6
+ end
7
+ end
data/lib/nd/enum.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require 'active_record/enum.rb'
5
+
6
+ require 'active_support'
7
+ require 'active_support/core_ext/string/inflections.rb'
8
+
9
+ require_relative 'enum/version'
10
+ require_relative 'enum/base'
11
+ require_relative 'enum/configuration'
12
+ require_relative 'enum/i18n'
13
+
14
+ module ND::Enum
15
+ extend ActiveSupport::Concern
16
+
17
+ included do
18
+ def self.nd_enum(db: false, i18n: {}, model: self, model_name: nil, **configuration)
19
+ options = ND::Enum.set_options(binding, model, model_name)
20
+ enum_module = ND::Enum.define_module(options)
21
+
22
+ ND::Enum.define_db_enum(options, enum_module) if options[:db]
23
+ ND::Enum::I18n.validate!(options)
24
+
25
+ const_set(options[:attribute].to_s.camelize, enum_module)
26
+ end
27
+ end
28
+
29
+ class Error < StandardError; end
30
+ class MissingTranslationError < Error
31
+ def message
32
+ 'One or several translations are missing'
33
+ end
34
+ end
35
+
36
+ class << self
37
+ include ND::Enum::Configuration
38
+
39
+ def set_options(caller_binding, caller_class, caller_class_name)
40
+ options = caller_class.method(:nd_enum).parameters.each_with_object({}) do |(_, name), options|
41
+ options[name.to_sym] = caller_binding.local_variable_get(name)
42
+ end
43
+ options[:attribute], options[:values] = options.delete(:configuration).to_a.first
44
+ options[:model] = caller_class
45
+ options[:model_name] = caller_class_name
46
+
47
+ options
48
+ end
49
+
50
+ def define_module(options)
51
+ Module.new do
52
+ include ND::Enum::Base
53
+
54
+ # Public methods
55
+
56
+ define_singleton_method(:all) { options[:values] }
57
+
58
+ # Private methods
59
+
60
+ define_singleton_method(:options) { options }
61
+ options.each_key do |name|
62
+ define_singleton_method(name) { options[name] }
63
+ end
64
+
65
+ [:options, *options.keys].each do |method_name|
66
+ singleton_class.class_eval { private method_name.to_sym }
67
+ end
68
+
69
+ # Constants
70
+
71
+ options[:values].map do |value|
72
+ const_set(value.to_s.upcase, value.to_s)
73
+ end
74
+ end
75
+ end
76
+
77
+ def define_db_enum(options, enum_module)
78
+ enum_options = options[:db].is_a?(Hash) ? options[:db] : {}
79
+
80
+ enum_options[:_prefix] = enum_options.delete(:prefix) if enum_options.key?(:prefix)
81
+ enum_options[:_suffix] = enum_options.delete(:suffix) if enum_options.key?(:suffix)
82
+
83
+ options[:model].enum(options[:attribute] => enum_module.to_h, **enum_options)
84
+ end
85
+ end
86
+ end
87
+
88
+ class ActiveRecord::Base
89
+ include ND::Enum
90
+ end
data/nd-enum.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/nd/enum/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'nd-enum'
7
+ spec.version = ND::Enum::VERSION
8
+ spec.authors = ['Romain Clavel']
9
+ spec.email = ['romain@clavel.io']
10
+
11
+ spec.summary = 'Powerful enum for Rails models.'
12
+ spec.homepage = 'https://github.com/rclavel/nd-enum'
13
+ spec.license = 'MIT'
14
+ spec.required_ruby_version = '>= 2.6.0'
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/rclavel/nd-enum'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/rclavel/nd-enum/blob/master/CHANGELOG.md'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ # Uncomment to register a new dependency of your gem
32
+ spec.add_dependency 'activesupport', '>= 6.0.0'
33
+ spec.add_dependency 'activerecord', '>= 6.0.0'
34
+
35
+ # For more information and examples about making a new gem, check out our
36
+ # guide at: https://bundler.io/guides/creating_gem.html
37
+ end
data/sig/nd/enum.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module ND
2
+ module Enum
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata CHANGED
@@ -1,22 +1,65 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nd-enum
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
  - Romain Clavel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-08 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 6.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 6.0.0
13
41
  description:
14
42
  email:
15
43
  - romain@clavel.io
16
44
  executables: []
17
45
  extensions: []
18
46
  extra_rdoc_files: []
19
- files: []
47
+ files:
48
+ - ".rspec"
49
+ - CHANGELOG.md
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - Guardfile
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - lib/nd/enum.rb
57
+ - lib/nd/enum/base.rb
58
+ - lib/nd/enum/configuration.rb
59
+ - lib/nd/enum/i18n.rb
60
+ - lib/nd/enum/version.rb
61
+ - nd-enum.gemspec
62
+ - sig/nd/enum.rbs
20
63
  homepage: https://github.com/rclavel/nd-enum
21
64
  licenses:
22
65
  - MIT