camel_snake_struct 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +21 -0
- data/CHANGELOG.md +2 -0
- data/README.md +81 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/setup +8 -0
- data/camel_snake_struct.gemspec +25 -0
- data/gems.rb +15 -0
- data/lib/camel_snake_struct.rb +139 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e030119b3af6eaca6666865803895cf418913a62152decc3578af302ee62dc60
|
4
|
+
data.tar.gz: 9bd9ef3d9d215900202ba1388c13b784e54207f7e11accf96a6bd23332b0bcb7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eca1edf5388aa041ac1c68438a2e24139f4a2e9263470f766bef1ff65c95943bd5593dd47d9ffe14d423a7ada766aedc6f8834859b142551dc8593e4c84d2ee3
|
7
|
+
data.tar.gz: 0306446d56b056675a72d591ba125b7eaa1a98e507069d4cbedd76161b5983eff18d1e47de77306e8e4d44b074bb2517a08a6e1acbf0d8bd0d0a6aa2629aaa73
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rspec
|
3
|
+
- rubocop-rake
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
NewCops: enable
|
7
|
+
TargetRubyVersion: 2.4
|
8
|
+
Exclude:
|
9
|
+
- 'vendor/**/*'
|
10
|
+
|
11
|
+
Style:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Metrics/AbcSize:
|
15
|
+
Max: 25
|
16
|
+
|
17
|
+
Metrics/MethodLength:
|
18
|
+
Max: 15
|
19
|
+
|
20
|
+
Metrics/ClassLength:
|
21
|
+
Max: 200
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# CamelSnake
|
2
|
+
|
3
|
+
Easily access camelCased hashes in a ruby_friendly_way.
|
4
|
+
Main focus is handling responses from APIs that use camelCased keys.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'camel_snake'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle install
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install camel_snake
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
For once of hashes
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
result = CamelSnakeStruct.new('version' => 1, 'rubyVersion' => '2.5.0',
|
28
|
+
'sites' => [{ 'url' => "https://duckduckgo.com", 'searchEngine' => true },
|
29
|
+
{ 'url' => "https://d.tube/", 'searchEngine' => false }])
|
30
|
+
|
31
|
+
puts result.version # 1
|
32
|
+
puts result.ruby_version # 2.5.0
|
33
|
+
puts result.sites[0].url # https://duckduckgo.com
|
34
|
+
puts result.sites[1].url # https://d.tube/
|
35
|
+
puts result.unknown # NoMethodError
|
36
|
+
puts result['version'] # 1
|
37
|
+
```
|
38
|
+
|
39
|
+
Or Learning Structs
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
MyLearningStruct = Class.new(CamelSnakeStruct)
|
43
|
+
|
44
|
+
result1 = MyLearningStruct.new('data' => [{ 'name' => 'Jeff' }])
|
45
|
+
puts result1.data.map(&:name) # ["Jeff"]
|
46
|
+
# never received errors key before
|
47
|
+
puts result.errors # NoMethodError
|
48
|
+
|
49
|
+
result2 = MyLearningStruct.new('errors' => ['failed to get response'])
|
50
|
+
# it remembers the shape from the first succesfull request
|
51
|
+
puts result2.data.map(&:name) # []
|
52
|
+
puts result.errors # ["failed to get response"]
|
53
|
+
|
54
|
+
MyLoadedStruct = Class.new(CamelSnakeStruct)
|
55
|
+
|
56
|
+
MyLoadedStruct.example('data' => [{ 'name' => 'Jeff' }], 'errors' => [], 'date' => { 'timezone' => 'UTC', 'unixTime' => 0})
|
57
|
+
|
58
|
+
result3 = MyLoadedStruct.new({'date' => { }})
|
59
|
+
puts result3.data # []
|
60
|
+
puts result3.errors # []
|
61
|
+
puts result3.date.timezone # nil
|
62
|
+
puts result3.date.unix_time # nil
|
63
|
+
```
|
64
|
+
|
65
|
+
### Limitations
|
66
|
+
|
67
|
+
* Expects to receive a hash
|
68
|
+
* Only works with string keys
|
69
|
+
* Does not work well with keys that point to array of arrays
|
70
|
+
* expects the hash to always have the same struct for learning structs
|
71
|
+
|
72
|
+
## Development
|
73
|
+
|
74
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
75
|
+
|
76
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/camel_snake.
|
81
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "camel_snake_struct"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../gems.rb",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../gems.rb",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/bin/setup
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "camel_snake_struct"
|
3
|
+
spec.version = "0.1.0"
|
4
|
+
spec.authors = ["Grant Petersen-Speelman"]
|
5
|
+
spec.email = ["grant@nexl.io"]
|
6
|
+
|
7
|
+
spec.summary = "Easily access camelCased hashes in a ruby friendly way"
|
8
|
+
spec.description = "Easily access camelCased hashes in a ruby friendly way"
|
9
|
+
spec.homepage = "https://github.com/NEXL-LTS/camel_snake_struct-ruby"
|
10
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
|
11
|
+
|
12
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
13
|
+
spec.metadata["source_code_uri"] = "https://github.com/NEXL-LTS/camel_snake_struct-ruby"
|
14
|
+
spec.metadata["changelog_uri"] = "https://github.com/NEXL-LTS/camel_snake_struct/CHANGELOG.md"
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
spec.add_dependency 'activesupport', '>= 3.2', '< 7.0'
|
25
|
+
end
|
data/gems.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in camel_snake.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem "rake", "~> 12.0"
|
7
|
+
gem "rspec", "~> 3.0"
|
8
|
+
gem "rubocop"
|
9
|
+
gem "rubocop-rake"
|
10
|
+
gem "rubocop-rspec"
|
11
|
+
gem "simplecov"
|
12
|
+
|
13
|
+
if ENV['GEM_VERSIONS'] == 'min'
|
14
|
+
gem 'activesupport', '~> 3.2.0'
|
15
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'active_support/core_ext/string'
|
2
|
+
|
3
|
+
class CamelSnakeStruct
|
4
|
+
def self.example(data)
|
5
|
+
new_example = new(data)
|
6
|
+
walk_example(new_example)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.walk_example(new_example)
|
10
|
+
new_example.send(:_method_to_key).keys.each do |m_name|
|
11
|
+
result = new_example.public_send(m_name)
|
12
|
+
if result.is_a?(CamelSnakeStruct)
|
13
|
+
walk_example(result)
|
14
|
+
elsif result.is_a?(Array) && result.first.is_a?(CamelSnakeStruct)
|
15
|
+
walk_example(result.first)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(hash)
|
21
|
+
@_raw_hash = hash&.to_h || {}
|
22
|
+
@_method_to_key = @_raw_hash.keys.each_with_object({}) do |key, mapping|
|
23
|
+
normalize_key = key.gsub('@', '').gsub('.', '_')
|
24
|
+
mapping[normalize_key] = key
|
25
|
+
mapping[normalize_key.underscore] = key
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
_val(@_raw_hash[key])
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_h
|
34
|
+
to_hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
@_raw_hash
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
attr_reader :_method_to_key
|
44
|
+
|
45
|
+
def _val(val)
|
46
|
+
if val.is_a?(Hash)
|
47
|
+
CamelSnakeStruct.new(val)
|
48
|
+
elsif val.is_a?(Array)
|
49
|
+
val.map { |v| _val(v) }
|
50
|
+
else
|
51
|
+
val
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_missing(method_name, *arguments, &block)
|
56
|
+
camelize_key = __method_to_key(method_name)
|
57
|
+
if camelize_key
|
58
|
+
if _define_new_method(method_name, camelize_key)
|
59
|
+
send(method_name)
|
60
|
+
else # no method defined for empty arrays as we don't know what it returns
|
61
|
+
@_raw_hash[camelize_key]
|
62
|
+
end
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def respond_to_missing?(method_name, include_private = false)
|
69
|
+
camelize_key = __method_to_key(method_name)
|
70
|
+
!camelize_key.nil? || super
|
71
|
+
end
|
72
|
+
|
73
|
+
def __method_to_key(method_name)
|
74
|
+
@_method_to_key[method_name.to_s]
|
75
|
+
end
|
76
|
+
|
77
|
+
def _define_hash_method(name, key)
|
78
|
+
is_sub_class = self.class != CamelSnakeStruct
|
79
|
+
if is_sub_class
|
80
|
+
klass = _define_sub_class(name)
|
81
|
+
self.class.send(:define_method, name) { @_raw_hash[key] && klass.new(@_raw_hash[key]) }
|
82
|
+
else
|
83
|
+
define_singleton_method(name) { CamelSnakeStruct.new(@_raw_hash[key]) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def _define_array_method(name, key)
|
88
|
+
is_sub_class = self.class != CamelSnakeStruct
|
89
|
+
if is_sub_class
|
90
|
+
klass = _define_sub_class(name.to_s.singularize)
|
91
|
+
self.class.send(:define_method, name) { (@_raw_hash[key] || []).map { |v| klass.new(v) } }
|
92
|
+
else
|
93
|
+
define_singleton_method(name) { @_raw_hash[key].map { |v| CamelSnakeStruct.new(v) } }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def _define_scaler_array_method(name, key)
|
98
|
+
is_sub_class = self.class != CamelSnakeStruct
|
99
|
+
if is_sub_class
|
100
|
+
self.class.send(:define_method, name) { (@_raw_hash[key] || []) }
|
101
|
+
else
|
102
|
+
define_singleton_method(name) { @_raw_hash[key] }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def _define_value_method(name, key)
|
107
|
+
is_sub_class = self.class != CamelSnakeStruct
|
108
|
+
if is_sub_class
|
109
|
+
self.class.send(:define_method, name) { @_raw_hash[key] }
|
110
|
+
else
|
111
|
+
define_singleton_method(name) { @_raw_hash[key] }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def _define_new_method(name, key)
|
116
|
+
name = name.to_sym
|
117
|
+
val = @_raw_hash[key]
|
118
|
+
if val.is_a?(Hash)
|
119
|
+
_define_hash_method(name, key)
|
120
|
+
elsif val.is_a?(Array) && val.first.is_a?(Hash)
|
121
|
+
_define_array_method(name, key)
|
122
|
+
elsif val.is_a?(Array) && val.empty?
|
123
|
+
return false
|
124
|
+
elsif val.is_a?(Array)
|
125
|
+
_define_scaler_array_method(name, key)
|
126
|
+
else
|
127
|
+
_define_value_method(name, key)
|
128
|
+
end
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def _define_sub_class(name)
|
133
|
+
sub_class_name = name.to_s.camelize(:upper)
|
134
|
+
self.class.const_get(sub_class_name, false)
|
135
|
+
rescue NameError
|
136
|
+
self.class.const_set(sub_class_name, Class.new(CamelSnakeStruct))
|
137
|
+
self.class.const_get(sub_class_name, false)
|
138
|
+
end
|
139
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: camel_snake_struct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Grant Petersen-Speelman
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7.0'
|
33
|
+
description: Easily access camelCased hashes in a ruby friendly way
|
34
|
+
email:
|
35
|
+
- grant@nexl.io
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- ".gitignore"
|
41
|
+
- ".rspec"
|
42
|
+
- ".rubocop.yml"
|
43
|
+
- CHANGELOG.md
|
44
|
+
- README.md
|
45
|
+
- Rakefile
|
46
|
+
- bin/console
|
47
|
+
- bin/rspec
|
48
|
+
- bin/rubocop
|
49
|
+
- bin/setup
|
50
|
+
- camel_snake_struct.gemspec
|
51
|
+
- gems.rb
|
52
|
+
- lib/camel_snake_struct.rb
|
53
|
+
homepage: https://github.com/NEXL-LTS/camel_snake_struct-ruby
|
54
|
+
licenses: []
|
55
|
+
metadata:
|
56
|
+
homepage_uri: https://github.com/NEXL-LTS/camel_snake_struct-ruby
|
57
|
+
source_code_uri: https://github.com/NEXL-LTS/camel_snake_struct-ruby
|
58
|
+
changelog_uri: https://github.com/NEXL-LTS/camel_snake_struct/CHANGELOG.md
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 2.4.0
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubygems_version: 3.1.4
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: Easily access camelCased hashes in a ruby friendly way
|
78
|
+
test_files: []
|