snake-eyes 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +7 -5
  4. data/lib/snake-eyes.rb +6 -6
  5. data/lib/snake_eyes/compatibility.rb +19 -0
  6. data/lib/snake_eyes/configuration.rb +53 -0
  7. data/lib/{snake-eyes → snake_eyes}/engine.rb +4 -4
  8. data/lib/snake_eyes/interface_changes.rb +49 -0
  9. data/lib/snake_eyes/logging.rb +19 -0
  10. data/lib/snake_eyes/memoization.rb +27 -0
  11. data/lib/snake_eyes/transform.rb +124 -0
  12. data/lib/snake_eyes/version.rb +5 -0
  13. data/spec/dummy/Rakefile +3 -1
  14. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  15. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  16. data/spec/dummy/app/views/layouts/application.html.erb +1 -1
  17. data/spec/dummy/bin/bundle +3 -1
  18. data/spec/dummy/bin/rails +3 -1
  19. data/spec/dummy/bin/rake +2 -0
  20. data/spec/dummy/bin/setup +10 -8
  21. data/spec/dummy/config.ru +2 -0
  22. data/spec/dummy/config/application.rb +4 -3
  23. data/spec/dummy/config/boot.rb +4 -2
  24. data/spec/dummy/config/environment.rb +3 -1
  25. data/spec/dummy/config/environments/development.rb +6 -3
  26. data/spec/dummy/config/environments/production.rb +2 -0
  27. data/spec/dummy/config/environments/test.rb +3 -1
  28. data/spec/dummy/config/initializers/assets.rb +4 -1
  29. data/spec/dummy/config/initializers/backtrace_silencers.rb +6 -2
  30. data/spec/dummy/config/initializers/cookies_serializer.rb +2 -0
  31. data/spec/dummy/config/initializers/filter_parameter_logging.rb +2 -0
  32. data/spec/dummy/config/initializers/inflections.rb +2 -0
  33. data/spec/dummy/config/initializers/mime_types.rb +2 -0
  34. data/spec/dummy/config/initializers/session_store.rb +2 -0
  35. data/spec/dummy/config/initializers/wrap_parameters.rb +2 -0
  36. data/spec/dummy/config/routes.rb +2 -1
  37. data/spec/dummy/log/test.log +5615 -0
  38. data/spec/nested_attributes_spec.rb +116 -118
  39. data/spec/params_spec.rb +107 -106
  40. data/spec/spec_helper.rb +5 -3
  41. data/spec/substitutions_spec.rb +202 -172
  42. metadata +27 -8
  43. data/lib/snake-eyes/interface_changes.rb +0 -187
  44. data/lib/snake-eyes/version.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88a300e546ae808376833e94ab9b17f2b33adf32
4
- data.tar.gz: '09c35643682438b1b4101d50eefa73fb9de5e7fa'
3
+ metadata.gz: ba7e12d594cec3ed457677a40f4396e5822f759c
4
+ data.tar.gz: 111a3c36a1d94e507f509f2fc6bd0dc3f5692d43
5
5
  SHA512:
6
- metadata.gz: 41c5b9cadc36ab390107ee6e9f289a0b1038174a77b16ab4f5284ac024e9e6ee8268f625c3408c13fd42bea5e1956bdbabfe8fbd9dba949a3d03760c4449cd7a
7
- data.tar.gz: 5dc4f29e0f87bd583b7649e4e5d46f5328a28d4e313638751be7b7985deeff611f7cb76bbb1afe4940d2f1e676946f919aec15671e9aff37044e10af1ee550a6
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 controllor, `params` accepts an options hash, which can be used to specify which attributes should have the `_attributes` suffix appended.
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("../spec/dummy/Rakefile", __FILE__)
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 {|f| load f }
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 "Run all specs in spec directory (excluding plugin specs)"
28
- RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
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 :default => :spec
32
+ task default: :spec
@@ -1,17 +1,17 @@
1
- require "snake-eyes/version"
2
- require "snake-eyes/interface_changes"
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 :config :configuration
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, :fixture => false
6
- g.fixture_replacement :factory_girl, :dir => 'spec/factories'
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SnakeEyes
4
+ VERSION = '2.0.0'
5
+ end
@@ -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('../config/application', __FILE__)
6
+ require File.expand_path('config/application', __dir__)
5
7
 
6
8
  Rails.application.load_tasks
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ApplicationController < ActionController::Base
2
4
  # Prevent CSRF attacks by raising an exception.
3
5
  # For APIs, you may want to use :null_session instead.
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApplicationHelper
2
4
  end
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
4
  <title>Dummy</title>
5
5
  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
@@ -1,3 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
2
+ # frozen_string_literal: true
3
+
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3
5
  load Gem.bin_path('bundler', 'bundle')
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
- APP_PATH = File.expand_path('../../config/application', __FILE__)
2
+ # frozen_string_literal: true
3
+
4
+ APP_PATH = File.expand_path('../config/application', __dir__)
3
5
  require_relative '../config/boot'
4
6
  require 'rails/commands'