snake-eyes 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/{LICENSE.txt → LICENSE} +1 -1
  3. data/README.md +104 -20
  4. data/Rakefile +29 -1
  5. data/lib/snake-eyes/engine.rb +12 -0
  6. data/lib/snake-eyes/interface_changes.rb +117 -71
  7. data/lib/snake-eyes/version.rb +1 -1
  8. data/spec/dummy/README.rdoc +28 -0
  9. data/spec/dummy/Rakefile +6 -0
  10. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  11. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  12. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  13. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  14. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  15. data/spec/dummy/bin/bundle +3 -0
  16. data/spec/dummy/bin/rails +4 -0
  17. data/spec/dummy/bin/rake +4 -0
  18. data/spec/dummy/bin/setup +29 -0
  19. data/spec/dummy/config.ru +4 -0
  20. data/spec/dummy/config/application.rb +26 -0
  21. data/spec/dummy/config/boot.rb +5 -0
  22. data/spec/dummy/config/database.yml +25 -0
  23. data/spec/dummy/config/environment.rb +5 -0
  24. data/spec/dummy/config/environments/development.rb +41 -0
  25. data/spec/dummy/config/environments/production.rb +79 -0
  26. data/spec/dummy/config/environments/test.rb +42 -0
  27. data/spec/dummy/config/initializers/assets.rb +11 -0
  28. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  29. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  30. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  31. data/spec/dummy/config/initializers/inflections.rb +16 -0
  32. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  33. data/spec/dummy/config/initializers/session_store.rb +3 -0
  34. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  35. data/spec/dummy/config/locales/en.yml +23 -0
  36. data/spec/dummy/config/routes.rb +3 -0
  37. data/spec/dummy/config/secrets.yml +22 -0
  38. data/spec/dummy/db/test.sqlite3 +0 -0
  39. data/spec/dummy/log/test.log +7104 -0
  40. data/spec/dummy/public/404.html +67 -0
  41. data/spec/dummy/public/422.html +67 -0
  42. data/spec/dummy/public/500.html +66 -0
  43. data/spec/dummy/public/favicon.ico +0 -0
  44. data/spec/nested_attributes_spec.rb +231 -0
  45. data/spec/params_spec.rb +154 -0
  46. data/spec/spec_helper.rb +20 -0
  47. data/spec/substitutions_spec.rb +286 -0
  48. metadata +135 -15
  49. data/.gitignore +0 -22
  50. data/Gemfile +0 -3
  51. data/snake-eyes.gemspec +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9528a2e50402eff13ab812c8909b0bb251c301a3
4
- data.tar.gz: 20fefd48f80e6175bfc7f46727c089ad5803fbdc
3
+ metadata.gz: e5d87558dd242a686cdeb61d193ff4785f0dca88
4
+ data.tar.gz: 3342dc90f6aa0cb230758f303d9ba7694135369a
5
5
  SHA512:
6
- metadata.gz: f30b1a86afb2ee7cbdad47cc54cc042693190e6bac57783b001acad97d3f8ee27d4ee8fec90c521f7a78e6af49144356c2429914c3a4116a5aada6f4924d71d1
7
- data.tar.gz: 7f62c295eda2b33f42456f2cac333d002b3c1e080809a27c3a0a1c9908f1fa67c8c3b23cc86781aa6f68be1ce339c67cf8254db4d2cbcd76bbf9debac4d6ea7f
6
+ metadata.gz: 2b52f46eb4f20de9c95d6b00e8ba465c98c40fcae292d06508c3ec6a35b856d25b4f06f41b78cad1364d301c82c0f4ad375ba501f94ee16c63bf89f8db980140
7
+ data.tar.gz: accbf453ddafdf9b49b598a1e89cc01de69285e204a7417fb157de84918224adfd372b1c8860f76940dab4dbccf721119b2c32d0a6dfbe7d88d34b359b2cfc94
@@ -1,4 +1,4 @@
1
- Copyright (c) 2016 Aleck Greenham
1
+ Copyright 2018 Aleck Greenham
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,14 +1,10 @@
1
1
  # SnakeEyes
2
2
 
3
- Automatically convert between camel case APIs to snake case for your Rails code
4
-
5
- ## Important
6
-
7
- 🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧
8
-
9
- If you are using a version below `0.0.4`, please upgrade to avoid [potentially logging sensitive user information](https://github.com/greena13/snake-eyes/issues/1)
3
+ [![Gem](https://img.shields.io/gem/dt/snake-eyes.svg)]()
4
+ [![Build Status](https://travis-ci.org/greena13/snake-eyes.svg)](https://travis-ci.org/greena13/snake-eyes)
5
+ [![GitHub license](https://img.shields.io/github/license/greena13/snake-eyes.svg)](https://github.com/greena13/snake-eyes/blob/master/LICENSE)
10
6
 
11
- 🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧
7
+ Automatically convert between camel case APIs to snake case for your Rails code
12
8
 
13
9
  ## Installation
14
10
 
@@ -71,6 +67,8 @@ Once `snake_eyes_params` has been enabled for a controllor, `params` accepts an
71
67
  end
72
68
  ```
73
69
 
70
+ #### Avoid _attributes suffix on parents: the _ prefix
71
+
74
72
  To specify nested objects that should not have the `_attributes` suffix (but contain attributes that should), you can prefix them with an underscore:
75
73
 
76
74
 
@@ -92,22 +90,108 @@ To specify nested objects that should not have the `_attributes` suffix (but con
92
90
  end
93
91
  ```
94
92
 
95
- ## Limitations
93
+ #### Reference any element of an array: the '*' wildcard
94
+
95
+ To apply the `_attributes` suffix to all elements of an array, use the `'*'` wildcard in place of the array index:
96
+
97
+ ```ruby
98
+ class WithSnakeEyesController < ApplicationController
99
+ snake_eyes_params
100
+
101
+ def show
102
+ # Given
103
+ params(nested_attributes: [ _array: { '*' => :string } ])
104
+
105
+ # If the params are:
106
+ #
107
+ # 'array' => [
108
+ # { 'string' => 'string' },
109
+ # { 'string' => 'string2' },
110
+ # ]
111
+ #
112
+ # What will be returned:
113
+ #
114
+ # 'array' => [
115
+ # { 'string_attributes' => 'string' },
116
+ # { 'string_attributes' => 'string2' },
117
+ # ]
118
+ end
119
+ end
120
+ ```
121
+
122
+ ## Substitutions
96
123
 
97
- SnakeEyes does *not* currently handle the following aspects of converting a JSON API to a Rails-compatible one:
124
+ If you want to substitute alternative values for the ones that the controller actually receives, you can do that using the `substitutions` option:
98
125
 
99
- **Converting arrays of objects to hashes, keyed by item indexes**
126
+ ```ruby
127
+ class WithSnakeEyesController < ApplicationController
128
+ snake_eyes_params
100
129
 
101
- Fox example:
130
+ def show
131
+ # Given
132
+ params(substitutions: {
133
+ shallow_object: {
134
+ price: { replace: 'FREE', with: 0.00 }
135
+ }
136
+ })
137
+
138
+ # If params is:
139
+ #
140
+ # 'shallowObject' => {
141
+ # 'price' => 'FREE'
142
+ # }
143
+ #
144
+ # What will be returned:
145
+ #
146
+ # 'shallow_object' => {
147
+ # 'price' => 0.00
148
+ # }
149
+ end
150
+ end
151
+ ```
102
152
 
103
- ```JSON
104
- {
105
- "a": [
106
- { "b": 1 },
107
- { "b": 2 },
108
- { "b": 3 }
109
- ]
110
- }
153
+ You can also provide multiple substitutions as an array. They are matched left-to-right, and the first matching substitution is the one that is used.
154
+
155
+ ```ruby
156
+ class WithSnakeEyesController < ApplicationController
157
+ snake_eyes_params
158
+
159
+ def show
160
+ # Given
161
+ params(substitutions: {
162
+ shallow_object: {
163
+ price: [
164
+ { replace: 'FREE', with: 0.00 } ,
165
+ { replace: 'EXPENSIVE', with: 999.00 }
166
+ ]
167
+ }
168
+ })
169
+
170
+ # If params is:
171
+ #
172
+ # 'shallowObject' => {
173
+ # 'price' => 'FREE'
174
+ # }
175
+ #
176
+ # What will be returned:
177
+ #
178
+ # 'shallow_object' => {
179
+ # 'price' => 0.00
180
+ # }
181
+
182
+ # If params is:
183
+ #
184
+ # 'shallowObject' => {
185
+ # 'price' => 'EXPENSIVE'
186
+ # }
187
+ #
188
+ # What will be returned:
189
+ #
190
+ # 'shallow_object' => {
191
+ # 'price' => 999.00
192
+ # }
193
+ end
194
+ end
111
195
  ```
112
196
 
113
197
  ## Configuration
data/Rakefile CHANGED
@@ -1,2 +1,30 @@
1
- require "bundler/gem_tasks"
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
2
6
 
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'SnakeEyes'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
23
+
24
+ require 'rspec/core'
25
+ require 'rspec/core/rake_task'
26
+
27
+ desc "Run all specs in spec directory (excluding plugin specs)"
28
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
29
+
30
+ task :default => :spec
@@ -0,0 +1,12 @@
1
+ module SnakeEyes
2
+ class Engine < ::Rails::Engine
3
+
4
+ config.generators do |g|
5
+ g.test_framework :rspec, :fixture => false
6
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
7
+ g.assets false
8
+ g.helper false
9
+ end
10
+
11
+ end
12
+ end
@@ -1,81 +1,38 @@
1
1
  module SnakeEyes
2
2
  module InterfaceChanges
3
+ KEYS_ALWAYS_PRESENT = [
4
+ "controller",
5
+ "action"
6
+ ]
7
+
3
8
  def params(options = {})
4
9
  validate_options(options)
5
10
 
6
11
  original_params = super()
7
12
 
8
- return original_params unless original_params.any?
9
-
10
- # List of subtrees maintained to mark the depth-first traversal's position
11
- # throughout the transformation of the original param's keys, whereby the
12
- # last element is the traversal's current position and backtracking (going
13
- # from child to parent) is achieved by popping off the last element.
14
-
15
- original_params_sub_trees = [
16
- original_params
17
- ]
18
-
19
- # Convert the relatively flat format used to specify the nested attributes
20
- # (easier for specification) into a series of nested objects (easier for
21
- # look-ups)
13
+ params_present = (original_params.keys | KEYS_ALWAYS_PRESENT).length > KEYS_ALWAYS_PRESENT.length
22
14
 
23
- nested_schema = build_nested_schema(options[:nested_attributes] || {})
15
+ unless params_present
16
+ return original_params
17
+ end
24
18
 
25
19
  @previous_params ||= { }
26
20
 
27
- return @previous_params[nested_schema] if @previous_params[nested_schema]
28
-
29
- # Similar to original_params_sub_trees, a list of subtrees used to maintain
30
- # the traversal position of nested_schema. This is kept in sync with the
31
- # traversal of original_params, to ensure the correct leaf of
32
- # nested_schema is checked at each point in the traversal of original_params
33
-
34
- nested_schema_sub_trees = [
35
- nested_schema
36
- ]
37
-
38
- transformed_params = original_params.deep_transform_keys do |original_key|
39
- # Synchronise the original params sub-tree with the current key being
40
- # transformed. We can detect that the sub-tree is stale because the key
41
- # being transformed does not appear amongst its own. When the sub-tree is
42
- # indeed stale, move the position to its parent for the original params
43
- # sub-tree and the nested schema sub-tree and repeat the check.
44
-
45
- while original_params_sub_trees.length > 1 && original_params_sub_trees.last[original_key].nil?
46
- original_params_sub_trees.pop
47
- nested_schema_sub_trees.pop
21
+ nested_schema = build_options_schema(options[:nested_attributes] || {}, '') do |target, parent_name|
22
+ if parent_name.empty? || parent_name.starts_with?('_')
23
+ target
24
+ else
25
+ target.merge({ _attributes_suffix: true })
48
26
  end
27
+ end
49
28
 
50
- original_params_sub_tree = original_params_sub_trees.last[original_key]
51
-
52
- # Append the '_attributes' suffix if the original params key has the
53
- # same name and is nested in the same place as one mentioned in the
54
- # nested_attributes option
29
+ options[:nested_attributes] = nested_schema
55
30
 
56
- transformed_key_base = original_key.underscore
31
+ return @previous_params[options] if @previous_params[options]
57
32
 
58
- transformed_key =
59
- if nested_schema_sub_trees.last[transformed_key_base]
60
- transformed_key_base + '_attributes'
61
- else
62
- transformed_key_base
63
- end
33
+ transformed_params = deep_transform(original_params, options)
64
34
 
65
- if original_params_sub_tree.kind_of?(Hash)
66
- original_params_sub_trees.push(original_params_sub_tree)
67
-
68
- nested_schema_sub_trees.push(
69
- nested_schema_sub_trees.last[transformed_key_base] ||
70
- nested_schema_sub_trees.last['_' + transformed_key_base] ||
71
- {}
72
- )
73
- end
74
-
75
- transformed_key
76
- end
77
-
78
- @previous_params[nested_schema] = @snake_eyes_params = ActionController::Parameters.new(transformed_params)
35
+ @previous_params[options] = @snake_eyes_params = ActionController::Parameters.new(transformed_params)
79
36
 
80
37
  log_snakized_params
81
38
 
@@ -86,7 +43,7 @@ module SnakeEyes
86
43
 
87
44
  def validate_options(options)
88
45
  options.keys.each do |key|
89
- raise ArgumentError.new("SnakeEyes: params received unrecognised option '#{key}'") if key != :nested_attributes
46
+ raise ArgumentError.new("SnakeEyes: params received unrecognised option '#{key}'") if key != :nested_attributes && key != :substitutions
90
47
  end
91
48
  end
92
49
 
@@ -100,28 +57,117 @@ module SnakeEyes
100
57
  end
101
58
  end
102
59
 
103
- def build_nested_schema(attributes_list = [])
60
+ def deep_transform(target, options = {})
104
61
 
105
- if attributes_list.kind_of?(Array)
62
+ nested_attributes = options[:nested_attributes] || {}
63
+
64
+ substitutions =
65
+ if options[:substitutions].kind_of?(Array)
66
+ options[:substitutions].map(&:stringify_keys)
67
+ else
68
+ (options[:substitutions] || {}).stringify_keys
69
+ end
106
70
 
107
- attributes_list.inject({}) do |memo, nested_attribute|
108
- memo.merge(build_nested_schema(nested_attribute))
71
+ if target.kind_of?(Array)
72
+ target.map do |targetElement|
73
+ deep_transform(targetElement, {
74
+ nested_attributes: nested_attributes['*'],
75
+ substitutions: substitutions.kind_of?(Array) ? {} : substitutions['*']
76
+ })
109
77
  end
110
78
 
111
- elsif attributes_list.kind_of?(Hash)
79
+ elsif target.kind_of?(Hash)
112
80
 
113
- attributes_list.inject({}) do |memo, key_and_value|
81
+ target.inject({}) do |memo, key_and_value|
114
82
  key, value = key_and_value
115
- memo[key.to_s] = build_nested_schema(value)
83
+
84
+ # Append the '_attributes' suffix if the original params key has the
85
+ # same name and is nested in the same place as one mentioned in the
86
+ # nested_attributes option
87
+
88
+ transformed_key_base = key.to_s.underscore
89
+
90
+ transformed_key =
91
+ if nested_attributes[transformed_key_base] && nested_attributes[transformed_key_base][:_attributes_suffix]
92
+ transformed_key_base + '_attributes'
93
+ else
94
+ transformed_key_base
95
+ end
96
+
97
+ transformed_value = deep_transform(value,
98
+ {
99
+ nested_attributes: nested_attributes[transformed_key_base] || nested_attributes['_' + transformed_key_base],
100
+ substitutions: substitutions.kind_of?(Array) ? {} : substitutions[transformed_key_base]
101
+ }
102
+ )
103
+
104
+ memo[transformed_key] = transformed_value
105
+
116
106
  memo
117
107
  end
118
108
 
119
109
  else
110
+ perform_substitution(target, substitutions)
111
+ end
120
112
 
121
- { attributes_list.to_s.underscore => {} }
113
+ end
114
+
115
+ def perform_substitution(target, substitution)
116
+ if substitution.kind_of?(Array)
117
+ matching_substitution = substitution.find do |substitution_item|
118
+ has_substitution_keys?(substitution_item) && target === substitution_item["replace"]
119
+ end
120
+
121
+ if matching_substitution
122
+ matching_substitution["with"]
123
+ else
124
+ target
125
+ end
126
+
127
+ else
128
+ if has_substitution_keys?(substitution)
129
+ target === substitution["replace"] ? substitution["with"] : target
130
+ else
131
+ target
132
+ end
133
+ end
134
+ end
135
+
136
+ def has_substitution_keys?(substitution)
137
+ substitution.has_key?("replace") && substitution.has_key?("with")
138
+ end
139
+
140
+ def build_options_schema(attributes_list = [], parent_name = '', options = {}, &block)
141
+
142
+ if attributes_list.kind_of?(Array)
143
+ attributes_array = attributes_list.inject({}) do |memo, nested_attribute|
144
+ memo.merge(build_options_schema(nested_attribute, parent_name, options, &block))
145
+ end
146
+
147
+ yield(attributes_array, parent_name)
148
+
149
+ elsif attributes_list.kind_of?(Hash) && (!options[:internal_attributes] || (attributes_list.keys & options[:internal_attributes]).length > options[:internal_attributes].length)
150
+
151
+ attributes_hash = attributes_list.inject({}) do |memo, key_and_value|
152
+ key, value = key_and_value
153
+
154
+ memo[key.to_s] = yield(build_options_schema(value, '', options, &block), key.to_s)
155
+
156
+ memo
157
+ end
158
+
159
+ yield(attributes_hash, parent_name)
160
+ else
161
+
162
+ {
163
+ attributes_list.to_s.underscore => yield({}, attributes_list.to_s.underscore)
164
+ }
122
165
 
123
166
  end
124
167
 
125
168
  end
169
+
170
+
126
171
  end
172
+
127
173
  end
@@ -1,3 +1,3 @@
1
1
  module SnakeEyes
2
- VERSION = "0.0.8"
2
+ VERSION = "0.1.0"
3
3
  end