blockster 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +398 -0
- data/Rakefile +12 -0
- data/lib/blockster/configuration.rb +29 -0
- data/lib/blockster/context.rb +41 -0
- data/lib/blockster/version.rb +5 -0
- data/lib/blockster/wrapper.rb +136 -0
- data/lib/blockster.rb +11 -0
- data/sig/blockster.rbs +4 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f573ec1a6938fea0284ebeec0c97680e3a365a68166527d2ee9681bec9de1542
|
4
|
+
data.tar.gz: 89d2f15d91f0dd0754170462c79aca096c9c53eb4f9e0ad9417937373b96fa29
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 674b8b8e9679c1984b2dfac1cb3bec01d9280dbe213f9ded443929a5125919762e92d9ef9052530c12ba4ec55c6c710078edeeb54a46b33edc3765454cf0020d
|
7
|
+
data.tar.gz: 83125b455d636b8c8f96404c5aa700bc7ef94eaf0c90a870c967bdbc11248e0aa2738886c2e5e4205338ea25718ae9576197678773ef47cc0024597c6bd11b19
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,398 @@
|
|
1
|
+
# Blockster
|
2
|
+
|
3
|
+
Blockster is a flexible Ruby gem that provides a clean DSL for defining and initializing objects with nested attributes. It's particularly useful when working with params from complex form objects, API wrappers, or any scenario where you need to dynamically define object attributes.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'blockster'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ gem install blockster
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Basic Usage
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class UserForm
|
31
|
+
include ActiveModel::Model
|
32
|
+
include ActiveModel::Attributes
|
33
|
+
end
|
34
|
+
|
35
|
+
wrapper = Blockster::Wrapper.new(UserForm)
|
36
|
+
user = wrapper.with(username: 'js_bach', email: 'js@bach.music') do
|
37
|
+
attribute :username, :string
|
38
|
+
attribute :email, :string
|
39
|
+
end
|
40
|
+
|
41
|
+
user.username # => 'js_bach'
|
42
|
+
user.email # => 'js@bach.music'
|
43
|
+
```
|
44
|
+
|
45
|
+
### Root Node and Nested Attributes
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
params = {
|
49
|
+
'user' => {
|
50
|
+
'username' => 'js_bach',
|
51
|
+
'email' => {
|
52
|
+
'address' => 'js@bach.music',
|
53
|
+
'notifications' => true
|
54
|
+
},
|
55
|
+
'preferences' => {
|
56
|
+
'theme' => 'dark',
|
57
|
+
'language' => 'en'
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
user = wrapper.with(params) do
|
63
|
+
root :user do
|
64
|
+
attribute :username, :string
|
65
|
+
|
66
|
+
nested :email do
|
67
|
+
attribute :address, :string
|
68
|
+
attribute :notifications, :boolean
|
69
|
+
end
|
70
|
+
|
71
|
+
nested :preferences do
|
72
|
+
attribute :theme, :string
|
73
|
+
attribute :language, :string
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
user.username # => 'js_bach'
|
79
|
+
user.email.address # => 'js@bach.music'
|
80
|
+
user.preferences.theme # => 'dark'
|
81
|
+
```
|
82
|
+
|
83
|
+
## Hash-like Behavior & ActiveRecord Integration
|
84
|
+
|
85
|
+
Blockster objects behave like hashes and integrate seamlessly with ActiveRecord. This makes them perfect for form objects and API wrappers.
|
86
|
+
|
87
|
+
### Hash Conversion
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
params = {
|
91
|
+
'user' => {
|
92
|
+
'username' => 'js_bach',
|
93
|
+
'email' => {
|
94
|
+
'address' => 'js@bach.music',
|
95
|
+
'notifications' => 'true'
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
form = Blockster::Wrapper.new(UserForm).with(params) do
|
101
|
+
root :user do
|
102
|
+
attribute :username, :string
|
103
|
+
nested :email do
|
104
|
+
attribute :address, :string
|
105
|
+
attribute :notifications, :boolean
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Convert to hash
|
111
|
+
form.to_h
|
112
|
+
# => {
|
113
|
+
# username: "js_bach",
|
114
|
+
# email: {
|
115
|
+
# address: "js@bach.music",
|
116
|
+
# notifications: true
|
117
|
+
# }
|
118
|
+
# }
|
119
|
+
|
120
|
+
# Use hash-like methods
|
121
|
+
form.keys # => [:username, :email]
|
122
|
+
form.empty? # => false
|
123
|
+
form.each_pair do |key, value|
|
124
|
+
puts "#{key}: #{value}"
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
### ActiveRecord Integration
|
129
|
+
|
130
|
+
Blockster objects can be used directly with ActiveRecord methods thanks to proper hash conversion:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# Create records
|
134
|
+
user = User.create(form)
|
135
|
+
|
136
|
+
# Update records
|
137
|
+
user.update(form)
|
138
|
+
|
139
|
+
# Mass assignment
|
140
|
+
user.assign_attributes(form)
|
141
|
+
|
142
|
+
# Form objects
|
143
|
+
class UserRegistrationForm
|
144
|
+
include ActiveModel::Model
|
145
|
+
include ActiveModel::Attributes
|
146
|
+
end
|
147
|
+
|
148
|
+
class UsersController < ApplicationController
|
149
|
+
def create
|
150
|
+
# Works seamlessly with ActiveRecord
|
151
|
+
@user = User.new(create_params)
|
152
|
+
|
153
|
+
if @user.save
|
154
|
+
redirect_to @user
|
155
|
+
else
|
156
|
+
render :new
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def create_params
|
163
|
+
Blockster::Wrapper.new(UserRegistrationForm).with(user_params) do
|
164
|
+
root :user do
|
165
|
+
attribute :username, :string
|
166
|
+
attribute :email, :string
|
167
|
+
|
168
|
+
nested :profile do
|
169
|
+
attribute :first_name, :string
|
170
|
+
attribute :last_name, :string
|
171
|
+
end
|
172
|
+
|
173
|
+
nested :preferences do
|
174
|
+
attribute :theme, :string
|
175
|
+
attribute :notifications, :boolean
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
### Nested Forms with ActiveRecord Relations
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class OrderForm
|
187
|
+
include ActiveModel::Model
|
188
|
+
include ActiveModel::Attributes
|
189
|
+
end
|
190
|
+
|
191
|
+
form = Blockster::Wrapper.new(OrderForm).with(params) do
|
192
|
+
root :order do
|
193
|
+
attribute :number, :string
|
194
|
+
attribute :total, :decimal
|
195
|
+
|
196
|
+
nested :line_items do
|
197
|
+
attribute :product_id, :integer
|
198
|
+
attribute :quantity, :integer
|
199
|
+
attribute :price, :decimal
|
200
|
+
end
|
201
|
+
|
202
|
+
nested :shipping_address do
|
203
|
+
attribute :street, :string
|
204
|
+
attribute :city, :string
|
205
|
+
attribute :postal_code, :string
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Create order with nested attributes
|
211
|
+
order = Order.create(form)
|
212
|
+
|
213
|
+
# Update existing order
|
214
|
+
order.update(form)
|
215
|
+
|
216
|
+
# Use with accepts_nested_attributes_for
|
217
|
+
class Order < ApplicationRecord
|
218
|
+
has_many :line_items
|
219
|
+
has_one :shipping_address
|
220
|
+
accepts_nested_attributes_for :line_items, :shipping_address
|
221
|
+
end
|
222
|
+
|
223
|
+
# Form's hash structure matches nested attributes requirements
|
224
|
+
order.assign_attributes(form)
|
225
|
+
```
|
226
|
+
|
227
|
+
### Working with APIs
|
228
|
+
|
229
|
+
The hash conversion makes it easy to work with APIs:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
class ApiWrapper
|
233
|
+
include ActiveModel::Model
|
234
|
+
include ActiveModel::Attributes
|
235
|
+
end
|
236
|
+
|
237
|
+
response = Blockster::Wrapper.new(ApiWrapper).with(api_response) do
|
238
|
+
attribute :status, :string
|
239
|
+
|
240
|
+
nested :data do
|
241
|
+
attribute :id, :integer
|
242
|
+
attribute :type, :string
|
243
|
+
|
244
|
+
nested :attributes do
|
245
|
+
attribute :title, :string
|
246
|
+
attribute :description, :text
|
247
|
+
attribute :published_at, :datetime
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Renders as properly formatted JSON
|
253
|
+
render json: response
|
254
|
+
|
255
|
+
# Or use directly in ActiveRecord
|
256
|
+
record = Record.create(response.data.attributes)
|
257
|
+
```
|
258
|
+
|
259
|
+
### JSON Serialization
|
260
|
+
|
261
|
+
Blockster objects work seamlessly with Rails' JSON rendering:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
class Api::UsersController < ApplicationController
|
265
|
+
def show
|
266
|
+
user_data = UserService.fetch(params[:id])
|
267
|
+
|
268
|
+
response = Blockster::Wrapper.new(ApiResponse).with(user_data) do
|
269
|
+
attribute :id, :integer
|
270
|
+
attribute :username, :string
|
271
|
+
|
272
|
+
nested :profile do
|
273
|
+
attribute :first_name, :string
|
274
|
+
attribute :last_name, :string
|
275
|
+
attribute :avatar_url, :string
|
276
|
+
end
|
277
|
+
|
278
|
+
nested :stats do
|
279
|
+
attribute :posts_count, :integer
|
280
|
+
attribute :followers_count, :integer
|
281
|
+
attribute :following_count, :integer
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Works directly with render :json
|
286
|
+
render json: response
|
287
|
+
end
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
You can also explicitly convert to JSON:
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
# Convert to hash first
|
295
|
+
response.as_json
|
296
|
+
# => { id: 1, username: "js_bach", profile: { first_name: "Johann", ... } }
|
297
|
+
|
298
|
+
# Convert directly to JSON string
|
299
|
+
response.to_json
|
300
|
+
# => '{"id":1,"username":"js_bach","profile":{"first_name":"Johann",...}}'
|
301
|
+
|
302
|
+
# Works with complex nested structures
|
303
|
+
form = Blockster::Wrapper.new(ComplexForm).with(data) do
|
304
|
+
attribute :name, :string
|
305
|
+
nested :settings do
|
306
|
+
attribute :theme, :string
|
307
|
+
attribute :notifications, :array
|
308
|
+
end
|
309
|
+
nested :permissions do
|
310
|
+
attribute :roles, :array
|
311
|
+
nested :features do
|
312
|
+
attribute :enabled, :array
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Renders as properly formatted JSON
|
318
|
+
render json: form
|
319
|
+
```
|
320
|
+
|
321
|
+
The JSON serialization maintains all nested structures and array values, making it perfect for API responses and client-side consumption.
|
322
|
+
|
323
|
+
|
324
|
+
### Debugging
|
325
|
+
|
326
|
+
Blockster objects have a readable inspect output that shows their current state:
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
form = Blockster::Wrapper.new(UserForm).with(params) do
|
330
|
+
root :user do
|
331
|
+
attribute :username, :string
|
332
|
+
nested :email do
|
333
|
+
attribute :address, :string
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
puts form.inspect
|
339
|
+
# => {:username=>"js_bach", :email=>{:address=>"js@bach.music"}}
|
340
|
+
```
|
341
|
+
|
342
|
+
This makes debugging in Rails console and logging much more convenient.
|
343
|
+
|
344
|
+
### Configuration
|
345
|
+
|
346
|
+
You can configure a default class to be used when initializing wrappers, if you are using rails, this would be a great way to configure, since the attributes api is part of rails:
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
# config/initializers/blockster.rb (in Rails)
|
350
|
+
Blockster.configure do |config|
|
351
|
+
config.default_class = Class.new do
|
352
|
+
include ActiveModel::Model
|
353
|
+
include ActiveModel::Attributes
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Now you can initialize wrappers without providing a class
|
358
|
+
wrapper = Blockster::Wrapper.new
|
359
|
+
```
|
360
|
+
|
361
|
+
Or use an existing class:
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
class DefaultFormObject
|
365
|
+
include ActiveModel::Model
|
366
|
+
include ActiveModel::Attributes
|
367
|
+
|
368
|
+
# Your default setup here
|
369
|
+
end
|
370
|
+
|
371
|
+
Blockster.configure do |config|
|
372
|
+
config.default_class = DefaultFormObject
|
373
|
+
end
|
374
|
+
```
|
375
|
+
|
376
|
+
When initializing a wrapper, you can still override the default class:
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
# Uses default class
|
380
|
+
wrapper = Blockster::Wrapper.new
|
381
|
+
|
382
|
+
# Overrides default class
|
383
|
+
wrapper = Blockster::Wrapper.new(CustomClass)
|
384
|
+
```
|
385
|
+
|
386
|
+
Note: Either a class must be provided to the wrapper or a default class must be configured.
|
387
|
+
|
388
|
+
## Development
|
389
|
+
|
390
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
391
|
+
|
392
|
+
## Contributing
|
393
|
+
|
394
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/yourusername/blockster.
|
395
|
+
|
396
|
+
## License
|
397
|
+
|
398
|
+
The gem is available as open source under the terms of the MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Blockster
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :default_class
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@default_class = nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure
|
18
|
+
yield(configuration)
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_class
|
22
|
+
configuration.default_class
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset_configuration!
|
26
|
+
@configuration = Configuration.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Blockster
|
4
|
+
class Context
|
5
|
+
def initialize(temp_class, wrapper)
|
6
|
+
@temp_class = temp_class
|
7
|
+
@wrapper = wrapper
|
8
|
+
@temp_class.class_eval do
|
9
|
+
class << self
|
10
|
+
attr_accessor :nested_attributes
|
11
|
+
end
|
12
|
+
@nested_attributes = {}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def root(key, &block)
|
17
|
+
@wrapper.instance_variable_set("@root_key", key)
|
18
|
+
instance_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def attribute(name, type, **_options)
|
22
|
+
@temp_class.attribute(name, type)
|
23
|
+
end
|
24
|
+
|
25
|
+
def nested(name, &block)
|
26
|
+
@temp_class.nested_attributes[name] = block
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(method_name, *args, &block)
|
30
|
+
if @temp_class.respond_to?(method_name, true)
|
31
|
+
@temp_class.send(method_name, *args, &block)
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def respond_to_missing?(method_name, include_private = false)
|
38
|
+
@temp_class.respond_to?(method_name, include_private) || super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Blockster
|
4
|
+
class Wrapper
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :to_h, :each_pair, :each, :empty?, :keys
|
7
|
+
|
8
|
+
def initialize(klass = nil)
|
9
|
+
@klass = klass || Blockster.default_class
|
10
|
+
raise ArgumentError, "No class provided and no default_class configured" unless @klass
|
11
|
+
@root_key = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def with(attributes = {}, &block)
|
15
|
+
raise ArgumentError, "Attributes must be a hash" unless attributes.is_a?(Hash)
|
16
|
+
|
17
|
+
temp_class = Class.new(@klass)
|
18
|
+
context = Context.new(temp_class, self)
|
19
|
+
context.instance_eval(&block) if block_given?
|
20
|
+
|
21
|
+
attributes = attributes[@root_key.to_s] if @root_key && attributes.key?(@root_key.to_s)
|
22
|
+
|
23
|
+
@instance = temp_class.new
|
24
|
+
set_nested_attributes(symbolize_keys(attributes))
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_h
|
29
|
+
return {} unless @instance
|
30
|
+
|
31
|
+
collected_attributes = {}
|
32
|
+
|
33
|
+
# Get defined attributes
|
34
|
+
if @instance.class.respond_to?(:attribute_types)
|
35
|
+
@instance.class.attribute_types.keys.each do |attr_name|
|
36
|
+
value = @instance.public_send(attr_name)
|
37
|
+
collected_attributes[attr_name.to_sym] = convert_value_to_hash(value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get nested attributes
|
42
|
+
if @instance.class.respond_to?(:nested_attributes)
|
43
|
+
@instance.class.nested_attributes.each_key do |attr_name|
|
44
|
+
nested_value = instance_variable_get("@#{attr_name}")
|
45
|
+
collected_attributes[attr_name.to_sym] = nested_value&.to_h || {}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
collected_attributes
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
to_h.inspect
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_hash
|
57
|
+
to_h
|
58
|
+
end
|
59
|
+
|
60
|
+
def as_json(options = nil)
|
61
|
+
to_h
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_json(options = nil)
|
65
|
+
as_json(options).to_json
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def convert_value_to_hash(value)
|
71
|
+
case value
|
72
|
+
when Wrapper
|
73
|
+
value.to_h
|
74
|
+
when Array
|
75
|
+
value.map { |v| convert_value_to_hash(v) }
|
76
|
+
else
|
77
|
+
value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_nested_attributes(attributes)
|
82
|
+
return unless attributes.is_a?(Hash)
|
83
|
+
|
84
|
+
@instance.class.nested_attributes&.each do |name, config|
|
85
|
+
# Define accessor method for nested attribute on the wrapper
|
86
|
+
define_singleton_method(name) do
|
87
|
+
instance_variable_get("@#{name}")
|
88
|
+
end
|
89
|
+
|
90
|
+
if attributes.key?(name)
|
91
|
+
klass = Class.new(@klass)
|
92
|
+
|
93
|
+
nested_wrapper = self.class.new(klass)
|
94
|
+
nested_wrapper.with(attributes[name], &config)
|
95
|
+
|
96
|
+
instance_variable_set("@#{name}", nested_wrapper)
|
97
|
+
else
|
98
|
+
# Initialize empty wrapper even if no attributes provided
|
99
|
+
klass = Class.new(@klass)
|
100
|
+
nested_wrapper = self.class.new(klass)
|
101
|
+
nested_wrapper.with({}, &config)
|
102
|
+
instance_variable_set("@#{name}", nested_wrapper)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Set regular attributes
|
107
|
+
attributes.each do |key, value|
|
108
|
+
next if @instance.class.nested_attributes&.key?(key)
|
109
|
+
setter = "#{key}="
|
110
|
+
@instance.send(setter, value) if @instance.respond_to?(setter)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def symbolize_keys(hash)
|
115
|
+
return {} unless hash.is_a?(Hash)
|
116
|
+
hash.transform_keys { |key| key.to_sym rescue key }
|
117
|
+
end
|
118
|
+
|
119
|
+
def method_missing(method_name, *args, &block)
|
120
|
+
if @instance&.respond_to?(method_name)
|
121
|
+
result = @instance.send(method_name, *args, &block)
|
122
|
+
if result == @instance
|
123
|
+
self
|
124
|
+
else
|
125
|
+
result
|
126
|
+
end
|
127
|
+
else
|
128
|
+
super
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def respond_to_missing?(method_name, include_private = false)
|
133
|
+
@instance&.respond_to?(method_name, include_private) || super
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/blockster.rb
ADDED
data/sig/blockster.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blockster
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Toby
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '13.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '13.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.12'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: zeitwerk
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activemodel
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '7.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '7.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activerecord
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.50'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.50'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- toby@darkroom.tech
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".rspec"
|
119
|
+
- ".rubocop.yml"
|
120
|
+
- CHANGELOG.md
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- lib/blockster.rb
|
125
|
+
- lib/blockster/configuration.rb
|
126
|
+
- lib/blockster/context.rb
|
127
|
+
- lib/blockster/version.rb
|
128
|
+
- lib/blockster/wrapper.rb
|
129
|
+
- sig/blockster.rbs
|
130
|
+
homepage: https://github.com/tobyond/blockster
|
131
|
+
licenses:
|
132
|
+
- MIT
|
133
|
+
metadata:
|
134
|
+
homepage_uri: https://github.com/tobyond/blockster
|
135
|
+
source_code_uri: https://github.com/tobyond/blockster
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 2.6.0
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubygems_version: 3.5.3
|
152
|
+
signing_key:
|
153
|
+
specification_version: 4
|
154
|
+
summary: Wrap classes in a block for easy access
|
155
|
+
test_files: []
|