class2 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|