snake-eyes 0.0.8 → 0.1.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 (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