client_side_validations 6.0.0 → 7.0.1

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: 62857994d1326cfb20a2c2d3c1c5fd746fa72065
4
- data.tar.gz: 5d187e21b9b0b8935f3d5bfee8099095a128a389
3
+ metadata.gz: a667f554594c134de2b2ab3e77679337a8bfcb22
4
+ data.tar.gz: fd9fd106426a734c79c9a6f9e3bc96877b4bd152
5
5
  SHA512:
6
- metadata.gz: 284d47098c63a20c73aad2d252028e54ecd7b519d6130d16170206440e4f70b6e28f70be895a71c4f71dc555acf65765f1137acbded5910aa5aea779bdf3862a
7
- data.tar.gz: 4c0b3c38d2254184cd34841d87683a3c21e0e5c1805be57134b913078f96e964e920cf78b85efb41a214f9bb81721b24e3e78996ca82e2877e751d15717cb6b2
6
+ metadata.gz: 378f957247dbfab1182963dadb3adf802cb4e79b2a2570af0d9507f3e94b2c7d87fe93002300542f4613b5d7a235f52dded3e906d358fdade2903ae36842ba4d
7
+ data.tar.gz: 04f88efc2d59603ea7189d1aefeda34f3e7678cbecfffed791df36612f35582c940ed4ef6df82b0db1d6e4da1d54023dc2d3722a99dda6ccbd4ebb72b608a790
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.0.1 (2017-01-22)
4
+
5
+ * Fix `rails.validations` asset not found error
6
+
7
+ ## 7.0.0 (2017-01-22)
8
+
9
+ * Remove middleware for remote validations
10
+ * Update development dependencies
11
+
3
12
  ## 6.0.0 (2017-01-20)
4
13
 
5
14
  * Rails 5.0 compatibility
data/README.md CHANGED
@@ -183,37 +183,6 @@ You can even turn them off per fieldset:
183
183
  ...
184
184
  ```
185
185
 
186
- ## Wrapper objects and remote validations ##
187
-
188
- For example, we have a wrapper class for the User model, UserForm, and it uses remote uniqueness validation for the `email` field.
189
-
190
- ```ruby
191
- class UserForm
192
- include ActiveRecord::Validations
193
- attr_accessor :email
194
- validates_uniqueness_of :email
195
- end
196
- ...
197
- <% form_for(UserForm.new) do %>
198
- ...
199
- ```
200
-
201
- However, this won't work since middleware will try to perform validation against UserForm, and it's not persisted.
202
-
203
- This is solved by passing `client_validations` options hash to the validator, that currently supports one key — `:class`, and setting correct name to the form object:
204
-
205
- ```ruby
206
- class UserForm
207
- include ActiveRecord::Validations
208
- attr_accessor :email
209
- validates_uniqueness_of :email, client_validations: { class:
210
- 'User' }
211
- end
212
- ...
213
- <% form_for(UserForm.new, as: :user) do %>
214
- ...
215
- ```
216
-
217
186
  ## Understanding the embedded `<script>` tag ##
218
187
 
219
188
  A rendered form with validations will always have a `<script>` appended
@@ -259,7 +228,7 @@ passing nothing:
259
228
  You can also force validators similarly to the input syntax:
260
229
 
261
230
  ```erb
262
- <%= f.validate :email, uniqueness: false %>
231
+ <%= f.validate :email, presence: false %>
263
232
  ```
264
233
 
265
234
  Take care when using this method. The embedded validators are
@@ -267,11 +236,11 @@ overwritten based upon the order they are rendered. So if you do
267
236
  something like:
268
237
 
269
238
  ```erb
270
- <%= f.text_field :email, validate: { uniqueness: false } %>
239
+ <%= f.text_field :email, validate: { presence: false } %>
271
240
  <%= f.validate %>
272
241
  ```
273
242
 
274
- The `uniqueness` validator will not be turned off because the options
243
+ The `presence` validator will not be turned off because the options
275
244
  were overwritten by the call to `FormBuilder#validate`
276
245
 
277
246
 
@@ -357,71 +326,6 @@ end
357
326
 
358
327
  Client Side Validations will apply the new validator and validate your forms as needed.
359
328
 
360
- ### Remote Validators ###
361
- A good example of a remote validator would be for Zipcodes. It wouldn't be reasonable to embed every single zipcode inline, so we'll need to check for its existence with remote javascript call back to our app. Assume we have a zipcode database mapped to the model Zipcode. The primary key is the unique zipcode. Our Rails validator would probably look something like this:
362
-
363
- ```ruby
364
- class ZipcodeValidator < ActiveModel::EachValidator
365
- def validate_each(record, attr_name, value)
366
- unless ::Zipcode.where(id: value).exists?
367
- record.errors.add(attr_name, :zipcode, options.merge(value: value))
368
- end
369
- end
370
- end
371
-
372
- # This allows us to assign the validator in the model
373
- module ActiveModel::Validations::HelperMethods
374
- def validates_zipcode(*attr_names)
375
- validates_with ZipcodeValidator, _merge_attributes(attr_names)
376
- end
377
- end
378
- ```
379
-
380
- Of course we still need to add the i18n message:
381
-
382
- ```yaml
383
- en:
384
- errors:
385
- messages:
386
- zipcode: "Not a valid US zip code"
387
- ```
388
-
389
- And let's add the Javascript validator. Because this will be remote validator we need to add it to `ClientSideValidations.validators.remote`:
390
-
391
- ```js
392
- window.ClientSideValidations.validators.remote['zipcode'] = function(element, options) {
393
- if ($.ajax({
394
- url: '/validators/zipcode',
395
- data: { id: element.val() },
396
- // async *must* be false
397
- async: false
398
- }).status == 404) { return options.message; }
399
- }
400
- ```
401
-
402
- All we're doing here is checking to see if the resource exists (in this case the given zipcode) and if it doesn't the error message is returned.
403
-
404
- Notice that the remote call is forced to *async: false*. This is necessary and the validator may not work properly if this is left out.
405
-
406
- Now the extra step for adding a remote validator is to add to the middleware. All ClientSideValidations middleware should inherit from `ClientSideValidations::Middleware::Base`:
407
-
408
- ```ruby
409
- module ClientSideValidations::Middleware
410
- class Zipcode < ClientSideValidations::Middleware::Base
411
- def response
412
- if ::Zipcode.where(id: request.params[:id]).exists?
413
- self.status = 200
414
- else
415
- self.status = 404
416
- end
417
- super
418
- end
419
- end
420
- end
421
- ```
422
-
423
- The `#response` method is always called and it should set the status accessor. Then a call to `super` is required. In the javascript we set the 'id' in the params to the value of the zipcode input, in the middleware we check to see if this zipcode exists in our zipcode database. If it does, we return 200, if it doesn't we return 404.
424
-
425
329
  ## Enabling, Disabling, and Resetting on the client ##
426
330
 
427
331
  There are many reasons why you might want to enable, disable, or even completely reset the bound validation events on the client. `ClientSideValidations` offers a simple API for this.
@@ -510,16 +414,6 @@ div.field_with_errors div.ui-effects-wrapper {
510
414
 
511
415
  Finally uncomment the `ActionView::Base.field_error_proc` override in `config/initializers/client_side_validations.rb`
512
416
 
513
- ## Security ##
514
-
515
- Client Side Validations comes with a uniqueness middleware. This can be a potential security issue, so the uniqueness validator is disabled by default. If you want to enable it, set the `disabled_validators` config variable in `config/initializers/client_side_validations.rb`:
516
-
517
- ```ruby
518
- ClientSideValidations::Config.disabled_validators = []
519
- ```
520
-
521
- Note that the `FormBuilder` will automatically skip building validators that are disabled.
522
-
523
417
  ## Authors ##
524
418
 
525
419
  [Brian Cardarella](https://twitter.com/bcardarella)
@@ -1,12 +1,9 @@
1
- module ClientSideValidations
2
- end
3
-
4
1
  require 'client_side_validations/config'
5
2
  require 'client_side_validations/active_model' if defined?(::ActiveModel)
6
3
  require 'client_side_validations/active_record' if defined?(::ActiveRecord)
7
4
  require 'client_side_validations/action_view' if defined?(::ActionView)
5
+
8
6
  if defined?(::Rails)
9
- require 'client_side_validations/generators'
10
- require 'client_side_validations/middleware'
11
7
  require 'client_side_validations/engine'
8
+ require 'client_side_validations/generators'
12
9
  end
@@ -1,4 +1,5 @@
1
1
  require 'client_side_validations/core_ext'
2
+ require 'client_side_validations/extender'
2
3
  require 'client_side_validations/active_model/conditionals'
3
4
 
4
5
  module ClientSideValidations
@@ -145,7 +146,4 @@ end
145
146
  ActiveModel::Validator.send(:include, ClientSideValidations::ActiveModel::Validator)
146
147
  ActiveModel::Validations.send(:include, ClientSideValidations::ActiveModel::Validations)
147
148
 
148
- %w(Absence Acceptance Exclusion Format Inclusion Length Numericality Presence).each do |validator|
149
- require "client_side_validations/active_model/#{validator.downcase}"
150
- ActiveModel::Validations.const_get("#{validator}Validator").send :include, ClientSideValidations::ActiveModel.const_get(validator)
151
- end
149
+ ClientSideValidations::Extender.extend 'ActiveModel', %w(Absence Acceptance Exclusion Format Inclusion Length Numericality Presence)
@@ -1,11 +1,6 @@
1
1
  require 'client_side_validations/active_model'
2
- require 'client_side_validations/middleware'
3
- require 'client_side_validations/active_record/middleware'
2
+ require 'client_side_validations/extender'
4
3
 
5
4
  ActiveRecord::Base.send(:include, ClientSideValidations::ActiveModel::Validations)
6
- ClientSideValidations::Middleware::Uniqueness.register_orm(ClientSideValidations::ActiveRecord::Middleware)
7
5
 
8
- %w(Uniqueness).each do |validator|
9
- require "client_side_validations/active_record/#{validator.downcase}"
10
- ActiveRecord::Validations.const_get("#{validator}Validator").send :include, ClientSideValidations::ActiveRecord.const_get(validator)
11
- end
6
+ ClientSideValidations::Extender.extend 'ActiveRecord', %w(Uniqueness)
@@ -6,7 +6,7 @@ module ClientSideValidations
6
6
  attr_accessor :root_path
7
7
  end
8
8
 
9
- self.disabled_validators = [:uniqueness]
9
+ self.disabled_validators = []
10
10
  self.number_format_with_locale = false
11
11
  self.root_path = nil
12
12
  end
@@ -1,5 +1,4 @@
1
1
  module ClientSideValidations
2
2
  class Engine < ::Rails::Engine
3
- config.app_middleware.use ClientSideValidations::Middleware::Validators
4
3
  end
5
4
  end
@@ -0,0 +1,12 @@
1
+ module ClientSideValidations
2
+ module Extender
3
+ module_function
4
+
5
+ def extend(klass, validators)
6
+ validators.each do |validator|
7
+ require "client_side_validations/#{klass.underscore}/#{validator.downcase}"
8
+ const_get(klass)::Validations.const_get("#{validator}Validator").send :include, ClientSideValidations.const_get(klass).const_get(validator)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ClientSideValidations
3
- VERSION = '6.0.0'.freeze
3
+ VERSION = '7.0.1'.freeze
4
4
  end
@@ -1,7 +1,7 @@
1
1
  # ClientSideValidations Initializer
2
2
 
3
- # Disabled validators. The uniqueness validator is disabled by default for security issues. Enable it on your own responsibility!
4
- # ClientSideValidations::Config.disabled_validators = [:uniqueness]
3
+ # Disabled validators
4
+ # ClientSideValidations::Config.disabled_validators = []
5
5
 
6
6
  # Uncomment to validate number format with current I18n locale
7
7
  # ClientSideValidations::Config.number_format_with_locale = true
@@ -1,6 +1,6 @@
1
1
 
2
2
  /*!
3
- * Client Side Validations - v6.0.0 (https://github.com/DavyJonesLocker/client_side_validations)
3
+ * Client Side Validations - v7.0.1 (https://github.com/DavyJonesLocker/client_side_validations)
4
4
  * Copyright (c) 2017 Geremia Taglialatela, Brian Cardarella
5
5
  * Licensed under MIT (http://opensource.org/licenses/mit-license.php)
6
6
  */
@@ -505,64 +505,7 @@
505
505
  }
506
506
  }
507
507
  },
508
- remote: {
509
- uniqueness: function(element, options) {
510
- var data, key, message, name, ref, scope_value, scoped_element, scoped_name;
511
- message = ClientSideValidations.validators.local.presence(element, options);
512
- if (message) {
513
- if (options.allow_blank === true) {
514
- return;
515
- }
516
- return message;
517
- }
518
- data = {};
519
- data.case_sensitive = !!options.case_sensitive;
520
- if (options.id) {
521
- data.id = options.id;
522
- }
523
- if (options.scope) {
524
- data.scope = {};
525
- ref = options.scope;
526
- for (key in ref) {
527
- scope_value = ref[key];
528
- scoped_name = element.attr('name').replace(/\[\w+\]$/, "[" + key + "]");
529
- scoped_element = $("[name='" + scoped_name + "']");
530
- $("[name='" + scoped_name + "']:checkbox").each(function() {
531
- if (this.checked) {
532
- return scoped_element = this;
533
- }
534
- });
535
- if (scoped_element[0] && scoped_element.val() !== scope_value) {
536
- data.scope[key] = scoped_element.val();
537
- scoped_element.unbind("change." + element.id).bind("change." + element.id, function() {
538
- element.trigger('change.ClientSideValidations');
539
- return element.trigger('focusout.ClientSideValidations');
540
- });
541
- } else {
542
- data.scope[key] = scope_value;
543
- }
544
- }
545
- }
546
- if (/_attributes\]/.test(element.attr('name'))) {
547
- name = element.attr('name').match(/\[\w+_attributes\]/g).pop().match(/\[(\w+)_attributes\]/).pop();
548
- name += /(\[\w+\])$/.exec(element.attr('name'))[1];
549
- } else {
550
- name = element.attr('name');
551
- }
552
- if (options['class']) {
553
- name = options['class'] + "[" + (name.split('[')[1]);
554
- }
555
- data[name] = element.val();
556
- if ($.ajax({
557
- url: ClientSideValidations.remote_validators_url_for('uniqueness'),
558
- data: data,
559
- async: false,
560
- cache: false
561
- }).status === 200) {
562
- return options.message;
563
- }
564
- }
565
- }
508
+ remote: {}
566
509
  };
567
510
 
568
511
  window.ClientSideValidations.remote_validators_url_for = function(validator) {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: client_side_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 7.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geremia Taglialatela
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-01-20 00:00:00.000000000 Z
12
+ date: 2017-01-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -93,14 +93,14 @@ dependencies:
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.8.18
96
+ version: 0.8.19
97
97
  type: :development
98
98
  prerelease: false
99
99
  version_requirements: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.8.18
103
+ version: 0.8.19
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: m
106
106
  requirement: !ruby/object:Gem::Requirement
@@ -119,22 +119,16 @@ dependencies:
119
119
  name: minitest
120
120
  requirement: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: 4.7.5
125
- - - "<"
122
+ - - "~>"
126
123
  - !ruby/object:Gem::Version
127
- version: 6.0.0
124
+ version: '5.10'
128
125
  type: :development
129
126
  prerelease: false
130
127
  version_requirements: !ruby/object:Gem::Requirement
131
128
  requirements:
132
- - - ">="
133
- - !ruby/object:Gem::Version
134
- version: 4.7.5
135
- - - "<"
129
+ - - "~>"
136
130
  - !ruby/object:Gem::Version
137
- version: 6.0.0
131
+ version: '5.10'
138
132
  - !ruby/object:Gem::Dependency
139
133
  name: mocha
140
134
  requirement: !ruby/object:Gem::Requirement
@@ -287,17 +281,16 @@ files:
287
281
  - lib/client_side_validations/active_model/numericality.rb
288
282
  - lib/client_side_validations/active_model/presence.rb
289
283
  - lib/client_side_validations/active_record.rb
290
- - lib/client_side_validations/active_record/middleware.rb
291
284
  - lib/client_side_validations/active_record/uniqueness.rb
292
285
  - lib/client_side_validations/config.rb
293
286
  - lib/client_side_validations/core_ext.rb
294
287
  - lib/client_side_validations/core_ext/range.rb
295
288
  - lib/client_side_validations/core_ext/regexp.rb
296
289
  - lib/client_side_validations/engine.rb
290
+ - lib/client_side_validations/extender.rb
297
291
  - lib/client_side_validations/files.rb
298
292
  - lib/client_side_validations/generators.rb
299
293
  - lib/client_side_validations/generators/rails_validations.rb
300
- - lib/client_side_validations/middleware.rb
301
294
  - lib/client_side_validations/version.rb
302
295
  - lib/generators/client_side_validations/copy_assets_generator.rb
303
296
  - lib/generators/client_side_validations/install_generator.rb
@@ -323,7 +316,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
323
316
  version: '0'
324
317
  requirements: []
325
318
  rubyforge_project:
326
- rubygems_version: 2.6.8
319
+ rubygems_version: 2.6.9
327
320
  signing_key:
328
321
  specification_version: 4
329
322
  summary: Client Side Validations
@@ -1,62 +0,0 @@
1
- module ClientSideValidations
2
- module ActiveRecord
3
- class Middleware
4
- def self.class?(klass)
5
- klass.abstract_class.blank? && klass < ::ActiveRecord::Base
6
- end
7
-
8
- def self.unique?(klass, attribute, value, params)
9
- klass = find_topmost_superclass(klass)
10
- connection = klass.connection
11
- column = klass.columns_hash[attribute.to_s]
12
- value = type_cast_value(connection, column, value)
13
-
14
- sql = sql_statement(klass, connection, attribute, value, params)
15
- relation = Arel::Nodes::SqlLiteral.new(sql)
16
-
17
- klass.where(relation).empty?
18
- end
19
-
20
- def self.sql_statement(klass, connection, attribute, value, params)
21
- sql = []
22
- t = klass.arel_table
23
-
24
- if params[:case_sensitive] == 'true'
25
- sql << 'BINARY' if connection.adapter_name =~ /^mysql/i
26
- sql << t[attribute].eq(value).to_sql
27
- else
28
- escaped_value = value.gsub(/[%_]/, '\\\\\0')
29
- sql << "#{t[attribute].matches(escaped_value).to_sql} ESCAPE '\\'"
30
- end
31
-
32
- sql << "AND #{t[klass.primary_key].not_eq(params[:id]).to_sql}" if params[:id]
33
-
34
- (params[:scope] || {}).each do |scope_attribute, scope_value|
35
- scope_value = type_cast_value(connection, klass.columns_hash[scope_attribute], scope_value)
36
- sql << "AND #{t[scope_attribute].eq(scope_value).to_sql}"
37
- end
38
-
39
- sql.join ' '
40
- end
41
-
42
- def self.type_cast_value(connection, column, value)
43
- type = connection.lookup_cast_type_from_column(column)
44
- value = type.deserialize(value)
45
-
46
- if column.limit && value.is_a?(String)
47
- value.mb_chars[0, column.limit]
48
- else
49
- value
50
- end
51
- end
52
-
53
- def self.find_topmost_superclass(klass)
54
- if class?(klass.superclass)
55
- find_topmost_superclass(klass.superclass)
56
- else
57
- klass
58
- end
59
- end
60
- end
61
- end
62
- end
@@ -1,159 +0,0 @@
1
- require 'client_side_validations/core_ext'
2
-
3
- module ClientSideValidations
4
- module Middleware
5
- class Validators
6
- def initialize(app)
7
- @app = app
8
- end
9
-
10
- def call(env)
11
- matches = %r{\A/?#{ClientSideValidations::Config.root_path}/validators\/(\w+)\z}.match(env['PATH_INFO'])
12
- if matches
13
- process_request(matches.captures.first, env)
14
- else
15
- @app.call(env)
16
- end
17
- end
18
-
19
- def process_request(validation, env)
20
- if disabled_validators.include?(validation)
21
- error_resp
22
- else
23
- klass_name = validation.camelize
24
- klass_name = "::ClientSideValidations::Middleware::#{klass_name}"
25
- klass_name.constantize.new(env).response
26
- end
27
- rescue
28
- error_resp
29
- end
30
-
31
- def disabled_validators
32
- ClientSideValidations::Config.disabled_validators.map(&:to_s)
33
- end
34
-
35
- def error_resp
36
- [500, { 'Content-Type' => 'application/json', 'Content-Length' => '0' }, ['']]
37
- end
38
- end
39
-
40
- class Base
41
- attr_accessor :request, :body, :status
42
-
43
- def initialize(env)
44
- # Filter out cache buster
45
- env['QUERY_STRING'] = env['QUERY_STRING'].split('&').select { |p| !p.match(/^_=/) }.join('&')
46
- self.body = ''
47
- self.status = 200
48
- self.request = ActionDispatch::Request.new(env)
49
- end
50
-
51
- def response
52
- [status, { 'Content-Type' => content_type, 'Content-Length' => body.length.to_s }, [body]]
53
- end
54
-
55
- def content_type
56
- 'application/json'
57
- end
58
- end
59
-
60
- class Uniqueness < Base
61
- IGNORE_PARAMS = %w(case_sensitive id scope).freeze
62
- @@registered_orms = []
63
- class NotValidatable < StandardError; end
64
-
65
- def response
66
- begin
67
- if unique?
68
- self.status = 404
69
- self.body = 'true'
70
- else
71
- self.status = 200
72
- self.body = 'false'
73
- end
74
- rescue NotValidatable
75
- self.status = 500
76
- self.body = ''
77
- end
78
- super
79
- end
80
-
81
- def self.register_orm(orm)
82
- registered_orms << orm
83
- end
84
-
85
- def self.registered_orms
86
- @@registered_orms
87
- end
88
-
89
- def registered_orms
90
- self.class.registered_orms
91
- end
92
-
93
- private
94
-
95
- def unique?
96
- convert_scope_value_from_null_to_nil
97
- klass, attribute, value = extract_resources
98
- middleware_class = nil
99
-
100
- unless Array.wrap(klass._validators[attribute.to_sym]).find { |v| v.kind == :uniqueness }
101
- raise NotValidatable
102
- end
103
-
104
- registered_orms.each do |orm|
105
- if orm.class?(klass)
106
- middleware_class = orm
107
- break
108
- end
109
- end
110
-
111
- middleware_class.unique?(klass, attribute, value, request.params)
112
- end
113
-
114
- def convert_scope_value_from_null_to_nil
115
- return unless request.params['scope']
116
- request.params['scope'].each do |key, value|
117
- request.params['scope'][key] = nil if value == 'null'
118
- end
119
- end
120
-
121
- def extract_resources
122
- parent_key = (request.params.keys - IGNORE_PARAMS).first
123
-
124
- if nested?(request.params[parent_key], 1)
125
- klass, attribute, value = uproot(request.params[parent_key])
126
- klass = klass.classify.constantize
127
- else
128
- klass = parent_key.classify.constantize
129
- attribute = request.params[parent_key].keys.first
130
- value = request.params[parent_key][attribute]
131
- end
132
-
133
- [klass, attribute, value]
134
- end
135
-
136
- def uproot(nested_hash = nil)
137
- uproot_helper(nested_hash)[-3..-1]
138
- end
139
-
140
- def uproot_helper(nested_hash = nil, keys = [])
141
- if nested_hash.respond_to?(:keys)
142
- keys << nested_hash.keys.first
143
- uproot_helper(nested_hash[nested_hash.keys.first], keys)
144
- else
145
- keys << nested_hash
146
- end
147
- end
148
-
149
- def nested?(hash = nil, levels = 0)
150
- i = 0
151
- while hash.respond_to? :keys
152
- hash = hash[hash.keys.first]
153
- i += 1
154
- end
155
- i > levels
156
- end
157
- end
158
- end
159
- end