snake-eyes 1.0.0 → 2.0.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 +4 -4
- data/README.md +1 -1
- data/Rakefile +7 -5
- data/lib/snake-eyes.rb +6 -6
- data/lib/snake_eyes/compatibility.rb +19 -0
- data/lib/snake_eyes/configuration.rb +53 -0
- data/lib/{snake-eyes → snake_eyes}/engine.rb +4 -4
- data/lib/snake_eyes/interface_changes.rb +49 -0
- data/lib/snake_eyes/logging.rb +19 -0
- data/lib/snake_eyes/memoization.rb +27 -0
- data/lib/snake_eyes/transform.rb +124 -0
- data/lib/snake_eyes/version.rb +5 -0
- data/spec/dummy/Rakefile +3 -1
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +1 -1
- data/spec/dummy/bin/bundle +3 -1
- data/spec/dummy/bin/rails +3 -1
- data/spec/dummy/bin/rake +2 -0
- data/spec/dummy/bin/setup +10 -8
- data/spec/dummy/config.ru +2 -0
- data/spec/dummy/config/application.rb +4 -3
- data/spec/dummy/config/boot.rb +4 -2
- data/spec/dummy/config/environment.rb +3 -1
- data/spec/dummy/config/environments/development.rb +6 -3
- data/spec/dummy/config/environments/production.rb +2 -0
- data/spec/dummy/config/environments/test.rb +3 -1
- data/spec/dummy/config/initializers/assets.rb +4 -1
- data/spec/dummy/config/initializers/backtrace_silencers.rb +6 -2
- data/spec/dummy/config/initializers/cookies_serializer.rb +2 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +2 -0
- data/spec/dummy/config/initializers/inflections.rb +2 -0
- data/spec/dummy/config/initializers/mime_types.rb +2 -0
- data/spec/dummy/config/initializers/session_store.rb +2 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +2 -0
- data/spec/dummy/config/routes.rb +2 -1
- data/spec/dummy/log/test.log +5615 -0
- data/spec/nested_attributes_spec.rb +116 -118
- data/spec/params_spec.rb +107 -106
- data/spec/spec_helper.rb +5 -3
- data/spec/substitutions_spec.rb +202 -172
- metadata +27 -8
- data/lib/snake-eyes/interface_changes.rb +0 -187
- data/lib/snake-eyes/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba7e12d594cec3ed457677a40f4396e5822f759c
|
4
|
+
data.tar.gz: 111a3c36a1d94e507f509f2fc6bd0dc3f5692d43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad81a0db80a6af2c9fb59a7a51aaf49438dbd60985d72db73ed49e062fdea3eca2547c1d90ff8c986572b19712412b924e9f73825cd42da7e545656c154fa2f5
|
7
|
+
data.tar.gz: 5002052f44301fb3c2eecc10e4c08f5928a4f91ad4ca43e8723c9d5d4806a78e44b7067bed29dced07724466bf9b38e8b02ae782bdb68391061b0934a547fdf8
|
data/README.md
CHANGED
@@ -32,7 +32,7 @@ end
|
|
32
32
|
|
33
33
|
### Dealing with nested params
|
34
34
|
|
35
|
-
Once `snake_eyes_params` has been enabled for a
|
35
|
+
Once `snake_eyes_params` has been enabled for a controller, `params` accepts an options hash, which can be used to specify which attributes should have the `_attributes` suffix appended.
|
36
36
|
|
37
37
|
```ruby
|
38
38
|
class WithoutSnakeEyesController < ApplicationController
|
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'bundler/setup'
|
3
5
|
rescue LoadError
|
@@ -14,17 +16,17 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
14
16
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
17
|
end
|
16
18
|
|
17
|
-
APP_RAKEFILE = File.expand_path(
|
19
|
+
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
18
20
|
load 'rails/tasks/engine.rake'
|
19
21
|
|
20
22
|
Bundler::GemHelper.install_tasks
|
21
23
|
|
22
|
-
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each
|
24
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each(&method(:load))
|
23
25
|
|
24
26
|
require 'rspec/core'
|
25
27
|
require 'rspec/core/rake_task'
|
26
28
|
|
27
|
-
desc
|
28
|
-
RSpec::Core::RakeTask.new(:
|
29
|
+
desc 'Run all specs in spec directory (excluding plugin specs)'
|
30
|
+
RSpec::Core::RakeTask.new(spec: 'app:db:test:prepare')
|
29
31
|
|
30
|
-
task :
|
32
|
+
task default: :spec
|
data/lib/snake-eyes.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'snake_eyes/version'
|
4
|
+
require 'snake_eyes/interface_changes'
|
3
5
|
|
4
6
|
module SnakeEyes
|
5
7
|
class << self
|
6
8
|
attr_accessor :log_snake_eyes_parameters
|
7
9
|
|
8
10
|
def configuration
|
9
|
-
if block_given?
|
10
|
-
yield(SnakeEyes)
|
11
|
-
end
|
11
|
+
yield(SnakeEyes) if block_given?
|
12
12
|
end
|
13
13
|
|
14
|
-
alias
|
14
|
+
alias config configuration
|
15
15
|
end
|
16
16
|
|
17
17
|
@log_snake_eyes_parameters = true
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SnakeEyes
|
4
|
+
module Compatibility
|
5
|
+
if Rails::VERSION::MAJOR >= 5
|
6
|
+
def _prepare(transformed_params)
|
7
|
+
# We permit all parameter values so that we many convert it to a hash,
|
8
|
+
# to work with ActionPack 5.2's ActionController::Parameters initializer
|
9
|
+
params_duplicate = transformed_params.dup
|
10
|
+
params_duplicate.permit!
|
11
|
+
params_duplicate.to_h
|
12
|
+
end
|
13
|
+
else
|
14
|
+
def _prepare(transformed_params)
|
15
|
+
transformed_params
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SnakeEyes
|
4
|
+
module Configuration
|
5
|
+
protected
|
6
|
+
|
7
|
+
def validate_options(options)
|
8
|
+
options.keys.each do |key|
|
9
|
+
if key != :nested_attributes && key != :substitutions
|
10
|
+
raise ArgumentError,
|
11
|
+
"SnakeEyes: params received unrecognised option '#{key}'"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_nested_attributes!(options)
|
17
|
+
options[:nested_attributes] =
|
18
|
+
build_options_schema(options[:nested_attributes] || {}, '') do |target, parent_name|
|
19
|
+
# noinspection RubyResolve
|
20
|
+
if parent_name.empty? || parent_name.starts_with?('_')
|
21
|
+
target
|
22
|
+
else
|
23
|
+
target.merge(_attributes_suffix: true)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_options_schema(attributes_list = [], parent_name = '', options = {}, &block)
|
29
|
+
if attributes_list.is_a?(Array)
|
30
|
+
attributes_array = attributes_list.inject({}) do |memo, nested_attribute|
|
31
|
+
memo.merge(build_options_schema(nested_attribute, parent_name, options, &block))
|
32
|
+
end
|
33
|
+
|
34
|
+
yield(attributes_array, parent_name)
|
35
|
+
elsif attributes_list.is_a?(Hash) && (
|
36
|
+
!options[:internal_attributes] ||
|
37
|
+
(attributes_list.keys && options[:internal_attributes]).length > options[:internal_attributes].length)
|
38
|
+
|
39
|
+
attributes_hash = attributes_list.each_with_object({}) do |key_and_value, memo|
|
40
|
+
key, value = key_and_value
|
41
|
+
|
42
|
+
memo[key.to_s] = yield(build_options_schema(value, '', options, &block), key.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
yield(attributes_hash, parent_name)
|
46
|
+
else
|
47
|
+
{
|
48
|
+
attributes_list.to_s.underscore => yield({}, attributes_list.to_s.underscore)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SnakeEyes
|
2
4
|
class Engine < ::Rails::Engine
|
3
|
-
|
4
5
|
config.generators do |g|
|
5
|
-
g.test_framework :rspec, :
|
6
|
-
g.fixture_replacement :factory_girl, :
|
6
|
+
g.test_framework :rspec, fixture: false
|
7
|
+
g.fixture_replacement :factory_girl, dir: 'spec/factories'
|
7
8
|
g.assets false
|
8
9
|
g.helper false
|
9
10
|
end
|
10
|
-
|
11
11
|
end
|
12
12
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('configuration', __dir__)
|
4
|
+
require File.expand_path('memoization', __dir__)
|
5
|
+
require File.expand_path('compatibility', __dir__)
|
6
|
+
require File.expand_path('logging', __dir__)
|
7
|
+
require File.expand_path('transform', __dir__)
|
8
|
+
|
9
|
+
module SnakeEyes
|
10
|
+
module InterfaceChanges
|
11
|
+
include Configuration
|
12
|
+
include Memoization
|
13
|
+
include Compatibility
|
14
|
+
include Logging
|
15
|
+
include Transform
|
16
|
+
|
17
|
+
KEYS_ALWAYS_PRESENT = %w[controller action].freeze
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
alias_method :old_params, :params
|
22
|
+
|
23
|
+
def params(options = {})
|
24
|
+
# noinspection RubyResolve
|
25
|
+
original_params = old_params
|
26
|
+
|
27
|
+
return original_params unless keys_to_snakeize?(original_params)
|
28
|
+
|
29
|
+
validate_options(options)
|
30
|
+
add_nested_attributes!(options)
|
31
|
+
|
32
|
+
return params_from_cache(options) if params_in_cache?(options)
|
33
|
+
|
34
|
+
@snake_eyes_params = transform(original_params, options)
|
35
|
+
|
36
|
+
log(@snake_eyes_params)
|
37
|
+
|
38
|
+
cache!(options, @snake_eyes_params)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def keys_to_snakeize?(params)
|
44
|
+
(params.keys || KEYS_ALWAYS_PRESENT).length > KEYS_ALWAYS_PRESENT.length
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SnakeEyes
|
4
|
+
module Logging
|
5
|
+
private
|
6
|
+
|
7
|
+
def log(snake_eyes_params)
|
8
|
+
return unless SnakeEyes.log_snake_eyes_parameters
|
9
|
+
|
10
|
+
ignored_params = ActionController::LogSubscriber::INTERNAL_PARAMS
|
11
|
+
filtered_params =
|
12
|
+
request.send(:parameter_filter).filter(
|
13
|
+
snake_eyes_params.except(*ignored_params)
|
14
|
+
)
|
15
|
+
|
16
|
+
logger.info " SnakeEyes Parameters: #{_prepare(filtered_params).inspect}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SnakeEyes
|
4
|
+
module Memoization
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
private
|
8
|
+
|
9
|
+
def params_from_cache(key)
|
10
|
+
previous_params[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def params_in_cache?(key)
|
14
|
+
previous_params.key?(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def cache!(key, value)
|
18
|
+
previous_params[key] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def previous_params
|
22
|
+
@previous_params ||= {}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SnakeEyes
|
4
|
+
module Transform
|
5
|
+
def transform(original_params, options)
|
6
|
+
ActionController::Parameters.new(
|
7
|
+
deep_transform(_prepare(original_params), options)
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def deep_transform(target, options = {})
|
14
|
+
case target
|
15
|
+
when Array
|
16
|
+
deep_transform_array(
|
17
|
+
target,
|
18
|
+
nested_attributes(options),
|
19
|
+
substitutions(options)
|
20
|
+
)
|
21
|
+
when Hash
|
22
|
+
deep_transform_hash(
|
23
|
+
target,
|
24
|
+
nested_attributes(options),
|
25
|
+
substitutions(options)
|
26
|
+
)
|
27
|
+
else
|
28
|
+
perform_substitution(target, substitutions(options))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def nested_attributes(options)
|
33
|
+
options[:nested_attributes] || {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def substitutions(options)
|
37
|
+
if options[:substitutions].is_a?(Array)
|
38
|
+
options[:substitutions].map(&:stringify_keys)
|
39
|
+
else
|
40
|
+
(options[:substitutions] || {}).stringify_keys
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def deep_transform_array(target, nested_attributes, substitutions)
|
45
|
+
target.map do |target_element|
|
46
|
+
array_nested_attributes = nested_attributes['*']
|
47
|
+
|
48
|
+
array_substitutions =
|
49
|
+
substitutions.is_a?(Array) ? {} : substitutions['*']
|
50
|
+
|
51
|
+
deep_transform(
|
52
|
+
target_element,
|
53
|
+
nested_attributes: array_nested_attributes,
|
54
|
+
substitutions: array_substitutions
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def snakeize(key)
|
60
|
+
key.to_s.underscore.gsub(/(\d+)/, '_\1')
|
61
|
+
end
|
62
|
+
|
63
|
+
def deep_transform_hash(target, nested_attributes, substitutions)
|
64
|
+
target.each_with_object({}) do |(key, value), memo|
|
65
|
+
|
66
|
+
transformed_key_base = snakeize(key)
|
67
|
+
|
68
|
+
# Append the '_attributes' suffix if the original params key has the
|
69
|
+
# same name and is nested in the same place as one mentioned in the
|
70
|
+
# nested_attributes option
|
71
|
+
transformed_key =
|
72
|
+
if nested_attributes[transformed_key_base] &&
|
73
|
+
nested_attributes[transformed_key_base][:_attributes_suffix]
|
74
|
+
|
75
|
+
transformed_key_base + '_attributes'
|
76
|
+
else
|
77
|
+
transformed_key_base
|
78
|
+
end
|
79
|
+
|
80
|
+
hash_nested_attributes =
|
81
|
+
nested_attributes[transformed_key_base] || nested_attributes['_' + transformed_key_base]
|
82
|
+
|
83
|
+
hash_substitutions =
|
84
|
+
substitutions.is_a?(Array) ? {} : substitutions[transformed_key_base]
|
85
|
+
|
86
|
+
transformed_hash = deep_transform(
|
87
|
+
value,
|
88
|
+
nested_attributes: hash_nested_attributes,
|
89
|
+
substitutions: hash_substitutions
|
90
|
+
)
|
91
|
+
|
92
|
+
memo[transformed_key] =
|
93
|
+
if memo.key?(transformed_key) && memo[transformed_key].is_a?(Hash)
|
94
|
+
memo[transformed_key].deep_merge(transformed_hash)
|
95
|
+
else
|
96
|
+
transformed_hash
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def perform_substitution(target, substitution)
|
102
|
+
if substitution.is_a?(Array)
|
103
|
+
matching_substitution = substitution.find do |substitution_item|
|
104
|
+
substitution_keys?(substitution_item) && target == substitution_item['replace']
|
105
|
+
end
|
106
|
+
|
107
|
+
if matching_substitution
|
108
|
+
matching_substitution['with']
|
109
|
+
else
|
110
|
+
target
|
111
|
+
end
|
112
|
+
|
113
|
+
elsif substitution_keys?(substitution)
|
114
|
+
target == substitution['replace'] ? substitution['with'] : target
|
115
|
+
else
|
116
|
+
target
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def substitution_keys?(substitution)
|
121
|
+
substitution.key?('replace') && substitution.key?('with')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/spec/dummy/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
4
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
5
|
|
4
|
-
require File.expand_path('
|
6
|
+
require File.expand_path('config/application', __dir__)
|
5
7
|
|
6
8
|
Rails.application.load_tasks
|
data/spec/dummy/bin/bundle
CHANGED
data/spec/dummy/bin/rails
CHANGED