action_schema 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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +53 -0
- data/LICENSE.txt +21 -0
- data/README.md +269 -0
- data/Rakefile +12 -0
- data/lib/action_schema/base.rb +151 -0
- data/lib/action_schema/configuration.rb +29 -0
- data/lib/action_schema/controller.rb +58 -0
- data/lib/action_schema/railtie.rb +9 -0
- data/lib/action_schema/version.rb +5 -0
- data/lib/action_schema.rb +14 -0
- data/sig/action_schema.rbs +4 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1900a8787c395b5cfa7fce53e3feb8d049f33a08a0498c766d98c389aa1a30ca
|
4
|
+
data.tar.gz: b51df6ac12c67618e34ad5deed5d4615a751b4d1cf999d0b38cdf7aa063d29ed
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9f5f8d04be3a8dfde7b46c06580446934e810b1f1486bfa5a73c3b586003d2fe5fbcad63507b4b33005f9bc4944e5570b141eb704fad20f5f61bfbe9603fec82
|
7
|
+
data.tar.gz: 4f9936bc68703ab3a0ad4dcd7e3a217cf022a4cdf2cf5859b47097e8e5b543be6c5220c558e9eb86583357f06335bd55482d78c545033c44210f21246d477f0c
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.1.0] - 2024-12-08
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
#### Core Features
|
15
|
+
|
16
|
+
- **Rendering:** Render data in controller actions with `schema_for`
|
17
|
+
- **Inline schemas:** Define schemas inline in controller actions
|
18
|
+
- **Controller schemas:** Define reusable schemas directly in controllers with `schema`
|
19
|
+
- **Class schemas:** Define global schemas in standalone schema classes inheriting from `ActionSchema::Base`
|
20
|
+
|
21
|
+
#### Field Definitions
|
22
|
+
|
23
|
+
- **Fields:** Define a field with `field` or `fields`
|
24
|
+
- **Conditional Fields:** Conditionally render fields using `if:` and `unless:`
|
25
|
+
- **Computed Fields:** Define dynamically calculated fields with `computed`
|
26
|
+
- **Omitting Fields:** Exclude fields with `omit`
|
27
|
+
|
28
|
+
#### Associations
|
29
|
+
|
30
|
+
- **Schema Associations:** Nest schemas using `association`. Supports inline, controller, and class schemas.
|
31
|
+
|
32
|
+
#### Context & Hooks
|
33
|
+
|
34
|
+
- **Contexts:** Pass additional context to schemas
|
35
|
+
- **Hooks:** Execute code before and after rendering with `before_render` and `after_render`
|
36
|
+
- **Transformation:** Transform data within hooks with `transform`
|
37
|
+
|
38
|
+
#### Configuration
|
39
|
+
|
40
|
+
- **Key Transformations:** Define global key transformations with `ActionSchema.configuration.transform_keys`
|
41
|
+
- **Base Class:** Define your own base class for schemas with `ActionSchema.configuration.base_class`
|
42
|
+
|
43
|
+
#### Rails Integration
|
44
|
+
|
45
|
+
- **Rails Integration:** Automatically include `ActionSchema::Controller` in Rails controllers via a Railtie.
|
46
|
+
|
47
|
+
#### Other
|
48
|
+
|
49
|
+
- 100% test coverage
|
50
|
+
- Comprehensive documentation
|
51
|
+
|
52
|
+
[Unreleased]: https://github.com/jtnegrotto/action_schema/compare/v0.1.0...HEAD
|
53
|
+
[0.1.0]: https://github.com/jtnegrotto/action_schema/releases/tag/v0.1.0
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Julien Negrotto
|
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,269 @@
|
|
1
|
+
# ActionSchema
|
2
|
+
|
3
|
+
ActionSchema provides a flexible, Rails-friendly approach to rendering ~and
|
4
|
+
parsing~ (coming soon) structured data in your controllers.
|
5
|
+
|
6
|
+
## Status: Experimental 🚧
|
7
|
+
|
8
|
+
**ActionSchema** is currently in early development. Its API is subject to
|
9
|
+
significant changes. If you do choose to use it, please:
|
10
|
+
|
11
|
+
* Lock the version in your Gemfile (e.g. `gem "action_schema", "= 0.1.0"`)
|
12
|
+
* Be prepared to update your code as the library evolves
|
13
|
+
* Refer to the [CHANGELOG](CHANGELOG.md) for updates
|
14
|
+
* [Report](https://github.com/jtnegrotto/action_schema/issues/new) any issues you encounter to help improve the library
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add the gem to your Gemfile:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
bundle add action_schema -v '= 0.1.0'
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Defining Schemas
|
27
|
+
|
28
|
+
#### Inline Schemas
|
29
|
+
|
30
|
+
The simplest way to define a schema is inline in your controller action:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class UsersController < ApplicationController
|
34
|
+
def index
|
35
|
+
users = User.all
|
36
|
+
render json: schema_for(users) do
|
37
|
+
fields :id, :email
|
38
|
+
computed(:full_name) { |user| "#{user.first_name} #{user.last_name}" }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
This approach is ideal for quick, one-off schemas. Think of it as a step up from `as_json` with added flexibility and readability.
|
45
|
+
|
46
|
+
#### Controller Schemas
|
47
|
+
|
48
|
+
For reusable schemas, define them in your controller. This keeps your code DRY and allows schemas to be shared across multiple actions:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class UsersController < ApplicationController
|
52
|
+
schema :index do
|
53
|
+
fields :id, :email
|
54
|
+
computed(:full_name) { |user| "#{user.first_name} #{user.last_name}" }
|
55
|
+
end
|
56
|
+
|
57
|
+
def index
|
58
|
+
users = User.all
|
59
|
+
render json: schema_for(users, :index)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
**Tip:** Controller schemas are inherited by subclasses. This makes it easy to define a common schemas in a base controller.
|
65
|
+
|
66
|
+
#### Class Schemas
|
67
|
+
|
68
|
+
For global schemas, use schema classes:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class UserSchema < ActionSchema::Base
|
72
|
+
fields :id, :email
|
73
|
+
computed(:full_name) { |user| "#{user.first_name} #{user.last_name}" }
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
You can use these in your controllers like so:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class UsersController < ApplicationController
|
81
|
+
def index
|
82
|
+
users = User.all
|
83
|
+
render json: schema_for(users, UserSchema)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
### Schema Definition DSL
|
89
|
+
|
90
|
+
#### Fields
|
91
|
+
|
92
|
+
Regular fields are defined using the `field` and `fields` methods:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
field :id
|
96
|
+
field :name
|
97
|
+
field :created_at
|
98
|
+
field :updated_at
|
99
|
+
```
|
100
|
+
|
101
|
+
or
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
fields :id, :name, :created_at, :updated_at
|
105
|
+
```
|
106
|
+
|
107
|
+
Fields can be conditionally rendered using the `if` and `unless` options:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
field :email, if: ->(user) { user.email.present? }
|
111
|
+
field :phone, unless: ->(user) { user.phone.nil? }
|
112
|
+
```
|
113
|
+
|
114
|
+
#### Omitting Fields
|
115
|
+
|
116
|
+
You can omit fields using the `omit` method:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
omit :created_at, :updated_at
|
120
|
+
```
|
121
|
+
|
122
|
+
This can be useful when your schema inherits fields from a superclass, but you
|
123
|
+
don't need all of them in a particular action.
|
124
|
+
|
125
|
+
#### Computed Fields
|
126
|
+
|
127
|
+
Computed fields are defined using the `computed` method:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
computed(:full_name) { |user| "#{user.first_name} #{user.last_name}" }
|
131
|
+
```
|
132
|
+
|
133
|
+
#### Associations
|
134
|
+
|
135
|
+
Association schemas are specified using the `association` method, and like
|
136
|
+
other schemas, these can be defined inline, at the controller level, or in a
|
137
|
+
schema class.
|
138
|
+
|
139
|
+
##### Inline
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
schema do
|
143
|
+
fields :id, :email
|
144
|
+
association :posts do
|
145
|
+
fields :id, :title
|
146
|
+
end
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
##### Named Schema
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
schema :index do
|
154
|
+
fields :id, :email
|
155
|
+
association :posts, :post
|
156
|
+
end
|
157
|
+
|
158
|
+
schema :post do
|
159
|
+
fields :id, :title
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
##### Class Schema
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
class UserSchema < ActionSchema::Base
|
167
|
+
fields :id, :email
|
168
|
+
association :posts, PostSchema
|
169
|
+
end
|
170
|
+
|
171
|
+
class PostSchema < ActionSchema::Base
|
172
|
+
fields :id, :title
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
#### Contexts
|
177
|
+
|
178
|
+
Computed fields can access contextual data:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
computed(:is_current_user) { |user, context| user == context[:current_user] }
|
182
|
+
```
|
183
|
+
|
184
|
+
In your controller, you can pass the context to `#schema_for`:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
render json: schema_for(users, context: { current_user: current_user })
|
188
|
+
```
|
189
|
+
|
190
|
+
Alternatively, you can define the context at the controller level:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
class UsersController < ApplicationController
|
194
|
+
schema_context({ current_user: :current_user })
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
198
|
+
Controller-level contexts are inherited, and are merged with contexts defined
|
199
|
+
in the subclass. Action-level contexts inherit from the controller-level
|
200
|
+
context, and are similarly merged. More specific contexts take precedence over
|
201
|
+
less specific ones.
|
202
|
+
|
203
|
+
#### Hooks
|
204
|
+
|
205
|
+
You can define hooks to run before or after rendering. The `before_render` hook
|
206
|
+
takes a block that receives the record or collection to be rendered. The
|
207
|
+
`after_render` hook takes a block that receives the rendered data. Both hooks
|
208
|
+
allow you to replace the data with a new value using the `transform` method:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
schema :default do
|
212
|
+
fields :id, :email
|
213
|
+
|
214
|
+
before_render do |user|
|
215
|
+
user.email.downcase!
|
216
|
+
end
|
217
|
+
|
218
|
+
after_render do |data|
|
219
|
+
transform({ user: data })
|
220
|
+
end
|
221
|
+
end
|
222
|
+
```
|
223
|
+
|
224
|
+
**Tip:** Keep in mind that hooks are run whether the rendered object is a
|
225
|
+
single record or a collection. Be sure to handle both cases as necessary.
|
226
|
+
|
227
|
+
## Rationale
|
228
|
+
|
229
|
+
The main reason that **ActionSchema** exists is that I find serialization in
|
230
|
+
Rails to be a bit of a pain. While Rails' built-in serialization methods
|
231
|
+
(`as_json`) are fine for simple cases, they quickly fall apart when you need to
|
232
|
+
handle anything more complex. On the other hand, many serialization libraries
|
233
|
+
feel like overkill, requiring too much boilerplate for tasks that should be
|
234
|
+
straightforward.
|
235
|
+
|
236
|
+
I believe that serialization is fundamentally the controller's responsibility.
|
237
|
+
After all, you can't effectively optimize your queries if you don't know what
|
238
|
+
data will be used. Integrating serialization into the controller, close to the
|
239
|
+
query, makes it easier to reason about and ensures a tighter integration
|
240
|
+
between your data and its representation.
|
241
|
+
|
242
|
+
That said, I understand that not everyone shares this view. While
|
243
|
+
**ActionSchema** provides a seamless API for defining schemas entirely within
|
244
|
+
controllers, it also supports defining reusable schema classes outside of
|
245
|
+
controllers for those who prefer a more decoupled approach. My goal is to
|
246
|
+
strike a balance: to make serialization simple when you need it to be, while
|
247
|
+
remaining flexible enough to adapt to a variety of use cases and scenarios.
|
248
|
+
|
249
|
+
## Development
|
250
|
+
|
251
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
252
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
253
|
+
prompt that will allow you to experiment.
|
254
|
+
|
255
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
256
|
+
release a new version, update the version number in `version.rb`, and then run
|
257
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
258
|
+
git commits and the created tag, and push the `.gem` file to
|
259
|
+
[rubygems.org](https://rubygems.org).
|
260
|
+
|
261
|
+
## Contributing
|
262
|
+
|
263
|
+
Bug reports and pull requests are welcome on GitHub at
|
264
|
+
https://github.com/jtnegrotto/action_schema.
|
265
|
+
|
266
|
+
## License
|
267
|
+
|
268
|
+
The gem is available as open source under the terms of the [MIT
|
269
|
+
License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
module ActionSchema
|
2
|
+
class FieldError < ActionSchema::Error
|
3
|
+
def initialize(field, record)
|
4
|
+
@field = field
|
5
|
+
@record = record
|
6
|
+
super("Field '#{field}' does not exist on record: #{record.inspect}")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Base
|
11
|
+
class << self
|
12
|
+
attr_accessor :schema, :before_render_hooks, :after_render_hooks
|
13
|
+
|
14
|
+
def call(*args, **kwargs, &block)
|
15
|
+
new(*args, **kwargs, &block).render
|
16
|
+
end
|
17
|
+
|
18
|
+
def before_render(&block)
|
19
|
+
self.before_render_hooks ||= []
|
20
|
+
before_render_hooks << block
|
21
|
+
end
|
22
|
+
|
23
|
+
def after_render(&block)
|
24
|
+
self.after_render_hooks ||= []
|
25
|
+
after_render_hooks << block
|
26
|
+
end
|
27
|
+
|
28
|
+
def inherited(subclass)
|
29
|
+
subclass.before_render_hooks = (before_render_hooks || []).dup
|
30
|
+
subclass.after_render_hooks = (after_render_hooks || []).dup
|
31
|
+
subclass.schema = schema.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
def field(name, value = nil, **options, &block)
|
35
|
+
schema[name] = { value: block || value || name, **options }
|
36
|
+
end
|
37
|
+
|
38
|
+
def association(name, schema_definition = nil, &block)
|
39
|
+
base_schema_class = ActionSchema.configuration.base_class
|
40
|
+
|
41
|
+
resolved_schema =
|
42
|
+
if schema_definition.is_a?(Symbol)
|
43
|
+
->(controller) { controller.resolve_schema(schema_definition) }
|
44
|
+
elsif schema_definition.is_a?(Class)
|
45
|
+
schema_definition
|
46
|
+
elsif block_given?
|
47
|
+
Class.new(base_schema_class, &block)
|
48
|
+
else
|
49
|
+
raise ArgumentError, "An association schema or block must be provided"
|
50
|
+
end
|
51
|
+
|
52
|
+
schema[name] = { association: resolved_schema }
|
53
|
+
end
|
54
|
+
|
55
|
+
def computed(name, &block)
|
56
|
+
schema[name] = { computed: true, value: block }
|
57
|
+
end
|
58
|
+
|
59
|
+
def fields(*names)
|
60
|
+
names.each { |name| field(name) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def omit(*names)
|
64
|
+
names.each { |name| schema.delete(name) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def schema
|
68
|
+
@schema ||= {}
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse(data)
|
72
|
+
raise NotImplementedError, "Parsing is not yet implemented"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :record_or_collection, :context, :controller
|
77
|
+
|
78
|
+
def initialize(record_or_collection, context: {}, controller: nil)
|
79
|
+
@record_or_collection = record_or_collection
|
80
|
+
@context = context
|
81
|
+
@controller = controller
|
82
|
+
end
|
83
|
+
|
84
|
+
def render
|
85
|
+
renderable = @record_or_collection
|
86
|
+
renderable = apply_hooks(:before_render, @record_or_collection)
|
87
|
+
|
88
|
+
output =
|
89
|
+
if renderable.respond_to?(:map)
|
90
|
+
renderable.map { |record| render_record(record) }
|
91
|
+
else
|
92
|
+
render_record(renderable)
|
93
|
+
end
|
94
|
+
|
95
|
+
apply_hooks(:after_render, output)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def render_record(record)
|
101
|
+
self.class.schema.each_with_object({}) do |(key, config), result|
|
102
|
+
if_condition = config[:if]
|
103
|
+
unless_condition = config[:unless]
|
104
|
+
|
105
|
+
next if if_condition && !instance_exec(record, &if_condition)
|
106
|
+
next if unless_condition && instance_exec(record, &unless_condition)
|
107
|
+
|
108
|
+
transformed_key = transform_key(key)
|
109
|
+
|
110
|
+
result[transformed_key] =
|
111
|
+
if config[:computed]
|
112
|
+
instance_exec(record, context, &config[:value])
|
113
|
+
elsif association = config[:association]
|
114
|
+
associated_record_or_collection = record.public_send(key)
|
115
|
+
if associated_record_or_collection.nil?
|
116
|
+
nil
|
117
|
+
else
|
118
|
+
resolved_schema = association.is_a?(Proc) ? association.call(controller) : association
|
119
|
+
child_context = context.merge(parent: record)
|
120
|
+
resolved_schema.new(record.public_send(key), context: child_context, controller: controller).render
|
121
|
+
end
|
122
|
+
else
|
123
|
+
if record.respond_to?(config[:value])
|
124
|
+
record.public_send(config[:value])
|
125
|
+
else
|
126
|
+
raise FieldError.new(config[:value], record)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def transform_key(key)
|
133
|
+
transform_keys = ActionSchema.configuration.transform_keys
|
134
|
+
transform_keys ? transform_keys.call(key) : key
|
135
|
+
end
|
136
|
+
|
137
|
+
def apply_hooks(type, data)
|
138
|
+
hooks = self.class.public_send("#{type}_hooks") || []
|
139
|
+
hooks.each do |hook|
|
140
|
+
@transformed = nil
|
141
|
+
instance_exec(data, &hook)
|
142
|
+
data = @transformed || data
|
143
|
+
end
|
144
|
+
data
|
145
|
+
end
|
146
|
+
|
147
|
+
def transform(data)
|
148
|
+
@transformed = data
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActionSchema
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :base_class, :transform_keys, :type_serializers
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@base_class = ActionSchema::Base
|
7
|
+
@transform_keys = nil
|
8
|
+
@type_serializers = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def base_class
|
12
|
+
if @base_class.is_a?(String)
|
13
|
+
@base_class.constantize
|
14
|
+
else
|
15
|
+
@base_class
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def configuration
|
22
|
+
@configuration ||= Configuration.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def configure
|
26
|
+
yield(configuration)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module ActionSchema
|
2
|
+
module Controller
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :action_schemas, default: {}
|
7
|
+
class_attribute :default_schema_context, default: {}
|
8
|
+
end
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def schema(name = :default, &block)
|
12
|
+
action_schemas[name] = Class.new(ActionSchema.configuration.base_class, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def schema_context(context)
|
16
|
+
self.default_schema_context = self.default_schema_context.merge(context)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def schema_context
|
21
|
+
resolve_schema_context(self.class.default_schema_context)
|
22
|
+
end
|
23
|
+
|
24
|
+
def schema_for(record_or_collection, schema_name = :default, context: {}, &block)
|
25
|
+
combined_schema_context = schema_context.merge(resolve_schema_context(context))
|
26
|
+
|
27
|
+
schema_definition =
|
28
|
+
if block_given?
|
29
|
+
Class.new(ActionSchema.configuration.base_class, &block)
|
30
|
+
elsif schema_name.is_a?(Class)
|
31
|
+
schema_name
|
32
|
+
else
|
33
|
+
self.class.action_schemas[schema_name]
|
34
|
+
end
|
35
|
+
|
36
|
+
raise ArgumentError, "Schema `#{schema_name}` not defined" unless schema_definition
|
37
|
+
|
38
|
+
schema_definition.new(record_or_collection, context: combined_schema_context, controller: self).render
|
39
|
+
end
|
40
|
+
|
41
|
+
def resolve_schema(schema_name)
|
42
|
+
self.class.action_schemas[schema_name] ||
|
43
|
+
raise(ArgumentError, "Schema `#{schema_name}` not defined")
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def resolve_schema_context(context)
|
49
|
+
context.transform_values do |value|
|
50
|
+
if value.is_a?(Proc)
|
51
|
+
instance_exec(&value)
|
52
|
+
else
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
|
6
|
+
module ActionSchema
|
7
|
+
class Error < StandardError; end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative "action_schema/version"
|
11
|
+
require_relative "action_schema/configuration"
|
12
|
+
require_relative "action_schema/base"
|
13
|
+
require_relative "action_schema/controller"
|
14
|
+
require_relative "action_schema/railtie" if defined?(Rails)
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_schema
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julien Negrotto
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-12-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.21'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.21'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-rails-omakase
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.22'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.22'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-rails
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: ActionSchema provides a flexible, Rails-friendly approach to rendering
|
126
|
+
and parsing structured data in your controllers.
|
127
|
+
email:
|
128
|
+
- jtnegrotto@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".rspec"
|
134
|
+
- ".rubocop.yml"
|
135
|
+
- CHANGELOG.md
|
136
|
+
- LICENSE.txt
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- lib/action_schema.rb
|
140
|
+
- lib/action_schema/base.rb
|
141
|
+
- lib/action_schema/configuration.rb
|
142
|
+
- lib/action_schema/controller.rb
|
143
|
+
- lib/action_schema/railtie.rb
|
144
|
+
- lib/action_schema/version.rb
|
145
|
+
- sig/action_schema.rbs
|
146
|
+
homepage: https://github.com/jtnegrotto/action_schema
|
147
|
+
licenses:
|
148
|
+
- MIT
|
149
|
+
metadata:
|
150
|
+
homepage_uri: https://github.com/jtnegrotto/action_schema
|
151
|
+
source_code_uri: https://github.com/jtnegrotto/action_schema
|
152
|
+
changelog_uri: https://github.com/jtnegrotto/action_schema/blob/master/CHANGELOG.md
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: 3.0.0
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubygems_version: 3.5.23
|
169
|
+
signing_key:
|
170
|
+
specification_version: 4
|
171
|
+
summary: A lightweight schema library for Rails controllers.
|
172
|
+
test_files: []
|