recursive_case_indifferent_ostruct 0.1
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.
- checksums.yaml +7 -0
- data/README.md +110 -0
- data/lib/recursive_case_indifferent_ostruct.rb +167 -0
- metadata +46 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b398b7f98e4169257f4811888715dd82fd43b4b2
|
4
|
+
data.tar.gz: f44fee346580aec8bc67449f40223967852f3bc6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e23e8daeca037f194a719201a486c509462c0e0abaa5bb52f8eb8e392075e885b96e2229195860b1bfc63338ff44202124cab26d8cc2d232bb4f1255cca5c399
|
7
|
+
data.tar.gz: fde5b277fe47328995e2f6d762f2ea14c11ed1bb41f1b63566807b032541d16673c454737140c1a088773397b527bccf98c65b764aa54221882f5a28433ddc7e
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Recursive Case Indifferent Ostruct
|
2
|
+
|
3
|
+
Takes a hash of snake, camel, train, or kabab case and make its
|
4
|
+
attributes accessible with any other case.
|
5
|
+
|
6
|
+
The main application is for working with JSON from an API and standardizing
|
7
|
+
to a Ruby-centric underscore/snake case interface.
|
8
|
+
|
9
|
+
##Usage
|
10
|
+
|
11
|
+
Gemfile
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "recursive_case_indifferent_ostruct"
|
15
|
+
````
|
16
|
+
|
17
|
+
|
18
|
+
## Examples
|
19
|
+
```ruby
|
20
|
+
user = RecursiveCaseIndifferentOstruct.new({
|
21
|
+
"firstName" => "Tommy",
|
22
|
+
"first-name" => "Bobby",
|
23
|
+
"LAST_NAME" => "Johnson",
|
24
|
+
"Birth-Place" => "Springfield",
|
25
|
+
"father" => {
|
26
|
+
"age" 72
|
27
|
+
},
|
28
|
+
"siblings" => [
|
29
|
+
{relation: "brother", age: 12}
|
30
|
+
]
|
31
|
+
}, :lower_camel)
|
32
|
+
|
33
|
+
# access attributes like so
|
34
|
+
user.first_name # "Tommy"
|
35
|
+
user.last_name # "Johnson"
|
36
|
+
user.father.age # 72
|
37
|
+
user.siblings[0].age # 12
|
38
|
+
|
39
|
+
user.birth_place = "New York" # set fields
|
40
|
+
user.first_name = "Ken" # sets {"firstName" => "Ken", "first-name" => "Ken"}
|
41
|
+
user.mothers_maiden = "Woods" # Since case is mixed in that hash, use default :lower_camel
|
42
|
+
|
43
|
+
|
44
|
+
user.to_h
|
45
|
+
# {
|
46
|
+
# "firstName" => "Ken",
|
47
|
+
# "first-name" => "Bobby",
|
48
|
+
# "LAST_NAME" => "Johnson",
|
49
|
+
# "Birth-Place" => "New York",
|
50
|
+
# "father" => {
|
51
|
+
# "age" 72
|
52
|
+
# },
|
53
|
+
# "siblings" => [
|
54
|
+
# {relation: "brother", age: 12}
|
55
|
+
# ],
|
56
|
+
# "mothersMaiden" => "Woods",
|
57
|
+
# }
|
58
|
+
```
|
59
|
+
|
60
|
+
|
61
|
+
### Case Matching
|
62
|
+
Since the key matching is fuzzy, should there be two attributes with
|
63
|
+
the same name but different case (`first-name` and `firstName`), the
|
64
|
+
library will return first value that it finds. For an assignment it
|
65
|
+
will assign all matching attributes to the new value.
|
66
|
+
|
67
|
+
### Default Case
|
68
|
+
Should you assign an attribute to the hash that does not already exist
|
69
|
+
the library will try to figure out which case to use based on other
|
70
|
+
keys in the hash. If it cannot determine which case is being used
|
71
|
+
it will fall back to `RecursiveCaseIndifferentOstruct::DEFAULT_CASE=:snake`
|
72
|
+
which you could set to override. Another option is to pass the default
|
73
|
+
case on initialization `RecursiveCaseIndifferentOstruct.new(hash, :snake)`.
|
74
|
+
|
75
|
+
Available options:
|
76
|
+
* `:snake` *this_is_snake*
|
77
|
+
* `:lower_camel` *thisIsLowerCamel*
|
78
|
+
* `:upper_camel` *ThisIsUpperCamel*
|
79
|
+
* `:kabab` *this-is-kabab*
|
80
|
+
* `:train` *This-Is-Train*
|
81
|
+
|
82
|
+
If you need to assign a value to a key that has an odd case you can
|
83
|
+
use a string with the bracket syntax like so: `json["PI:Value"] = 3.14`.
|
84
|
+
The value can still be accessed via `json.pi_value`.
|
85
|
+
|
86
|
+
|
87
|
+
### Hash Methods
|
88
|
+
`RecursiveCaseIndifferentOstruct` will pass methods along to the hash
|
89
|
+
if the method is not defined and there is not attribute matching that
|
90
|
+
name. However, all the hash methods operate on the original hash, so
|
91
|
+
`#has_key?`, `#each`, `#fetch`, etc, will not be case-indifferent.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
json = RecursiveCaseIndifferentOstruct.new({
|
95
|
+
ID: 123,
|
96
|
+
userName: "pax"
|
97
|
+
})
|
98
|
+
|
99
|
+
json.has_key?(:user_name) # false
|
100
|
+
json.has_key?("userName") # true
|
101
|
+
json.fetch(:id) # nil
|
102
|
+
json.keys # [:ID, :userName]
|
103
|
+
|
104
|
+
```
|
105
|
+
|
106
|
+
|
107
|
+
## Tests
|
108
|
+
`rake test`
|
109
|
+
|
110
|
+
|
@@ -0,0 +1,167 @@
|
|
1
|
+
|
2
|
+
class RecursiveCaseIndifferentOstruct
|
3
|
+
DEFAULT_CASE=:snake
|
4
|
+
attr_accessor :default_case
|
5
|
+
|
6
|
+
def initialize(hash={}, casing=nil)
|
7
|
+
@default_case = casing || DEFAULT_CASE
|
8
|
+
@hash = hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_h
|
12
|
+
@hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
handle_get(key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(key, value)
|
20
|
+
handle_assignment(key, value, true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method_sym, *args, &block)
|
24
|
+
method_name = method_sym.to_s
|
25
|
+
is_assignment = method_name.slice(-1) == '='
|
26
|
+
is_get = !is_assignment && args.length == 0 && !@hash.respond_to?(method_name)
|
27
|
+
|
28
|
+
# remove = if an assignment
|
29
|
+
if is_assignment
|
30
|
+
method_name = method_name.slice(0, method_name.length - 1)
|
31
|
+
end
|
32
|
+
|
33
|
+
if is_assignment
|
34
|
+
return handle_assignment(method_name, args.first)
|
35
|
+
elsif is_get
|
36
|
+
return handle_get(method_name)
|
37
|
+
else
|
38
|
+
@hash.send(method_name, *args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# if they called a function and passed some args, then
|
42
|
+
# raise no method error
|
43
|
+
#super
|
44
|
+
end
|
45
|
+
|
46
|
+
def case_being_used
|
47
|
+
# TODO: If all nil then scan for keys in nested hashes
|
48
|
+
cases_found = @hash.keys.map do |key|
|
49
|
+
if !key.to_s.match(/^[a-z]+\-[a-z]/).nil? # kabab
|
50
|
+
:kabab
|
51
|
+
elsif key.to_s.include?('_')# snake
|
52
|
+
:snake
|
53
|
+
elsif !key.to_s.match(/^[a-z]+[A-Z]/).nil? # lower camel
|
54
|
+
:lower_camel
|
55
|
+
elsif !key.to_s.match(/^[A-Z][a-z]+[A-Z]/).nil? # upper camel
|
56
|
+
:upper_camel
|
57
|
+
elsif !key.to_s.match(/^[A-Z][a-z]+\-[A-Z]/).nil? # Train-Case
|
58
|
+
:train
|
59
|
+
else
|
60
|
+
# Could just be one word in that case who knows what case it is
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
if cases_found.compact.uniq.size == 1
|
66
|
+
cases_found.compact.first
|
67
|
+
else
|
68
|
+
return @default_case
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_matching_keys(to_find)
|
74
|
+
@hash.keys.select do |key|
|
75
|
+
self.class.clean_key(to_find) == self.class.clean_key(key)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# string for comparison
|
80
|
+
def self.clean_key(key)
|
81
|
+
key.to_s.downcase.gsub(/[\W_]/, '')
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def handle_assignment(method_name, value, allow_override=false)
|
86
|
+
# assign all matching keys
|
87
|
+
keys = find_matching_keys(method_name)
|
88
|
+
|
89
|
+
# assigning a new value
|
90
|
+
if keys.empty?
|
91
|
+
# allow custom case via []=
|
92
|
+
if method_name.is_a?(String) && allow_override
|
93
|
+
@hash[method_name] = value
|
94
|
+
else
|
95
|
+
@hash[ensure_default_case(method_name)] = value
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
keys.each do |key|
|
101
|
+
@hash[key] = value
|
102
|
+
end
|
103
|
+
|
104
|
+
value
|
105
|
+
end
|
106
|
+
|
107
|
+
def handle_get(method_name)
|
108
|
+
keys = find_matching_keys(method_name)
|
109
|
+
|
110
|
+
value = @hash[keys.first]
|
111
|
+
|
112
|
+
if value.is_a?(Array)
|
113
|
+
# Make any hashes in the array an ostruct
|
114
|
+
value.map do |item|
|
115
|
+
item.is_a?(Hash) ? RecursiveCaseIndifferentOstruct.new(item, @default_case) : item
|
116
|
+
end
|
117
|
+
elsif value.is_a?(Hash)
|
118
|
+
RecursiveCaseIndifferentOstruct.new(value, @default_case)
|
119
|
+
else
|
120
|
+
value
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def ensure_default_case(key)
|
126
|
+
# Pull out the words/numbers from the key name
|
127
|
+
# find: lower camel UPCASE numbers
|
128
|
+
re = /( (?:[a-z]+) | (?:[A-Z][a-z]+) | (?:[A-Z]+) | [0-9]+ )/x
|
129
|
+
words = key.to_s.scan(re).map do |word|
|
130
|
+
word = word.first
|
131
|
+
if word.scan(/[A-Z]/).size < 2
|
132
|
+
word.downcase
|
133
|
+
else
|
134
|
+
word # it's an acronym in whichcase lets leave it.
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
if @default_case == :snake
|
139
|
+
words.join('_')
|
140
|
+
|
141
|
+
elsif @default_case == :kabab
|
142
|
+
words.join('-')
|
143
|
+
|
144
|
+
elsif @default_case == :lower_camel
|
145
|
+
words.map.with_index do |w, i|
|
146
|
+
if i > 0
|
147
|
+
w[0] = w[0].upcase
|
148
|
+
w
|
149
|
+
else
|
150
|
+
w
|
151
|
+
end
|
152
|
+
end.join
|
153
|
+
|
154
|
+
elsif @default_case == :upper_camel
|
155
|
+
words.map do |w|
|
156
|
+
w[0] = w[0].upcase
|
157
|
+
end.join
|
158
|
+
|
159
|
+
elsif @default_case == :train
|
160
|
+
words.map do |w|
|
161
|
+
w[0] = w[0].upcase
|
162
|
+
end.join('-')
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: recursive_case_indifferent_ostruct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tyler Roberts
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: See github.
|
14
|
+
email: code@polar-concepts.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- lib/recursive_case_indifferent_ostruct.rb
|
21
|
+
homepage: http://github.com/bdevel/recursive_case_indifferent_ostruct
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 2.4.5
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: Access JSON or Hashes attributes without regards to snake_case, camelCase,
|
45
|
+
or kabab-case.
|
46
|
+
test_files: []
|