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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 91ca10f950914ea029d15364b4722c064dc5d669
4
- data.tar.gz: c1b657103a971487aa414c95c7f789959c1bd5bf
3
+ metadata.gz: ab6287d91741715040bde7550bf9a9a0de474d4b
4
+ data.tar.gz: 46e7ab44843d52de375a7a259f976e6c653e9d4a
5
5
  SHA512:
6
- metadata.gz: fe9ece44a7faecc4b52dd0159c4fd0271ab38c65928d4ccdbdd2960ffb0abe8366951152fe2bc83b5fc3001b97758dcd3c9ae25f1fb19d91f665e459b97da5e7
7
- data.tar.gz: 0294c237a509932691f6e4fd8f976b1d3d49aea607e27053e15681b94eb69a7f58e2ac7601bf7283a195e6405721d466a43834ffea5bf57800024b590655ac90
6
+ metadata.gz: 19d653a1b5f20940ecf956f3952ebeabfcd8761e249b9fb136d34d30baf975f3a4db4ef124e982863f3157e6538b7ee2e5853e06b8113b3535a0183c311d4059
7
+ data.tar.gz: ad13a618934fc32fbe6ac72d8c0ee6645358d5d35df5b732217bb2c632abfba6a07eb957f43b2ee86ce549da890fcf99c82e17d7eb3f7ae9edd4dba183e7c73d
data/Changes CHANGED
@@ -1,3 +1,9 @@
1
+ 2017-08-15 v0.1.0
2
+ --------------------
3
+ * Add support for blocks
4
+ * Add Class2::StrictConstructor
5
+ * Bug fix: to_h conversion failure caused element to be skipped
6
+
1
7
  2017-08-10 v0.0.2
2
8
  --------------------
3
9
  * Add support for types
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
- :user => [
12
- :name, :age,
13
- :addresses => [
14
- :city, :state, :zip,
15
- :country => [ :name, :code ]
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
- :user => {
34
- :name => String,
35
- :age => Fixnum,
36
- :addresses => [
37
- :city, :state, :zip, # No explicit types for these
38
- :country => {
39
- :name => String,
40
- :code => String
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
- Attributes without types are treated as is.
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 -or a slightly modified version, see [Issues](#issues)- as the specification:
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
- #### Custom Methods
158
+ ### Constructor
172
159
 
173
- Just open up the class and write them:
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(:user => :name)
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
- ## Issues
169
+ Now an `ArgumentError` will be raised if anything but `id`, `name`, or
170
+ `age` are passed in.
188
171
 
189
- Can't use plural attributes that are not collections.
190
- Here we want `:users` to be an attribute, not a type:
172
+ Also see [Customizations](#customizations).
191
173
 
192
- ```rb
193
- Class2(:foo => [ :users => [ :id, :age, :foo ] ])
194
- ```
174
+ ### Customizations
195
175
 
196
- Instead you can use a `Set`
176
+ To add methods or include modules just open up the class and write or include them:
197
177
 
198
178
  ```rb
199
- Class2(:foo => [ :users => Set.new([ :id, :age, :foo ]) ])
200
- ```
179
+ Class2 :user => :name
201
180
 
202
- ---
181
+ class User
182
+ include SomeModule
203
183
 
204
- Building classes from example hashes will fail for arrays of scalars. For example:
184
+ def first_initial
185
+ name[0] if name
186
+ end
187
+ end
205
188
 
206
- ```rb
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
- Will fail because of `:hobbies`' value. It must be converted to:
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 = { :name => "sshaw", :hobbies => [] }
215
- Class2(:user => user)
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
- Others, I'm sure.
207
+ The Perl modules that served as inspiration:
221
208
 
222
- ## See Also
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
- The Perl module that served as the inspiration: [`MooseX::NestedAttributesConstructor`](https://github.com/sshaw/MooseX-NestedAttributesConstructor).
216
+ Surely others I cannot remember...
225
217
 
226
218
  ## Author
227
219
 
@@ -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, so use default type String
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
- # Give up
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
- attr_reader method
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
@@ -1,3 +1,3 @@
1
1
  class Class2
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
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.2
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-10 00:00:00.000000000 Z
11
+ date: 2017-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport