class2 0.0.2 → 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.
- checksums.yaml +4 -4
- data/Changes +6 -0
- data/README.md +61 -69
- data/lib/class2.rb +45 -9
- data/lib/class2/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab6287d91741715040bde7550bf9a9a0de474d4b
|
4
|
+
data.tar.gz: 46e7ab44843d52de375a7a259f976e6c653e9d4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19d653a1b5f20940ecf956f3952ebeabfcd8761e249b9fb136d34d30baf975f3a4db4ef124e982863f3157e6538b7ee2e5853e06b8113b3535a0183c311d4059
|
7
|
+
data.tar.gz: ad13a618934fc32fbe6ac72d8c0ee6645358d5d35df5b732217bb2c632abfba6a07eb957f43b2ee86ce549da890fcf99c82e17d7eb3f7ae9edd4dba183e7c73d
|
data/Changes
CHANGED
data/README.md
CHANGED
@@ -7,15 +7,13 @@ Easily create hierarchies of classes that support nested attributes, type conver
|
|
7
7
|
## Usage
|
8
8
|
|
9
9
|
```rb
|
10
|
-
Class2
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
]
|
18
|
-
)
|
10
|
+
Class2 :user => [
|
11
|
+
:name, :age,
|
12
|
+
:addresses => [
|
13
|
+
:city, :state, :zip,
|
14
|
+
:country => [ :name, :code ]
|
15
|
+
]
|
16
|
+
]
|
19
17
|
```
|
20
18
|
|
21
19
|
This creates 3 classes: `User`, `Address`, and `Country` with the following attribute accessors:
|
@@ -29,23 +27,22 @@ Each of these classes are created with [several additional methods](#methods).
|
|
29
27
|
You can also specify types:
|
30
28
|
|
31
29
|
```rb
|
32
|
-
Class2
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
}
|
44
|
-
)
|
30
|
+
Class2 :user => {
|
31
|
+
:name => String,
|
32
|
+
:age => Fixnum,
|
33
|
+
:addresses => [
|
34
|
+
:city, :state, :zip, # No explicit types for these
|
35
|
+
:country => {
|
36
|
+
:name => String,
|
37
|
+
:code => String
|
38
|
+
}
|
39
|
+
]
|
40
|
+
}
|
45
41
|
```
|
46
|
-
|
42
|
+
An attempt is made to convert the attribute's type when a value is passed to the constructor
|
43
|
+
or set via its accessor. Attributes without types are treated as is.
|
47
44
|
|
48
|
-
After calling either one of the above you can do following:
|
45
|
+
After calling either one of the above you can do the following:
|
49
46
|
|
50
47
|
```rb
|
51
48
|
user = User.new(
|
@@ -74,11 +71,9 @@ user.addresses << address
|
|
74
71
|
User.new(:name => "sshaw") == User.new(:name => "sshaw") # true
|
75
72
|
```
|
76
73
|
|
77
|
-
Unknown attributes passed to the constructor are ignored.
|
78
|
-
|
79
74
|
`Class2` can create classes with typed attributes from example hashes.
|
80
75
|
This makes it possible to build classes for things like API responses, using the API response
|
81
|
-
itself
|
76
|
+
itself as the specification:
|
82
77
|
|
83
78
|
```rb
|
84
79
|
# From JSON.parse
|
@@ -99,9 +94,7 @@ response = [
|
|
99
94
|
}
|
100
95
|
]
|
101
96
|
|
102
|
-
Class2
|
103
|
-
:commit => response.first
|
104
|
-
)
|
97
|
+
Class2 :commit => response.first
|
105
98
|
|
106
99
|
commit = Commit.new(response.first)
|
107
100
|
commit.author.name # "sshaw"
|
@@ -128,17 +121,11 @@ Custom conversions are possible, just add the conversion to
|
|
128
121
|
`Class2` can use an exiting namespace or create a new one:
|
129
122
|
|
130
123
|
```rb
|
131
|
-
Class2
|
132
|
-
My::Namespace,
|
133
|
-
:user => %i[name age]
|
134
|
-
)
|
124
|
+
Class2 My::Namespace, :user => %i[name age]
|
135
125
|
|
136
126
|
My::Namespace::User.new(:name => "sshaw")
|
137
127
|
|
138
|
-
Class2
|
139
|
-
"New::Namespace",
|
140
|
-
:user => %i[name age]
|
141
|
-
)
|
128
|
+
Class2 "New::Namespace", :user => %i[name age]
|
142
129
|
|
143
130
|
New::Namespace::User.new(:name => "sshaw")
|
144
131
|
```
|
@@ -168,60 +155,65 @@ Classes created by `Class2` will have:
|
|
168
155
|
* `#eql?` and `#==`
|
169
156
|
* `#hash`
|
170
157
|
|
171
|
-
|
158
|
+
### Constructor
|
172
159
|
|
173
|
-
|
160
|
+
The default constructor ignores unknown attributes.
|
161
|
+
If you prefer to raise an exception include `Class2::StrictConstructor`:
|
174
162
|
|
175
163
|
```rb
|
176
|
-
Class2
|
177
|
-
|
178
|
-
class User
|
179
|
-
def first_initial
|
180
|
-
name[0] if name
|
181
|
-
end
|
164
|
+
Class2 :user => %w[id name age] do
|
165
|
+
include Class2::StrictConstructor
|
182
166
|
end
|
183
|
-
|
184
|
-
User.new(:name => "sshaw").first_initial
|
185
167
|
```
|
186
168
|
|
187
|
-
|
169
|
+
Now an `ArgumentError` will be raised if anything but `id`, `name`, or
|
170
|
+
`age` are passed in.
|
188
171
|
|
189
|
-
|
190
|
-
Here we want `:users` to be an attribute, not a type:
|
172
|
+
Also see [Customizations](#customizations).
|
191
173
|
|
192
|
-
|
193
|
-
Class2(:foo => [ :users => [ :id, :age, :foo ] ])
|
194
|
-
```
|
174
|
+
### Customizations
|
195
175
|
|
196
|
-
|
176
|
+
To add methods or include modules just open up the class and write or include them:
|
197
177
|
|
198
178
|
```rb
|
199
|
-
Class2
|
200
|
-
```
|
179
|
+
Class2 :user => :name
|
201
180
|
|
202
|
-
|
181
|
+
class User
|
182
|
+
include SomeModule
|
203
183
|
|
204
|
-
|
184
|
+
def first_initial
|
185
|
+
name[0] if name
|
186
|
+
end
|
187
|
+
end
|
205
188
|
|
206
|
-
|
207
|
-
user = { :name => "sshaw", :hobbies => ["screen-staring", "other thangz"] }
|
208
|
-
Class2(:user => user)
|
189
|
+
User.new(:name => "sshaw").first_initial
|
209
190
|
```
|
210
191
|
|
211
|
-
|
192
|
+
`Class2` does accept a block whose contents will be added to
|
193
|
+
*every* class defined within the call:
|
212
194
|
|
213
195
|
```rb
|
214
|
-
user
|
215
|
-
|
196
|
+
Class2 :user => :name, :address => :city do
|
197
|
+
include ActiveModel::Conversion
|
198
|
+
extend ActiveModel::Naming
|
199
|
+
end
|
200
|
+
|
201
|
+
User.new.model_name.route_key
|
202
|
+
Address.new.model_name.route_key
|
216
203
|
```
|
217
204
|
|
218
|
-
|
205
|
+
## See Also
|
219
206
|
|
220
|
-
|
207
|
+
The Perl modules that served as inspiration:
|
221
208
|
|
222
|
-
|
209
|
+
* [`MooseX::NestedAttributesConstructor`](https://github.com/sshaw/MooseX-NestedAttributesConstructor)
|
210
|
+
* [`Class::Tiny`](https://metacpan.org/pod/Class::Tiny)
|
211
|
+
* [`Moose`](https://metacpan.org/pod/Moose), [`Moo`](https://metacpan.org/pod/Moo), and [`Mouse`](https://metacpan.org/pod/Mouse)
|
212
|
+
* [`Type::Tiny`](https://metacpan.org/pod/Type::Tiny)
|
213
|
+
* [`MooseX::Types`](https://metacpan.org/pod/MooseX::Types)
|
214
|
+
* [`Rubyish`](https://metacpan.org/pod/Rubyish)
|
223
215
|
|
224
|
-
|
216
|
+
Surely others I cannot remember...
|
225
217
|
|
226
218
|
## Author
|
227
219
|
|
data/lib/class2.rb
CHANGED
@@ -6,8 +6,8 @@ require "active_support/core_ext/string"
|
|
6
6
|
|
7
7
|
require "class2/version"
|
8
8
|
|
9
|
-
def Class2(*args)
|
10
|
-
Class2.new(*args)
|
9
|
+
def Class2(*args, &block)
|
10
|
+
Class2.new(*args, &block)
|
11
11
|
end
|
12
12
|
|
13
13
|
class Class2
|
@@ -29,7 +29,7 @@ class Class2
|
|
29
29
|
CONVERSIONS.default = lambda { |v| v }
|
30
30
|
|
31
31
|
class << self
|
32
|
-
def new(*argz)
|
32
|
+
def new(*argz, &block)
|
33
33
|
specs = argz
|
34
34
|
namespace = Object
|
35
35
|
|
@@ -39,7 +39,7 @@ class Class2
|
|
39
39
|
|
40
40
|
specs.each do |spec|
|
41
41
|
spec = [spec] unless spec.respond_to?(:each)
|
42
|
-
spec.each { |klass, attributes| make_class(namespace, klass, attributes) }
|
42
|
+
spec.each { |klass, attributes| make_class(namespace, klass, attributes, block) }
|
43
43
|
end
|
44
44
|
|
45
45
|
nil
|
@@ -60,7 +60,7 @@ class Class2
|
|
60
60
|
|
61
61
|
attributes = [attributes] unless attributes.is_a?(Array)
|
62
62
|
attributes.compact.each do |attr|
|
63
|
-
# Just an attribute name, no type
|
63
|
+
# Just an attribute name, no type
|
64
64
|
if !attr.is_a?(Hash)
|
65
65
|
simple << { attr => nil }
|
66
66
|
next
|
@@ -86,10 +86,10 @@ class Class2
|
|
86
86
|
[ nested, simple ]
|
87
87
|
end
|
88
88
|
|
89
|
-
def make_class(namespace, name, attributes)
|
89
|
+
def make_class(namespace, name, attributes, block)
|
90
90
|
nested, simple = split_and_normalize_attributes(attributes)
|
91
91
|
nested.each do |object|
|
92
|
-
object.each { |klass, attrs| make_class(namespace, klass, attrs) }
|
92
|
+
object.each { |klass, attrs| make_class(namespace, klass, attrs, block) }
|
93
93
|
end
|
94
94
|
|
95
95
|
make_method_name = lambda { |x| x.to_s.gsub(/[^\w]+/, "_") } # good enough
|
@@ -129,7 +129,7 @@ class Class2
|
|
129
129
|
begin
|
130
130
|
e.respond_to?(:to_h) ? e.to_h : e
|
131
131
|
rescue *errors
|
132
|
-
|
132
|
+
e
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
@@ -149,9 +149,21 @@ class Class2
|
|
149
149
|
method, type = cfg.first
|
150
150
|
method = make_method_name[method]
|
151
151
|
|
152
|
-
|
152
|
+
# Use Enum somehow?
|
153
|
+
retval = if type == Array || type.is_a?(Array)
|
154
|
+
"[]"
|
155
|
+
elsif type == Hash || type.is_a?(Hash)
|
156
|
+
"{}"
|
157
|
+
else
|
158
|
+
"nil"
|
159
|
+
end
|
153
160
|
|
154
161
|
class_eval <<-CODE
|
162
|
+
def #{method}
|
163
|
+
@#{method} = #{retval} unless defined? @#{method}
|
164
|
+
@#{method}
|
165
|
+
end
|
166
|
+
|
155
167
|
def #{method}=(v)
|
156
168
|
@#{method} = #{CONVERSIONS[type]["v"]}
|
157
169
|
end
|
@@ -170,6 +182,9 @@ class Class2
|
|
170
182
|
CODE
|
171
183
|
end
|
172
184
|
|
185
|
+
# Do this last to allow for overriding the methods we define
|
186
|
+
class_eval(&block) unless block.nil?
|
187
|
+
|
173
188
|
private
|
174
189
|
|
175
190
|
def assign_attributes(attributes)
|
@@ -195,4 +210,25 @@ class Class2
|
|
195
210
|
namespace.const_set(name.to_s.classify, klass)
|
196
211
|
end
|
197
212
|
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# By default unknown arguments are ignored. <code>include<code>ing this will
|
216
|
+
# cause an ArgumentError to be raised if an attribute is unknown:
|
217
|
+
#
|
218
|
+
module StrictConstructor
|
219
|
+
def self.included(klass)
|
220
|
+
klass.class_eval do
|
221
|
+
def initialize(attributes = nil)
|
222
|
+
return unless attributes.is_a?(Hash)
|
223
|
+
assign_attributes(attributes)
|
224
|
+
|
225
|
+
accepted = to_h.keys
|
226
|
+
attributes.each do |name, _|
|
227
|
+
next if accepted.include?(name.respond_to?(:to_sym) ? name.to_sym : name)
|
228
|
+
raise ArgumentError, "unknown attribute: #{name}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
198
234
|
end
|
data/lib/class2/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: class2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skye Shaw
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|