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 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