magicka 1.1.0 → 1.2.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/.circleci/config.yml +6 -9
- data/.github/copilot-instructions.md +229 -0
- data/.github/magicka-usage.md +394 -0
- data/.github/sinclair-usage.md +492 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +12 -2
- data/.rubocop_todo.yml +12 -0
- data/Dockerfile +2 -2
- data/Gemfile +33 -0
- data/Makefile +21 -0
- data/README.md +6 -7
- data/Rakefile +3 -0
- data/lib/magicka/aggregator/class_methods.rb +3 -1
- data/lib/magicka/aggregator.rb +3 -1
- data/lib/magicka/element/class_methods.rb +3 -3
- data/lib/magicka/form_element.rb +1 -1
- data/lib/magicka/version.rb +1 -1
- data/magicka.gemspec +3 -31
- data/magicka.jpg +0 -0
- data/spec/dummy/app/models/document.rb +1 -1
- data/spec/dummy/bin/setup +0 -4
- data/spec/dummy/bin/update +0 -4
- data/spec/dummy/config/environments/development.rb +1 -1
- data/spec/dummy/config/environments/production.rb +2 -2
- data/spec/dummy/config/puma.rb +3 -3
- data/spec/integration/yard/magicka/helper_spec.rb +1 -1
- data/spec/lib/magicka/aggregator/class_methods_spec.rb +1 -1
- data/spec/lib/magicka/aggregator_spec.rb +1 -1
- data/spec/lib/magicka/button_spec.rb +1 -1
- data/spec/lib/magicka/display_spec.rb +1 -1
- data/spec/lib/magicka/element/class_methods_spec.rb +1 -1
- data/spec/lib/magicka/element_spec.rb +1 -1
- data/spec/lib/magicka/form_element_spec.rb +1 -1
- data/spec/lib/magicka/form_spec.rb +1 -1
- data/spec/lib/magicka/input_spec.rb +1 -1
- data/spec/lib/magicka/select_spec.rb +1 -1
- data/spec/lib/magicka/text_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -1
- metadata +12 -372
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# Sinclair – Usage Guide for Dependent Projects
|
|
2
|
+
|
|
3
|
+
This document describes how to use the **sinclair** gem in your project.
|
|
4
|
+
Copy this file into your project's `.github/` directory so that GitHub Copilot
|
|
5
|
+
is aware of the patterns and conventions Sinclair provides.
|
|
6
|
+
|
|
7
|
+
**Current release**: 3.1.0
|
|
8
|
+
**Docs**: <https://www.rubydoc.info/gems/sinclair/3.1.0>
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Add to your `Gemfile`:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem 'sinclair'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
then run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bundle install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Features Overview
|
|
29
|
+
|
|
30
|
+
| Feature | Class / Module | Purpose |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Method builder | `Sinclair` | Add instance/class methods dynamically |
|
|
33
|
+
| Configuration | `Sinclair::Configurable` + `Sinclair::Config` | Read-only config with defaults |
|
|
34
|
+
| Options | `Sinclair::Options` | Validated parameter objects |
|
|
35
|
+
| Env variables | `Sinclair::EnvSettable` | Read ENV vars via class methods |
|
|
36
|
+
| Equality | `Sinclair::Comparable` | Attribute-based `==` |
|
|
37
|
+
| Plain models | `Sinclair::Model` | Quick data-model classes |
|
|
38
|
+
| Type casting | `Sinclair::Caster` | Extensible type transformations |
|
|
39
|
+
| RSpec matchers | `Sinclair::Matchers` | Test method-builder behaviour |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 1. Sinclair – Dynamic Method Builder
|
|
44
|
+
|
|
45
|
+
`Sinclair` lets you add instance and class methods to any class at runtime.
|
|
46
|
+
Methods are queued with `add_method` / `add_class_method` and created only
|
|
47
|
+
when `build` is called.
|
|
48
|
+
|
|
49
|
+
### Stand-alone usage
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
class Clazz; end
|
|
53
|
+
|
|
54
|
+
builder = Sinclair.new(Clazz)
|
|
55
|
+
builder.add_method(:twenty, '10 + 10') # string-based
|
|
56
|
+
builder.add_method(:eighty) { 4 * twenty } # block-based
|
|
57
|
+
builder.add_class_method(:one_hundred) { 100 }
|
|
58
|
+
builder.build
|
|
59
|
+
|
|
60
|
+
instance = Clazz.new
|
|
61
|
+
instance.twenty # => 20
|
|
62
|
+
instance.eighty # => 80
|
|
63
|
+
Clazz.one_hundred # => 100
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Block DSL (`Sinclair.build`)
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
Sinclair.build(MyClass) do
|
|
70
|
+
add_method(:random_number) { Random.rand(10..20) }
|
|
71
|
+
add_class_method(:static_value) { 42 }
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### String method with parameters
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
Sinclair.build(MyClass) do
|
|
79
|
+
add_class_method(
|
|
80
|
+
:power, 'a ** b + c',
|
|
81
|
+
parameters: [:a],
|
|
82
|
+
named_parameters: [:b, { c: 15 }]
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
MyClass.power(10, b: 2) # => 115
|
|
87
|
+
MyClass.power(10, b: 2, c: 0) # => 100
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Call-based method (delegates to the class itself)
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
builder = Sinclair.new(MyClass)
|
|
94
|
+
builder.add_class_method(:attr_accessor, :number, type: :call)
|
|
95
|
+
builder.build
|
|
96
|
+
|
|
97
|
+
MyClass.number # => nil
|
|
98
|
+
MyClass.number = 10
|
|
99
|
+
MyClass.number # => 10
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Caching results
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
builder.add_method(:expensive, cached: true) { slow_computation }
|
|
106
|
+
# equivalent to: @expensive ||= slow_computation
|
|
107
|
+
|
|
108
|
+
builder.add_method(:nullable, cached: :full) { may_return_nil }
|
|
109
|
+
# caches even nil / false values
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Extending the builder
|
|
113
|
+
|
|
114
|
+
Subclass `Sinclair` to create domain-specific builders:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
class ValidationBuilder < Sinclair
|
|
118
|
+
delegate :expected, to: :options_object
|
|
119
|
+
|
|
120
|
+
def add_validation(field)
|
|
121
|
+
add_method("#{field}_valid?", "#{field}.is_a?(#{expected})")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
module Validatable
|
|
126
|
+
extend ActiveSupport::Concern
|
|
127
|
+
|
|
128
|
+
class_methods do
|
|
129
|
+
def validate(*fields, expected_class)
|
|
130
|
+
builder = ValidationBuilder.new(self, expected: expected_class)
|
|
131
|
+
fields.each { |f| builder.add_validation(f) }
|
|
132
|
+
builder.build
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class MyModel
|
|
138
|
+
include Validatable
|
|
139
|
+
validate :name, String
|
|
140
|
+
validate :age, Integer
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 2. Sinclair::Configurable – Application Configuration
|
|
147
|
+
|
|
148
|
+
`Sinclair::Configurable` adds a read-only `config` object to any class or
|
|
149
|
+
module. Settings can only be changed through `configure`.
|
|
150
|
+
|
|
151
|
+
### Inline attributes
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
module MyApp
|
|
155
|
+
extend Sinclair::Configurable
|
|
156
|
+
|
|
157
|
+
configurable_with :host, port: 80, debug: false
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
MyApp.configure(port: 5555) do |config|
|
|
161
|
+
config.host 'example.com'
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
MyApp.config.host # => 'example.com'
|
|
165
|
+
MyApp.config.port # => 5555
|
|
166
|
+
|
|
167
|
+
MyApp.reset_config
|
|
168
|
+
MyApp.config.host # => nil
|
|
169
|
+
MyApp.config.port # => 80
|
|
170
|
+
|
|
171
|
+
# Convert to Options object (useful for passing around)
|
|
172
|
+
MyApp.as_options(host: 'other').host # => 'other'
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Custom config class
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
class ServerConfig < Sinclair::Config
|
|
179
|
+
config_attributes :host, :port
|
|
180
|
+
|
|
181
|
+
def url
|
|
182
|
+
@port ? "http://#{@host}:#{@port}" : "http://#{@host}"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
class Client
|
|
187
|
+
extend Sinclair::Configurable
|
|
188
|
+
configurable_by ServerConfig
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
Client.configure { host 'api.example.com' }
|
|
192
|
+
Client.config.url # => 'http://api.example.com'
|
|
193
|
+
|
|
194
|
+
Client.configure { port 8080 }
|
|
195
|
+
Client.config.url # => 'http://api.example.com:8080'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 3. Sinclair::Options – Validated Option Objects
|
|
201
|
+
|
|
202
|
+
`Sinclair::Options` creates structured option/parameter value objects with
|
|
203
|
+
defaults and validation against unknown keys.
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
class ConnectionOptions < Sinclair::Options
|
|
207
|
+
with_options :timeout, :retries, port: 443, protocol: 'https'
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
opts = ConnectionOptions.new(timeout: 30, protocol: 'http')
|
|
211
|
+
opts.timeout # => 30
|
|
212
|
+
opts.retries # => nil
|
|
213
|
+
opts.port # => 443 (default)
|
|
214
|
+
opts.protocol # => 'http'
|
|
215
|
+
opts.to_h # => { timeout: 30, retries: nil, port: 443, protocol: 'http' }
|
|
216
|
+
|
|
217
|
+
ConnectionOptions.new(unknown_key: 1)
|
|
218
|
+
# raises Sinclair::Exception::InvalidOptions
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Call `skip_validation` in the class body to allow unknown keys:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
class LooseOptions < Sinclair::Options
|
|
225
|
+
with_options :name
|
|
226
|
+
skip_validation
|
|
227
|
+
end
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 4. Sinclair::EnvSettable – Environment Variable Access
|
|
233
|
+
|
|
234
|
+
`EnvSettable` exposes environment variables as class-level methods, with
|
|
235
|
+
optional prefix and default values.
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
class ServiceClient
|
|
239
|
+
extend Sinclair::EnvSettable
|
|
240
|
+
|
|
241
|
+
settings_prefix 'SERVICE'
|
|
242
|
+
with_settings :username, :password, port: 80, hostname: 'my-host.com'
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
ENV['SERVICE_USERNAME'] = 'my-login'
|
|
246
|
+
ENV['SERVICE_HOSTNAME'] = 'host.com'
|
|
247
|
+
|
|
248
|
+
ServiceClient.username # => 'my-login'
|
|
249
|
+
ServiceClient.hostname # => 'host.com'
|
|
250
|
+
ServiceClient.port # => 80 (default – ENV var not set)
|
|
251
|
+
ServiceClient.password # => nil (ENV var not set, no default)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Type casting
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
class AppConfig
|
|
258
|
+
extend Sinclair::EnvSettable
|
|
259
|
+
|
|
260
|
+
settings_prefix 'APP'
|
|
261
|
+
setting_with_options :timeout, type: :integer, default: 30
|
|
262
|
+
setting_with_options :debug, type: :boolean
|
|
263
|
+
setting_with_options :rate, type: :float
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
ENV['APP_TIMEOUT'] = '60'
|
|
267
|
+
AppConfig.timeout # => 60 (Integer)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 5. Sinclair::Comparable – Attribute-based Equality
|
|
273
|
+
|
|
274
|
+
Include `Sinclair::Comparable` and declare which attributes are used for `==`.
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
class Person
|
|
278
|
+
include Sinclair::Comparable
|
|
279
|
+
|
|
280
|
+
comparable_by :name
|
|
281
|
+
attr_reader :name, :age
|
|
282
|
+
|
|
283
|
+
def initialize(name:, age:)
|
|
284
|
+
@name = name
|
|
285
|
+
@age = age
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
p1 = Person.new(name: 'Alice', age: 30)
|
|
290
|
+
p2 = Person.new(name: 'Alice', age: 25)
|
|
291
|
+
|
|
292
|
+
p1 == p2 # => true (only :name is compared)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## 6. Sinclair::Model – Quick Plain-Ruby Models
|
|
298
|
+
|
|
299
|
+
`Sinclair::Model` generates reader/writer methods, a keyword-argument
|
|
300
|
+
initializer, and equality (via `Sinclair::Comparable`) in one call.
|
|
301
|
+
|
|
302
|
+
There are two ways to define a model:
|
|
303
|
+
|
|
304
|
+
- **`initialize_with`** – called inside the class body; adds methods to the current class.
|
|
305
|
+
- **`.for`** – class method that returns a new anonymous subclass; useful when inheriting inline.
|
|
306
|
+
|
|
307
|
+
### Basic model (initialize_with)
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
class Human < Sinclair::Model
|
|
311
|
+
initialize_with :name, :age, { gender: :undefined }, **{}
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
h1 = Human.new(name: 'John', age: 22)
|
|
315
|
+
h2 = Human.new(name: 'John', age: 22)
|
|
316
|
+
|
|
317
|
+
h1.name # => 'John'
|
|
318
|
+
h1.gender # => :undefined
|
|
319
|
+
h1 == h2 # => true
|
|
320
|
+
|
|
321
|
+
h1.name = 'Jane' # setter generated by default
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Disabling writers or equality (initialize_with)
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
class Tv < Sinclair::Model
|
|
328
|
+
initialize_with :brand, :model, writter: false, comparable: false
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
tv1 = Tv.new(brand: 'Sony', model: 'X90L')
|
|
332
|
+
tv2 = Tv.new(brand: 'Sony', model: 'X90L')
|
|
333
|
+
|
|
334
|
+
tv1 == tv2 # => false (comparable disabled)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Using .for (inline subclassing)
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
class Car < Sinclair::Model.for(:brand, :model, writter: false)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
car = Car.new(brand: :ford, model: :T)
|
|
344
|
+
|
|
345
|
+
car.brand # => :ford
|
|
346
|
+
car.model # => :T
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Using .for with default values
|
|
350
|
+
|
|
351
|
+
```ruby
|
|
352
|
+
class Job < Sinclair::Model.for({ state: :starting }, writter: true)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
job = Job.new
|
|
356
|
+
|
|
357
|
+
job.state # => :starting
|
|
358
|
+
job.state = :done
|
|
359
|
+
job.state # => :done
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Options accepted by both `initialize_with` and `.for`:
|
|
363
|
+
|
|
364
|
+
| Option | Default | Description |
|
|
365
|
+
|---|---|---|
|
|
366
|
+
| `writter:` | `true` | Generate setter methods |
|
|
367
|
+
| `comparable:` | `true` | Include field in `==` comparison |
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 7. Sinclair::Caster – Type Casting
|
|
372
|
+
|
|
373
|
+
`Sinclair::Caster` provides a registry of named type casters.
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
class MyCaster < Sinclair::Caster
|
|
377
|
+
cast_with(:upcase, :upcase)
|
|
378
|
+
cast_with(:log) { |value, base: 10| Math.log(value.to_f, base) }
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
MyCaster.cast('hello', :upcase) # => 'HELLO'
|
|
382
|
+
MyCaster.cast(100, :log) # => 2.0
|
|
383
|
+
MyCaster.cast(16, :log, base: 2) # => 4.0
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Class-based casting
|
|
387
|
+
|
|
388
|
+
```ruby
|
|
389
|
+
class TypeCaster < Sinclair::Caster
|
|
390
|
+
master_caster!
|
|
391
|
+
|
|
392
|
+
cast_with(Integer, :to_i)
|
|
393
|
+
cast_with(Float, :to_f)
|
|
394
|
+
cast_with(String, :to_s)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
TypeCaster.cast('42', Integer) # => 42
|
|
398
|
+
TypeCaster.cast(3, Float) # => 3.0
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## 8. Sinclair::Matchers – RSpec Matchers
|
|
404
|
+
|
|
405
|
+
Include `Sinclair::Matchers` in your RSpec configuration to gain matchers for
|
|
406
|
+
testing that a builder adds or changes methods.
|
|
407
|
+
|
|
408
|
+
### Setup
|
|
409
|
+
|
|
410
|
+
```ruby
|
|
411
|
+
# spec/spec_helper.rb
|
|
412
|
+
RSpec.configure do |config|
|
|
413
|
+
config.include Sinclair::Matchers
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Available matchers
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
420
|
+
# Checks that build adds an instance method
|
|
421
|
+
expect { builder.build }.to add_method(:name).to(instance)
|
|
422
|
+
expect { builder.build }.to add_method(:name).to(klass)
|
|
423
|
+
|
|
424
|
+
# Checks that build adds a class method
|
|
425
|
+
expect { builder.build }.to add_class_method(:count).to(klass)
|
|
426
|
+
|
|
427
|
+
# Checks that build changes an existing instance method
|
|
428
|
+
expect { builder.build }.to change_method(:value).on(instance)
|
|
429
|
+
|
|
430
|
+
# Checks that build changes an existing class method
|
|
431
|
+
expect { builder.build }.to change_class_method(:count).on(klass)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Example spec
|
|
435
|
+
|
|
436
|
+
```ruby
|
|
437
|
+
RSpec.describe MyBuilder do
|
|
438
|
+
let(:klass) { Class.new }
|
|
439
|
+
let(:instance) { klass.new }
|
|
440
|
+
let(:builder) { MyBuilder.new(klass) }
|
|
441
|
+
|
|
442
|
+
it 'adds a greeting method to instances' do
|
|
443
|
+
expect { builder.build }.to add_method(:greet).to(instance)
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
it 'adds a factory class method' do
|
|
447
|
+
expect { builder.build }.to add_class_method(:create).to(klass)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Complete Example
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
# Combining multiple Sinclair features in one class
|
|
458
|
+
|
|
459
|
+
class ApiClient
|
|
460
|
+
extend Sinclair::Configurable
|
|
461
|
+
extend Sinclair::EnvSettable
|
|
462
|
+
include Sinclair::Comparable
|
|
463
|
+
|
|
464
|
+
# --- Configuration (set programmatically) ---
|
|
465
|
+
configurable_with :timeout, retries: 3
|
|
466
|
+
|
|
467
|
+
# --- Environment variables ---
|
|
468
|
+
settings_prefix 'API'
|
|
469
|
+
with_settings :api_key, :secret, base_url: 'https://api.example.com'
|
|
470
|
+
|
|
471
|
+
# --- Equality based on base_url ---
|
|
472
|
+
comparable_by :base_url
|
|
473
|
+
|
|
474
|
+
attr_reader :base_url
|
|
475
|
+
|
|
476
|
+
def initialize(base_url: self.class.base_url)
|
|
477
|
+
@base_url = base_url
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# Wire up at boot time
|
|
482
|
+
ENV['API_API_KEY'] = 'secret-key'
|
|
483
|
+
ApiClient.configure(timeout: 60)
|
|
484
|
+
|
|
485
|
+
client1 = ApiClient.new
|
|
486
|
+
client2 = ApiClient.new
|
|
487
|
+
|
|
488
|
+
client1 == client2 # => true
|
|
489
|
+
ApiClient.config.timeout # => 60
|
|
490
|
+
ApiClient.config.retries # => 3
|
|
491
|
+
ApiClient.api_key # => 'secret-key'
|
|
492
|
+
```
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-rspec
|
|
3
|
+
- rubocop-factory_bot
|
|
4
|
+
- rubocop-rails
|
|
5
|
+
- rubocop-rake
|
|
6
|
+
- rubocop-rspec_rails
|
|
7
|
+
|
|
2
8
|
inherit_from: .rubocop_todo.yml
|
|
3
9
|
|
|
4
10
|
AllCops:
|
|
5
|
-
TargetRubyVersion:
|
|
11
|
+
TargetRubyVersion: 3.3
|
|
12
|
+
NewCops: enable
|
|
6
13
|
|
|
7
14
|
Layout/LineLength:
|
|
8
15
|
Max: 90
|
|
@@ -51,3 +58,6 @@ Style/HashTransformValues:
|
|
|
51
58
|
RSpec/DescribeClass:
|
|
52
59
|
Exclude:
|
|
53
60
|
- 'spec/integration/**/*_spec.rb'
|
|
61
|
+
|
|
62
|
+
Gemspec/RequireMFA:
|
|
63
|
+
Enabled: false
|
data/.rubocop_todo.yml
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config`
|
|
3
|
+
# on 2026-03-10 17:40:58 UTC using RuboCop version 1.85.1.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 65
|
|
10
|
+
# Configuration parameters: AllowSubject.
|
|
11
|
+
RSpec/MultipleMemoizedHelpers:
|
|
12
|
+
Max: 10
|
data/Dockerfile
CHANGED
data/Gemfile
CHANGED
|
@@ -3,3 +3,36 @@
|
|
|
3
3
|
source 'https://rubygems.org'
|
|
4
4
|
|
|
5
5
|
gemspec
|
|
6
|
+
|
|
7
|
+
gem 'activerecord', '7.2.2.1'
|
|
8
|
+
gem 'bundler', '>= 2.5.13'
|
|
9
|
+
gem 'factory_bot', '6.5.6'
|
|
10
|
+
gem 'nokogiri', '1.19.1'
|
|
11
|
+
gem 'pry', '0.14.2'
|
|
12
|
+
gem 'pry-nav', '1.0.0'
|
|
13
|
+
gem 'rails', '7.2.2.1'
|
|
14
|
+
gem 'rails-controller-testing', '1.0.5'
|
|
15
|
+
gem 'rake', '13.2.1'
|
|
16
|
+
gem 'reek', '6.5.0'
|
|
17
|
+
gem 'rspec', '3.13.2'
|
|
18
|
+
gem 'rspec-core', '3.13.6'
|
|
19
|
+
gem 'rspec-expectations', '3.13.5'
|
|
20
|
+
gem 'rspec-mocks', '3.13.8'
|
|
21
|
+
gem 'rspec-rails', '8.0.3'
|
|
22
|
+
gem 'rspec-support', '3.13.7'
|
|
23
|
+
gem 'rubocop', '1.85.1'
|
|
24
|
+
gem 'rubocop-factory_bot', '2.28.0'
|
|
25
|
+
gem 'rubocop-rails', '2.34.3'
|
|
26
|
+
gem 'rubocop-rake', '0.7.1'
|
|
27
|
+
gem 'rubocop-rspec', '3.9.0'
|
|
28
|
+
gem 'rubocop-rspec_rails', '2.32.0'
|
|
29
|
+
gem 'rubycritic', '5.0.0'
|
|
30
|
+
gem 'shoulda-matchers', '6.5.0'
|
|
31
|
+
gem 'simplecov', '0.22.0'
|
|
32
|
+
gem 'simplecov-html', '0.13.2'
|
|
33
|
+
gem 'simplecov-lcov', '0.9.0'
|
|
34
|
+
gem 'sprockets-rails', '3.5.2'
|
|
35
|
+
gem 'sqlite3', '1.4.2'
|
|
36
|
+
gem 'tzinfo-data', '1.2026.1'
|
|
37
|
+
gem 'yard', '0.9.38'
|
|
38
|
+
gem 'yardstick', '0.9.9'
|
data/Makefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.PHONY: build dev tests
|
|
2
|
+
|
|
3
|
+
PROJECT?=magicka
|
|
4
|
+
IMAGE?=$(PROJECT)
|
|
5
|
+
BASE_IMAGE?=$(DOCKER_ID_USER)/$(PROJECT)-base
|
|
6
|
+
DOCKER_FILE_BASE=Dockerfile.$(PROJECT)-base
|
|
7
|
+
|
|
8
|
+
all:
|
|
9
|
+
@echo "Usage:"
|
|
10
|
+
@echo " make build\n Build docker image for $(PROJECT)"
|
|
11
|
+
@echo " make dev\n Run development environment for $(PROJECT)"
|
|
12
|
+
@echo " make tests\n Run tests for $(PROJECT)"
|
|
13
|
+
|
|
14
|
+
build:
|
|
15
|
+
docker build -f Dockerfile.$(PROJECT) . -t $(IMAGE) -t $(PUSH_IMAGE):latest
|
|
16
|
+
|
|
17
|
+
tests:
|
|
18
|
+
docker-compose run $(PROJECT)_tests /bin/bash
|
|
19
|
+
|
|
20
|
+
dev:
|
|
21
|
+
docker-compose run $(PROJECT) /bin/bash
|
data/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
Magicka
|
|
2
2
|
====
|
|
3
|
-
[](https://dl.circleci.com/status-badge/redirect/gh/darthjee/magicka/tree/main)
|
|
4
|
+
[](https://app.codacy.com/gh/darthjee/magicka/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
5
|
+
[](https://app.codacy.com/gh/darthjee/magicka/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
|
|
6
6
|
[](https://badge.fury.io/rb/magicka)
|
|
7
|
-
[](https://www.codacy.com/manual/darthjee/magicka?utm_source=github.com&utm_medium=referral&utm_content=darthjee/magicka&utm_campaign=Badge_Grade)
|
|
8
7
|
[](http://inch-ci.org/github/darthjee/magicka)
|
|
9
8
|
|
|
10
9
|

|
|
@@ -12,13 +11,13 @@ Magicka
|
|
|
12
11
|
Magica helps creating html templates for forms and display data using js applications
|
|
13
12
|
such as AngulaJS
|
|
14
13
|
|
|
15
|
-
Current Release
|
|
14
|
+
**Current Release**: [1.2.0](https://github.com/darthjee/magicka/tree/1.2.0)
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
**Next release**: [1.3.0](https://github.com/darthjee/magicka/compare/1.2.0...master)
|
|
18
17
|
|
|
19
18
|
Yard Documentation
|
|
20
19
|
-------------------
|
|
21
|
-
[https://www.rubydoc.info/gems/magicka/1.
|
|
20
|
+
[https://www.rubydoc.info/gems/magicka/1.2.0](https://www.rubydoc.info/gems/magicka/1.2.0)
|
|
22
21
|
|
|
23
22
|
Installation
|
|
24
23
|
---------------
|
data/Rakefile
CHANGED
data/lib/magicka/aggregator.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Magicka
|
|
|
11
11
|
extend Aggregator::ClassMethods
|
|
12
12
|
|
|
13
13
|
attr_reader :model
|
|
14
|
+
|
|
14
15
|
# @method model
|
|
15
16
|
# @api public
|
|
16
17
|
#
|
|
@@ -95,7 +96,7 @@ module Magicka
|
|
|
95
96
|
#
|
|
96
97
|
# @return [TrueClass,FalseClass]
|
|
97
98
|
def equal?(other)
|
|
98
|
-
return unless other.class == self.class
|
|
99
|
+
return false unless other.class == self.class
|
|
99
100
|
|
|
100
101
|
other.renderer == renderer &&
|
|
101
102
|
other.model == model
|
|
@@ -106,6 +107,7 @@ module Magicka
|
|
|
106
107
|
protected
|
|
107
108
|
|
|
108
109
|
attr_reader :renderer
|
|
110
|
+
|
|
109
111
|
# @method renderer
|
|
110
112
|
# @private
|
|
111
113
|
# @api private
|