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.
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'