enum_ext 0.2.1 → 0.3.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: 4e95f6a4c82487df31be4eb7e31a4f310292c590
4
- data.tar.gz: 367ea13f066e7a7fe7b00ad790d36983a5b6e000
3
+ metadata.gz: 8d87b2abcd1fc85e6b40920b0623a190a8b6ae8e
4
+ data.tar.gz: cfdcb6362c984922ef6047a4ec719363312d8b26
5
5
  SHA512:
6
- metadata.gz: 8032fae12c639e80edfdf9940da399660cb3f9d0c64f096d8a8ae33d8139eb250071bbc8dc4e97ebfdfc1a041196b8fcd13df3fde45f9867d9932eaecc53ab20
7
- data.tar.gz: d82b610d27f075c96d94e3457338ba394710e94296ac5e31c947423eb00accfc1a02f6e0123948cf782d8250d24b07d42ba9679817b5a5de147494e53fa6539a
6
+ metadata.gz: e921603dd1328763efef7dbd5ac36e335037d4d381e4361c3c5dcf2453503e326876a42935ad679e257e6109e6d74b5aee49782047835eb70f3880c34ef76ff2
7
+ data.tar.gz: f6ecf63202ba792af34678ecae8f3bc73ca051894e931aebe21b2eb1de4887577f4728e06727a6e814c318a761b2b52bfdb41c8b47226abce5682db4d1676c6f
data/Gemfile.lock CHANGED
@@ -1,33 +1,70 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- enum_ext (0.1.8)
4
+ enum_ext (0.3.0)
5
5
  activerecord (>= 4.1)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (4.2.5)
11
- activesupport (= 4.2.5)
10
+ actionpack (5.0.2)
11
+ actionview (= 5.0.2)
12
+ activesupport (= 5.0.2)
13
+ rack (~> 2.0)
14
+ rack-test (~> 0.6.3)
15
+ rails-dom-testing (~> 2.0)
16
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
17
+ actionview (5.0.2)
18
+ activesupport (= 5.0.2)
12
19
  builder (~> 3.1)
13
- activerecord (4.2.5)
14
- activemodel (= 4.2.5)
15
- activesupport (= 4.2.5)
16
- arel (~> 6.0)
17
- activesupport (4.2.5)
20
+ erubis (~> 2.7.0)
21
+ rails-dom-testing (~> 2.0)
22
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
23
+ activemodel (5.0.2)
24
+ activesupport (= 5.0.2)
25
+ activerecord (5.0.2)
26
+ activemodel (= 5.0.2)
27
+ activesupport (= 5.0.2)
28
+ arel (~> 7.0)
29
+ activesupport (5.0.2)
30
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
31
  i18n (~> 0.7)
19
- json (~> 1.7, >= 1.7.7)
20
32
  minitest (~> 5.1)
21
- thread_safe (~> 0.3, >= 0.3.4)
22
33
  tzinfo (~> 1.1)
23
- arel (6.0.3)
24
- builder (3.2.2)
25
- i18n (0.7.0)
26
- json (1.8.3)
34
+ arel (7.1.4)
35
+ builder (3.2.3)
36
+ concurrent-ruby (1.0.5)
37
+ erubis (2.7.0)
38
+ i18n (0.8.1)
39
+ loofah (2.0.3)
40
+ nokogiri (>= 1.5.9)
41
+ method_source (0.8.2)
42
+ mini_portile2 (2.1.0)
27
43
  minitest (5.8.3)
44
+ nokogiri (1.7.1)
45
+ mini_portile2 (~> 2.1.0)
46
+ rack (2.0.1)
47
+ rack-test (0.6.3)
48
+ rack (>= 1.0)
49
+ rails-dom-testing (2.0.2)
50
+ activesupport (>= 4.2.0, < 6.0)
51
+ nokogiri (~> 1.6)
52
+ rails-html-sanitizer (1.0.3)
53
+ loofah (~> 2.0)
54
+ rails-i18n (5.0.3)
55
+ i18n (~> 0.7)
56
+ railties (~> 5.0)
57
+ railties (5.0.2)
58
+ actionpack (= 5.0.2)
59
+ activesupport (= 5.0.2)
60
+ method_source
61
+ rake (>= 0.8.7)
62
+ thor (>= 0.18.1, < 2.0)
28
63
  rake (10.4.2)
29
- thread_safe (0.3.5)
30
- tzinfo (1.2.2)
64
+ sqlite3 (1.3.13)
65
+ thor (0.19.4)
66
+ thread_safe (0.3.6)
67
+ tzinfo (1.2.3)
31
68
  thread_safe (~> 0.1)
32
69
 
33
70
  PLATFORMS
@@ -36,7 +73,10 @@ PLATFORMS
36
73
  DEPENDENCIES
37
74
  bundler (~> 1.11)
38
75
  enum_ext!
76
+ minitest
77
+ rails-i18n (>= 4)
39
78
  rake (~> 10.0)
79
+ sqlite3
40
80
 
41
81
  BUNDLED WITH
42
- 1.11.2
82
+ 1.14.6
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # EnumExt
2
2
 
3
- EnumExt extends rails enum adding localization template, mass-assign on scopes with bang and some sets logic over existing enum.
3
+ EnumExt extends rails enum adding localization/translation and it's helpers, mass-assign on scopes with bang, advanced sets logic over existing.
4
4
 
5
5
  ## Installation
6
6
 
@@ -24,18 +24,20 @@ Or install it yourself as:
24
24
  class SomeModel
25
25
  extend EnumExt
26
26
 
27
+ enum_i ...
27
28
  humanize_enum ...
28
29
  translate_enum ...
29
30
  ext_enum_sets ...
30
31
  mass_assign_enum ...
31
32
  end
32
33
 
33
- Let's assume that we have model Request representing some buying requests with enum **status**, and we have model Order with requests, representing single purchase, like this:
34
+ Let's assume that we have model Request representing some buying requests with enum **status**, and we have model Order with requests,
35
+ representing single purchase, like this:
34
36
 
35
37
  class Request
36
38
  extend EnumExt
37
39
  belongs_to :order
38
- enum status: [ :in_cart, :waiting_for_payment, :payed, :ready_for_shipment, :on_delivery, :delivered ]
40
+ enum status: [ :in_cart, :waiting_for_payment, :paid, :ready_for_shipment, :on_delivery, :delivered ]
39
41
  end
40
42
 
41
43
  class Order
@@ -46,48 +48,65 @@ Or install it yourself as:
46
48
 
47
49
  ### Humanization (humanize_enum)
48
50
 
49
- class Request
50
- ...
51
- localize_enum :status, {
52
-
53
- #locale dependent example with internal pluralization and lambda:
54
- payed: -> (t_self) { I18n.t("request.status.payed", count: t_self.sum ) }
55
-
56
- #locale dependent example with internal pluralization and proc:
57
- payed: proc { I18n.t("request.status.payed", count: sum ) }
58
-
59
- #locale independent:
60
- ready_for_shipment: "Ready to go!"
61
- }
62
- end
63
-
64
- Console:
65
-
66
- request.sum = 3
67
- request.payed!
68
- request.status # >> payed
69
- request.t_status # >> "Payed 3 dollars"
70
- Request.t_statuses # >> { in_cart: -> { I18n.t("request.status.in_cart") }, .... }
71
-
72
- If you need some substitution you can go like this:
51
+ if app doesn't need internationalization, it may use humanize_enum to make enum user friendly
73
52
 
74
- localize_enum :status, {
75
- ..
76
- delivered: "Delivered at: %{date}"
77
- }
78
- request.delivered!
79
- request.t_status % {date: Time.now.to_s} >> Delivered at: 05.02.2016
80
-
81
- If you need select status on form:
82
- f.select :status, Request.t_statuses_options
53
+ ```
54
+ humanize_enum :status, {
55
+ #locale dependent example with pluralization and lambda:
56
+ in_cart: -> (t_self) { I18n.t("request.status.in_cart", count: t_self.sum ) }
57
+
58
+ #locale dependent example with pluralization and proc:
59
+ paid: Proc.new{ I18n.t("request.status.paid", count: self.sum ) }
60
+
61
+ #locale independent:
62
+ ready_for_shipment: "Ready to go!"
63
+ }
64
+ end
65
+ ```
66
+
67
+ This call adds to instance:
68
+ - t_in_cart, t_paid, t_ready_for_shipment
69
+
70
+ adds to class:
71
+ - t_statuses - as given or generated values
72
+ - t_statuses_options - translated enum values options for select input
73
+ - t_statuses_options_i - same as above but use int values with translations works for ActiveAdmin filters for instance
83
74
 
84
- Works with ext_enum_sets, slicing t_enum_set from original set of enum values ( enum - status, set_name - delivery_set )
85
-
86
- f.select :status, Request.t_delivery_set_statuses_options
75
+
76
+ Example with block:
87
77
 
78
+ ```
79
+ humanize_enum :status do
80
+ I18n.t("scope.#{status}")
81
+ end
82
+ ```
83
+
84
+ Example for select:
85
+
86
+ ```
87
+ f.select :status, Request.t_statuses_options
88
+ ```
89
+
90
+ in Active Admin filters
91
+ ```
92
+ filter :status, as: :select, label: 'Status', collection: Request.t_statuses_options_i
93
+ ```
94
+
95
+
96
+ Rem: select options may break when using lambda() or proc with instance method, but will survive with block
97
+
98
+ Console:
99
+ ```
100
+ request.sum = 3
101
+ request.paid!
102
+ request.status # >> paid
103
+ request.t_status # >> "paid 3 dollars"
104
+ Request.t_statuses # >> { in_cart: -> { I18n.t("request.status.in_cart") }, .... }
105
+ ```
106
+
88
107
  ### Translate (translate_enum)
89
108
 
90
- Enum is translated using scope 'active_record.attributes.class_name_underscore.enum', or the given one:\
109
+ Enum is translated using scope 'active_record.attributes.class_name_underscore.enum_plural', or the given one:
91
110
 
92
111
  translate_enum :status, 'active_record.request.enum'
93
112
 
@@ -97,9 +116,6 @@ Or it can be done with block either with translate or humanize:
97
116
  I18n.t( "active_record.request.enum.#{status}" )
98
117
  end
99
118
 
100
- Also since we place by default enum translation in same place as enum name translation
101
- human_attribute_name is redefined so it will work fine in ActiveAdmin, but you need to add translation to locale.
102
-
103
119
  ### Enum to_i shortcut ( enum_i )
104
120
 
105
121
  Defines method enum_name_i shortcut for Model.enum_names[elem.enum_name]
@@ -107,125 +123,101 @@ Defines method enum_name_i shortcut for Model.enum_names[elem.enum_name]
107
123
  **Ex**
108
124
  enum_i :status
109
125
  ...
110
- request.payed_i # 10
126
+ request.paid_i # 10
111
127
 
112
128
 
113
129
  ### Enum Sets (ext_enum_sets)
114
130
 
115
- **Use-case** For example you have pay bills of different types, and you want to group some types in debit and credit "super-types", and have scope PayBill.debit, instance method with question mark as usual enum does pay_bill.debit?.
131
+ **Use-case** For example you have pay bills of different types, and you want to group some types in debit and credit "super-types",
132
+ and have scope PayBill.debit, instance method with question mark as usual enum does pay_bill.debit?.
116
133
 
117
- You can do this with method **ext_enum_sets**, it creates: scopes for subsets like enum did, instance method with ? similar to enum methods, and so...
134
+ You can do this with method **ext_enum_sets** it creates: scopes for subsets, instance method with ? and some class methods helpers
135
+
136
+ For this call:
137
+ ```
138
+ ext_enum_sets :status, {
139
+ delivery_set: [:ready_for_shipment, :on_delivery, :delivered] # for shipping department for example
140
+ in_warehouse: [:ready_for_shipment] # this just for superposition example below
141
+ }
142
+ ```
143
+
144
+ it will generate:
145
+ instance:
146
+ methods: delivery_set?, in_warehouse?
147
+ class:
148
+ named scopes: delivery_set, in_warehouse
149
+ parametrized scopes: with_statuses, without_statuses
150
+ class helpers:
151
+ - delivery_set_statuses (=[:ready_for_shipment, :on_delivery, :delivered] ), in_warehouse_statuses
152
+ - delivery_set_statuses_i (= [3,4,5]), in_warehouse_statuses_i (=[3])
153
+ class translation helpers ( started with t_... )
154
+ for select inputs purposes:
155
+ - t_delivery_set_statuses_options (= [['translation or humanization', :ready_for_shipment] ...])
156
+ same as above but with integer as value ( for example to use in Active admin filters )
157
+ - t_delivery_set_statuses_options_i (= [['translation or humanization', 3] ...])
158
+ ```
159
+ Console:
160
+ request.on_delivery!
161
+ request.delivery_set? # >> true
118
162
 
119
- I strongly recommend you to create special comment near method call, to remember what methods will be defined on instance, on class itself, and what scopes will be defined
120
-
121
- class Request
122
- ...
123
- #instance methods: non_payed?, delivery_set?, in_warehouse?
124
- #scopes: non_payed, delivery_set, in_warehouse
125
- #scopes: with_statuses, without_statuses
126
- #class methods: non_payed_statuses, delivery_set_statuses ( = [:in_cart, :waiting_for_payment], [:ready_for_shipment, :on_delivery, :delivered].. )
127
- #class methods: t_non_payed_statuses, t_delivery_set_statuses ( = {in_cart: "In cart localization" ...} )
128
-
129
- ext_enum_sets :status, {
130
- non_payed: [:in_cart, :waiting_for_payment],
131
- delivery_set: [:ready_for_shipment, :on_delivery, :delivered] #for shipping department for example
132
- in_warehouse: [:ready_for_shipment] #it's just for example below
133
- }
134
- end
135
-
136
- Console:
137
-
138
- request.waiting_for_payment!
139
- request.non_payed? # >> true
140
-
141
- Request.non_payed.exists?(request) # >> true
142
- Request.delivery_set.exists?(request) # >> false
143
-
144
- Request.non_payed_statuses # >> [:in_cart, :waiting_for_payment]
145
-
146
- Request.with_statuses( :payed, :in_cart ) # >> scope for all in_cart and payed requests
147
- Request.without_statuses( :payed ) # >> scope for all requests with statuses not eq to payed
148
- Request.without_statuses( :payed, :non_payed ) # >> scope all requests with statuses not eq to payed and in_cart + waiting_for_payment
149
-
150
-
151
- #### Rem:
152
-
153
- You can call ext_enum_sets more than one time defining a superposition of already defined sets:
154
-
155
- class Request
156
- ...
157
- ext_enum_sets (... first time you call ext_enum_sets )
158
- ext_enum_sets :status, {
159
- already_payed: ( [:payed] | delivery_set_statuses ),
160
- outside_wharehouse: ( delivery_set_statuses - in_warehouse_statuses )... # any other array operations like &, + and so can be used
161
- }
162
-
163
+ Request.delivery_set.exists?(request) # >> true
164
+ Request.in_warehouse.exists?(request) # >> false
165
+
166
+ Request.delivery_set_statuses # >> [:ready_for_shipment, :on_delivery, :delivered]
167
+
168
+ Request.with_statuses( :payed, :delivery_set ) # >> :payed and [:ready_for_shipment, :on_delivery, :delivered] requests
169
+ Request.without_statuses( :payed ) # >> scope for all requests with statuses not eq to :payed
170
+ Request.without_statuses( :payed, :in_warehouse ) # >> scope all requests with statuses not eq to :payed or :ready_for_shipment
171
+ ```
172
+
173
+ Rem:
174
+ ext_enum_sets can be called twice defining a superposition of already defined sets ( considering previous example ):
175
+
176
+ ```
177
+ ext_enum_sets :status, {
178
+ outside_wharehouse: ( delivery_set_statuses - in_warehouse_statuses )... any other array operations like &, + and so can be used
179
+ }
180
+ ```
163
181
 
182
+
164
183
  ### Mass-assign ( mass_assign_enum )
165
184
 
166
185
  Syntax sugar for mass-assigning enum values.
167
186
 
168
- **Use-case:** it's often case when I need bulk update without callbacks, so it's gets frustrating to repeat: some_scope.update_all(status: Request.statuses[:new_status], update_at: Time.now)
169
- If you need callbacks you can do like this: some_scope.each(&:new_stat!) but if you don't need callbacks and you has hundreds and thousands of records to change at once you need update_all
187
+ **Use-case:** it's often case when I need bulk update without callbacks, so it's gets frustrating to repeat:
188
+ ```
189
+ some_scope.update_all(status: Request.statuses[:new_status], update_at: Time.now)
190
+ ```
191
+ If you need callbacks you can do like this: some_scope.each(&:new_stat!) but if you don't need callbacks and you
192
+ has hundreds and thousands of records to change at once you need update_all
170
193
 
171
- class Request
172
- ...
173
- mass_assign_enum( :status )
174
- end
194
+ ```
195
+ mass_assign_enum( :status )
196
+ ```
175
197
 
176
198
  Console:
177
-
178
- request1.in_cart!
179
- request2.waiting_for_payment!
180
- Request.non_payed.payed!
181
- request1.payed? # >> true
182
- request2.payed? # >> true
183
- request1.updated_at # >> ~ Time.now
184
- defined?(Request::MassAssignEnum) # >> true
185
-
186
-
187
- order.requests.already_payed.count # >> N
188
- order.requests.delivered.count # >> M
189
- order.requests.already_payed.delivered!
190
- order.requests.already_payed.count # >> 0
191
- order.requests.delivered.count # >> N + M
192
-
193
-
194
-
195
- ####Rem:
196
-
197
- **mass_assign_enum** accepts additional options as last argument. Calling
198
-
199
- mass_assign_enum( :status )
200
-
201
- actually is equal to call:
202
-
203
- mass_assign_enum( :status, { relation: true, association_relation: true } )
204
-
205
- ###### Meaning:
206
-
207
- relation: true - Request.some_scope.payed! - works
208
-
209
- association_relation: true - Order.first.requests.scope.new_stat! - works
210
-
211
- **but it wouldn't work without 'scope' part!** If you want to use it without 'scope' you may do it this way:
212
-
213
- class Request
214
- ...
215
- mass_assign_enum( :status, relation: true, association_relation: false )
216
- end
217
199
 
218
- class Order
219
- has_many :requests, extend: Request::MassAssignEnum
220
- end
200
+ ```
201
+ request1.in_cart!
202
+ request2.waiting_for_payment!
203
+ Request.non_paid.paid!
204
+ request1.paid? # >> true
205
+ request2.paid? # >> true
206
+ request1.updated_at # >> ~ Time.now
207
+ defined?(Request::MassAssignEnum) # >> true
208
+
221
209
 
222
- Order.first.requests.respond_to?(:in_cart!) # >> true
210
+ order.requests.already_paid.count # >> N
211
+ order.requests.delivered.count # >> M
212
+ order.requests.already_paid.delivered!
213
+ order.requests.already_paid.count # >> 0
214
+ order.requests.delivered.count # >> N + M
215
+ ```
216
+
223
217
 
224
- #### Rem2:
225
- You can mass-assign more than one enum ::MassAssignEnum module will contain mass assign for both. It will break nothing since all enum name must be uniq across model
226
218
 
227
219
  ## Tests
228
- Right now goes without automated tests :(
220
+ rake test
229
221
 
230
222
  ## Development
231
223
 
@@ -239,3 +231,6 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/alekse
239
231
 
240
232
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
241
233
 
234
+ ### Thanks
235
+
236
+ Thanks for the star vzamanillo, it inspires me to do mass refactor and gracefully cover code in this gem by tests.
data/Rakefile CHANGED
@@ -1,2 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
- task :default => :spec
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "enum_ext"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/enum_ext.gemspec CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["alekseyl"]
10
10
  spec.email = ["leshchuk@gmail.com"]
11
11
 
12
- spec.summary = %q{Enum extension, ads enum sets, mass-assign for them, localization for them.}
13
- spec.description = %q{Enum extension, ads enum sets, mass-assign for them, localization for them.}
12
+ spec.summary = %q{Enum extension, ads enum sets, mass-assign, localization, and some sugar helpers.}
13
+ spec.description = %q{Enum extension, ads enum sets, mass-assign, localization, and some sugar helpers.}
14
14
  spec.homepage = "https://github.com/alekseyl/enum_ext"
15
15
  spec.license = "MIT"
16
16
 
@@ -19,8 +19,11 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "activerecord", ">=4.1"
22
+ spec.add_dependency 'activerecord', '>=4.1'
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.11"
25
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency 'minitest'
25
+ spec.add_development_dependency 'bundler', '~> 1.11'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rails-i18n', '>=4'
28
+ spec.add_development_dependency 'sqlite3'
26
29
  end
@@ -1,3 +1,3 @@
1
1
  module EnumExt
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/enum_ext.rb CHANGED
@@ -1,10 +1,10 @@
1
- require "enum_ext/version"
1
+ require 'enum_ext/version'
2
2
 
3
3
  # Let's assume we have model Request with enum status, and we have model Order with requests like this:
4
4
  # class Request
5
5
  # extend EnumExt
6
6
  # belongs_to :order
7
- # enum status: [ :in_cart, :waiting_for_payment, :payed, :ready_for_shipment, :on_delivery, :delivered ]
7
+ # enum status: { in_cart: 0, waiting_for_payment: 1, payed: 2, ready_for_shipment: 3, on_delivery: 4, delivered: 5 }
8
8
  # end
9
9
  #
10
10
  # class Order
@@ -13,6 +13,9 @@ require "enum_ext/version"
13
13
  #
14
14
  module EnumExt
15
15
 
16
+ # defines shortcut for getting integer value of enum.
17
+ # for enum named status will generate:
18
+ # instance.status_i
16
19
  def enum_i( enum_name )
17
20
  define_method "#{enum_name}_i" do
18
21
  self.class.send("#{enum_name.to_s.pluralize}")[send(enum_name)].to_i
@@ -20,175 +23,158 @@ module EnumExt
20
23
  end
21
24
 
22
25
 
23
- # Ex ext_enum_sets
24
- # This method intend for creating and using some sets of enum values with similar to original enum syntax
25
- # it creates: scopes for subsets like enum did, instance method with ? similar to enum methods, and methods like Request.statuses
26
- # Usually I supply comment near method call to remember what methods will be defined
27
- # class Request
28
- # ...
29
- # #instance non_payed?, delivery_set?, in_warehouse?
30
- # #class scopes: non_payed, delivery_set, in_warehouse
31
- # #class scopes: with_statuses, without_statuses
32
- # #class non_payed_statuses, delivery_set_statuses ( = [:in_cart, :waiting_for_payment], [:ready_for_shipment, :on_delivery, :delivered].. )
26
+ # ext_enum_sets
27
+ # This method intend for creating and using some sets of enum values
28
+ # it creates: scopes for subsets,
29
+ # instance method with ?,
30
+ # and some class methods helpers
31
+ #
32
+ # For this call:
33
33
  # ext_enum_sets :status, {
34
- # non_payed: [:in_cart, :waiting_for_payment],
35
34
  # delivery_set: [:ready_for_shipment, :on_delivery, :delivered] # for shipping department for example
36
- # in_warehouse: [:ready_for_shipment] # it's just for example below
35
+ # in_warehouse: [:ready_for_shipment] # this just for superposition example below
37
36
  # }
38
- # end
37
+ #
38
+ # it will generate:
39
+ # instance:
40
+ # methods: delivery_set?, in_warehouse?
41
+ # class:
42
+ # named scopes: delivery_set, in_warehouse
43
+ # parametrized scopes: with_statuses, without_statuses
44
+ # class helpers:
45
+ # - delivery_set_statuses (=[:ready_for_shipment, :on_delivery, :delivered] ), in_warehouse_statuses
46
+ # - delivery_set_statuses_i (= [3,4,5]), in_warehouse_statuses_i (=[3])
47
+ # class translation helpers ( started with t_... )
48
+ # for select inputs purposes:
49
+ # - t_delivery_set_statuses_options (= [['translation or humanization', :ready_for_shipment] ...])
50
+ # same as above but with integer as value ( for example to use in Active admin filters )
51
+ # - t_delivery_set_statuses_options_i (= [['translation or humanization', 3] ...])
39
52
 
40
53
  # Console:
41
- # request.waiting_for_payment!
42
- # request.non_payed? # >> true
43
-
44
- # Request.non_payed.exists?(request) # >> true
45
- # Request.delivery_set.exists?(request) # >> false
54
+ # request.on_delivery!
55
+ # request.delivery_set? # >> true
46
56
 
47
- # Request.non_payed_statuses # >> [:in_cart, :waiting_for_payment]
57
+ # Request.delivery_set.exists?(request) # >> true
58
+ # Request.in_warehouse.exists?(request) # >> false
48
59
  #
49
- # Request.with_statuses( :payed, :in_cart ) # >> scope for all in_cart and payed requests
50
- # Request.without_statuses( :payed ) # >> scope for all requests with statuses not eq to payed
51
- # Request.without_statuses( :payed, :non_payed ) # >> scope all requests with statuses not eq to payed and in_cart + waiting_for_payment
60
+ # Request.delivery_set_statuses # >> [:ready_for_shipment, :on_delivery, :delivered]
61
+ #
62
+ # Request.with_statuses( :payed, :delivery_set ) # >> :payed and [:ready_for_shipment, :on_delivery, :delivered] requests
63
+ # Request.without_statuses( :payed ) # >> scope for all requests with statuses not eq to :payed
64
+ # Request.without_statuses( :payed, :in_warehouse ) # >> scope all requests with statuses not eq to :payed or :ready_for_shipment
52
65
  #
53
66
 
54
67
  #Rem:
55
- # ext_enum_sets can be called twice defining a superpositoin of already defined sets:
56
- # class Request
57
- # ...
58
- # ext_enum_sets (... first time call )
59
- # ext_enum_sets :status, {
60
- # already_payed: ( [:payed] | delivery_set_statuses ),
61
- # outside_wharehouse: ( delivery_set_statuses - in_warehouse_statuses )... any other array operations like &, + and so can be used
62
- # }
68
+ # ext_enum_sets can be called twice defining a superposition of already defined sets ( considering previous example ):
69
+ # ext_enum_sets :status, {
70
+ # outside_wharehouse: ( delivery_set_statuses - in_warehouse_statuses )... any other array operations like &, + and so can be used
71
+ # }
63
72
  def ext_enum_sets( enum_name, options )
64
73
  enum_plural = enum_name.to_s.pluralize
65
74
 
66
75
  self.instance_eval do
67
76
  options.each do |set_name, enum_vals|
77
+ # set_name scope
68
78
  scope set_name, -> { where( enum_name => self.send( enum_plural ).slice( *enum_vals.map(&:to_s) ).values ) }
69
79
 
80
+ # with_enums scope
81
+ scope "with_#{enum_plural}", -> (sets_arr) {
82
+ where( enum_name => self.send( enum_plural ).slice(
83
+ *sets_arr.map{|set_name| self.try( "#{set_name}_#{enum_plural}" ) || set_name }.flatten.uniq.map(&:to_s) ).values )
84
+ } unless respond_to?("with_#{enum_plural}")
85
+
86
+ # without_enums scope
87
+ scope "without_#{enum_plural}", -> (sets_arr) {
88
+ where.not( id: self.send("with_#{enum_plural}", sets_arr) )
89
+ } unless respond_to?("without_#{enum_plural}")
90
+
70
91
 
92
+ # class.enum_set_values
71
93
  define_singleton_method( "#{set_name}_#{enum_plural}" ) do
72
94
  enum_vals
73
95
  end
74
96
 
75
- # set?
76
- define_method "#{set_name}?" do
77
- self.send(enum_name) && ( enum_vals.include?( self.send(enum_name) ) || enum_vals.include?( self.send(enum_name).to_sym ))
78
- end
79
-
80
- # t_set_enums
81
- define_singleton_method( "t_#{set_name}_#{enum_plural}" ) do
82
- send( "t_#{enum_plural}" ).slice( *self.send("#{set_name}_#{enum_plural}") )
97
+ # class.enum_set_enums_i
98
+ define_singleton_method( "#{set_name}_#{enum_plural}_i" ) do
99
+ self.send( "#{enum_plural}" ).slice( *self.send("#{set_name}_#{enum_plural}") ).values
83
100
  end
84
101
 
85
- # t_set_enums_options
102
+ # t_... - are translation dependent methods
103
+ # class.t_enums_options
86
104
  define_singleton_method( "t_#{set_name}_#{enum_plural}_options" ) do
87
- send( "t_#{set_name}_#{enum_plural}" ).invert.to_a.map do | key_val |
88
- key_val[0] = key_val[0].call if key_val[0].respond_to?(:call) && key_val[0].try(:arity) < 1
89
- key_val
90
- end
105
+ return [["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2] unless respond_to?( "t_#{enum_plural}_options_raw" )
106
+
107
+ send("t_#{enum_plural}_options_raw", send("t_#{set_name}_#{enum_plural}") )
91
108
  end
92
109
 
93
- # set_enums_i
94
- define_singleton_method( "#{set_name}_#{enum_plural}_i" ) do
95
- self.send( "#{enum_plural}" ).slice( *self.send("#{set_name}_#{enum_plural}") ).values
110
+ # class.t_enums_options_i
111
+ define_singleton_method( "t_#{set_name}_#{enum_plural}_options_i" ) do
112
+ return [["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2] unless respond_to?( "t_#{enum_plural}_options_raw_i" )
113
+
114
+ send("t_#{enum_plural}_options_raw_i", send("t_#{set_name}_#{enum_plural}") )
96
115
  end
97
116
 
98
- end
117
+ # instance.set_name?
118
+ define_method "#{set_name}?" do
119
+ self.send(enum_name) && ( enum_vals.include?( self.send(enum_name) ) || enum_vals.include?( self.send(enum_name).to_sym ))
120
+ end
99
121
 
100
- scope "with_#{enum_plural}", -> (sets_arr) {
101
- where( enum_name => self.send( enum_plural ).slice(
102
- *sets_arr.map{|set_name| self.try( "#{set_name}_#{enum_plural}" ) || set_name }.flatten.uniq.map(&:to_s) ).values )
103
- } unless respond_to?("with_#{enum_plural}")
122
+ # protected?
123
+ # class.t_setname_enums ( translations or humanizations subset for a given set )
124
+ define_singleton_method( "t_#{set_name}_#{enum_plural}" ) do
125
+ return [(["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2)].to_h unless respond_to?( "t_#{enum_plural}" )
104
126
 
105
- scope "without_#{enum_plural}", -> (sets_arr) {
106
- where.not( id: self.send("with_#{enum_plural}", sets_arr) )
107
- } unless respond_to?("without_#{enum_plural}")
127
+ send( "t_#{enum_plural}" ).slice( *self.send("#{set_name}_#{enum_plural}") )
128
+ end
129
+ end
108
130
  end
109
131
  end
110
132
 
111
133
  # Ex mass_assign_enum
112
- # Used for mass assigning for collection, it creates dynamically nested module with methods similar to enum bang methods, and includes it to relation classes
113
- # Behind the scene it creates bang methods for collections using update_all.
114
- # it's often case when I need bulk update without callbacks, so it's gets frustrating to repeat: some_scope.update_all(status: Request.statuses[:new_status], update_at: Time.now)
115
- # If you need callbacks you can do like this: some_scope.each(&:new_stat!) but if you don't need callbacks and you has hundreds and thousands of records to change at once you need update_all
134
+ # Used for mass assigning for collection without callbacks it creates bang methods for collections using update_all.
135
+ # it's often case when you need bulk update without callbacks, so it's gets frustrating to repeat:
136
+ # some_scope.update_all(status: Request.statuses[:new_status], update_at: Time.now)
137
+ # If you need callbacks you can do like this: some_scope.each(&:new_stat!) but if you don't need callbacks and you have lots of records
138
+ # to change at once you need update_all
116
139
  #
117
- # class Request
118
- # ...
119
- # mass_assign_enum( :status )
120
- # end
140
+ # mass_assign_enum( :status )
141
+ #
142
+ # class methods:
143
+ # in_cart! paid! in_warehouse! and so
121
144
  #
122
145
  # Console:
123
146
  # request1.in_cart!
124
147
  # request2.waiting_for_payment!
125
- # Request.non_payed.payed!
126
- # request1.payed? # >> true
127
- # request2.payed? # >> true
148
+ # Request.with_statuses( :in_cart, :waiting_for_payment ).payed!
149
+ # request1.paid? # >> true
150
+ # request2.paid? # >> true
128
151
  # request1.updated_at # >> Time.now
129
152
  # defined?(Request::MassAssignEnum) # >> true
130
153
  #
131
- # order.requests.already_payed.all?(&:already_payed?) # >> true
132
- # order.requests.already_payed.delivered!
154
+ # order.requests.paid.all?(&:paid?) # >> true
155
+ # order.requests.paid.delivered!
133
156
  # order.requests.map(&:status).uniq #>> [:delivered]
134
- #
135
- #
136
- # Rem:
137
- # mass_assign_enum accepts additional options as last argument.
138
- # calling mass_assign_enum( :status ) actually is equal to call: mass_assign_enum( :status, { relation: true, association_relation: true } )
139
- #
140
- # Meaning:
141
157
 
142
- # relation: true - Request.some_scope.payed! - works
143
-
144
- # association_relation: true - Order.first.requests.scope.new_stat! - works
145
- # but it wouldn't works without 'scope' part! If you want to use it without 'scope' you may do it this way:
146
- # class Request
147
- # ...
148
- # mass_assign_enum( :status, association_relation: false )
149
- # end
150
- # class Order
151
- # has_many :requests, extend: Request::MassAssignEnum
152
- # end
153
- #
154
- # Order.first.requests.respond_to?(:in_cart!) # >> true
155
- #
156
- # Rem2:
157
- # you can mass-assign more than one enum ::MassAssignEnum module will contain mass assign for both. It will break nothing since all enum name must be uniq across model
158
-
159
- def mass_assign_enum( *options )
160
- relation_options = (options[-1].is_a?(Hash) && options.pop || {relation: true, association_relation: true} ).with_indifferent_access
161
- enums_names = options
158
+ def mass_assign_enum( *enums_names )
162
159
  enums_names.each do |enum_name|
163
160
  enum_vals = self.send( enum_name.to_s.pluralize )
164
161
 
165
- mass_ass_module = ( defined?(self::MassAssignEnum) && self::MassAssignEnum || Module.new )
166
-
167
- mass_ass_module.instance_eval do
168
- enum_vals.keys.each do |enum_el|
169
- define_method( "#{enum_el}!" ) do
170
- self.update_all( {enum_name => enum_vals[enum_el]}.merge( self.column_names.include?('updated_at') ? {updated_at: Time.now} : {} ))
171
- end
162
+ enum_vals.keys.each do |enum_el|
163
+ define_singleton_method( "#{enum_el}!" ) do
164
+ self.update_all( {enum_name => enum_vals[enum_el]}.merge( self.column_names.include?('updated_at') ? {updated_at: Time.now} : {} ))
172
165
  end
173
166
  end
174
- self.const_set( :MassAssignEnum, mass_ass_module ) unless defined?(self::MassAssignEnum)
175
-
176
- self::ActiveRecord_Relation.include( self::MassAssignEnum ) if relation_options[:relation]
177
- self::ActiveRecord_AssociationRelation.include( self::MassAssignEnum ) if relation_options[:association_relation]
178
167
  end
179
168
  end
180
169
 
181
- # Ex using localize_enum with Request
182
- # class Request
183
- #
184
170
  # if app doesn't need internationalization, it may use humanize_enum to make enum user friendly
185
- #
171
+ # class Request
186
172
  # humanize_enum :status, {
187
173
  # #locale dependent example with pluralization and lambda:
188
174
  # payed: -> (t_self) { I18n.t("request.status.payed", count: t_self.sum ) }
189
175
  #
190
176
  # #locale dependent example with pluralization and proc:
191
- # payed: proc{ I18n.t("request.status.payed", count: self.sum ) }
177
+ # payed: Proc.new{ I18n.t("request.status.payed", count: self.sum ) }
192
178
  #
193
179
  # #locale independent:
194
180
  # ready_for_shipment: "Ready to go!"
@@ -200,48 +186,43 @@ module EnumExt
200
186
  # humanize_enum :status do
201
187
  # I18n.t("scope.#{status}")
202
188
  # end
203
-
189
+ #
190
+ # in select:
191
+ # f.select :status, Request.t_statuses_options
192
+ #
193
+ # in select in Active Admin filter
194
+ # collection: Request.t_statuses_options_i
195
+ #
196
+ # Rem: select options breaks when using lambda() with params
197
+ #
204
198
  # Console:
205
199
  # request.sum = 3
206
200
  # request.payed!
207
- # request.status # >> payed
208
- # request.t_status # >> "Payed 3 dollars"
201
+ # request.status # >> payed
202
+ # request.t_status # >> "Payed 3 dollars"
209
203
  # Request.t_statuses # >> { in_cart: -> { I18n.t("request.status.in_cart") }, .... }
210
-
211
- # if you need some substitution you can go like this
212
- # localize_enum :status, {
213
- # ..
214
- # delivered: "Delivered at: %{date}"
215
- # }
216
- # request.delivered!
217
- # request.t_status % {date: Time.now.to_s} # >> Delivered at: 05.02.2016
218
- #
219
- # Using in select:
220
- # f.select :status, Request.t_statuses_options
221
- #
222
- # Rem: select options breaks when using lambda
223
-
224
204
  def humanize_enum( *args, &block )
225
205
  enum_name = args.shift
226
206
  localizations = args.pop
227
- enum_pural = enum_name.to_s.pluralize
207
+ enum_plural = enum_name.to_s.pluralize
228
208
 
229
209
  self.instance_eval do
230
210
 
231
211
  #t_enums
232
- define_singleton_method( "t_#{enum_pural}" ) do
212
+ define_singleton_method( "t_#{enum_plural}" ) do
233
213
  # if localization is abscent than block must be given
234
214
  localizations.try(:with_indifferent_access) || localizations ||
235
- send(enum_pural).keys.map {|en| [en, self.new( {enum_name => en} ).send("t_#{enum_name}")] }.to_h.with_indifferent_access
215
+ send(enum_plural).keys.map {|en| [en, self.new( {enum_name => en} ).send("t_#{enum_name}")] }.to_h.with_indifferent_access
236
216
  end
237
217
 
238
218
  #t_enums_options
239
- define_singleton_method( "t_#{enum_pural}_options" ) do
240
- send("t_#{enum_pural}").invert.to_a.map do | key_val |
241
- # since all procs in t_enum are evaluated in context of a record than it's not always possible to create select options
242
- key_val[0] = ( key_val[0].try(:call) || "Cannot create option for #{key_val[0]}" ) if key_val[0].respond_to?(:call) && key_val[0].try(:arity) < 1
243
- key_val
244
- end
219
+ define_singleton_method( "t_#{enum_plural}_options" ) do
220
+ send("t_#{enum_plural}_options_raw", send("t_#{enum_plural}") )
221
+ end
222
+
223
+ #t_enums_options_i
224
+ define_singleton_method( "t_#{enum_plural}_options_i" ) do
225
+ send("t_#{enum_plural}_options_raw_i", send("t_#{enum_plural}") )
245
226
  end
246
227
 
247
228
  #t_enum
@@ -255,6 +236,29 @@ module EnumExt
255
236
  t
256
237
  end.to_s
257
238
  end
239
+
240
+ #protected?
241
+ define_singleton_method( "t_#{enum_plural}_options_raw_i" ) do |t_enum_set|
242
+ send("t_#{enum_plural}_options_raw", t_enum_set ).map do | key_val |
243
+ key_val[1] = send(enum_plural)[key_val[1]]
244
+ key_val
245
+ end
246
+ end
247
+
248
+ define_singleton_method( "t_#{enum_plural}_options_raw" ) do |t_enum_set|
249
+ t_enum_set.invert.to_a.map do | key_val |
250
+ # since all procs in t_enum are evaluated in context of a record than it's not always possible to create select options
251
+ if key_val[0].respond_to?(:call)
252
+ if key_val[0].try(:arity) < 1
253
+ key_val[0] = key_val[0].try(:call) rescue "Cannot create option for #{key_val[1]} ( proc fails to evaluate )"
254
+ else
255
+ key_val[0] = "Cannot create option for #{key_val[1]} because of a lambda"
256
+ end
257
+ end
258
+ key_val
259
+ end
260
+ end
261
+
258
262
  end
259
263
  end
260
264
  alias localize_enum humanize_enum
@@ -264,33 +268,14 @@ module EnumExt
264
268
  # If block is given than no scopes are taken in consider
265
269
  def translate_enum( *args, &block )
266
270
  enum_name = args.shift
267
- t_scope = args.pop || "activerecord.attributes.#{self.name.underscore}.#{enum_name}"
268
-
269
- translated_enums << enum_name.to_sym
271
+ enum_plural = enum_name.to_s.pluralize
272
+ t_scope = args.pop || "activerecord.attributes.#{self.name.underscore}.#{enum_plural}"
270
273
 
271
274
  if block_given?
272
275
  humanize_enum( enum_name, &block )
273
276
  else
274
- humanize_enum( enum_name, send(enum_name.to_s.pluralize).keys.map{|en| [ en, Proc.new{ I18n.t("#{t_scope}.#{en}") }] }.to_h )
277
+ humanize_enum( enum_name, send(enum_plural).keys.map{|en| [ en, Proc.new{ I18n.t("#{t_scope}.#{en}") }] }.to_h )
275
278
  end
276
-
277
- end
278
-
279
- # It useful for Active Admin, since it use by default human_attribute_name
280
- # to translate or humanize elements, if no translation given.
281
- # So when enums translated it breaks default human_attribute_name since it's search I18n scope from
282
- def human_attribute_name( name, options = {} )
283
- enum_translated?(name) ? super( "t_#{name}", options ) : super( name, options )
284
- end
285
-
286
- # helper to determine is attribute is translated enum
287
- def enum_translated?( name )
288
- translated_enums.include?( name.to_sym )
289
- end
290
-
291
- private
292
- def translated_enums
293
- @translated_enums ||= Set.new
294
279
  end
295
280
 
296
281
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enum_ext
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - alekseyl
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-21 00:00:00.000000000 Z
11
+ date: 2017-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,8 +66,36 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '10.0'
55
- description: Enum extension, ads enum sets, mass-assign for them, localization for
56
- them.
69
+ - !ruby/object:Gem::Dependency
70
+ name: rails-i18n
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Enum extension, ads enum sets, mass-assign, localization, and some sugar
98
+ helpers.
57
99
  email:
58
100
  - leshchuk@gmail.com
59
101
  executables: []
@@ -66,6 +108,8 @@ files:
66
108
  - LICENSE.txt
67
109
  - README.md
68
110
  - Rakefile
111
+ - bin/console
112
+ - bin/setup
69
113
  - enum_ext.gemspec
70
114
  - lib/enum_ext.rb
71
115
  - lib/enum_ext/version.rb
@@ -89,8 +133,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
133
  version: '0'
90
134
  requirements: []
91
135
  rubyforge_project:
92
- rubygems_version: 2.5.1
136
+ rubygems_version: 2.6.8
93
137
  signing_key:
94
138
  specification_version: 4
95
- summary: Enum extension, ads enum sets, mass-assign for them, localization for them.
139
+ summary: Enum extension, ads enum sets, mass-assign, localization, and some sugar
140
+ helpers.
96
141
  test_files: []