jpie 0.3.1 → 0.4.1

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.
@@ -0,0 +1,147 @@
1
+ # Resource Attribute Configuration Example
2
+
3
+ This example demonstrates all the different ways to configure resource attributes in JPie, showcasing the various configuration patterns and customization techniques available for attributes.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Model (`app/models/user.rb`)
8
+ ```ruby
9
+ class User < ActiveRecord::Base
10
+ validates :first_name, presence: true
11
+ validates :last_name, presence: true
12
+ validates :email, presence: true, uniqueness: true
13
+ validates :username, presence: true, uniqueness: true
14
+
15
+ def active?
16
+ true # Simple boolean for demonstration
17
+ end
18
+ end
19
+ ```
20
+
21
+ ### 2. Resource with All Attribute Configuration Types (`app/resources/user_resource.rb`)
22
+ ```ruby
23
+ class UserResource < JPie::Resource
24
+ # 1. Basic attributes - direct model attribute access
25
+ attributes :email, :first_name, :last_name
26
+
27
+ # 2. Attribute with custom attr mapping (maps to different model attribute)
28
+ attribute :display_name, attr: :username
29
+
30
+ # 3. Attribute with block override
31
+ attribute :status do
32
+ object.active? ? 'active' : 'inactive'
33
+ end
34
+
35
+ # 4. Attribute with custom method override
36
+ attribute :full_name
37
+
38
+ private
39
+
40
+ # Custom method for attribute override
41
+ def full_name
42
+ "#{object.first_name} #{object.last_name}".strip
43
+ end
44
+ end
45
+ ```
46
+
47
+ ### 3. Controller (`app/controllers/users_controller.rb`)
48
+ ```ruby
49
+ class UsersController < ApplicationController
50
+ include JPie::Controller
51
+ end
52
+ ```
53
+
54
+ ## HTTP Examples
55
+
56
+ ### Create User
57
+ ```http
58
+ POST /users
59
+ Content-Type: application/vnd.api+json
60
+
61
+ {
62
+ "data": {
63
+ "type": "users",
64
+ "attributes": {
65
+ "email": "john.doe@example.com",
66
+ "first_name": "John",
67
+ "last_name": "Doe"
68
+ }
69
+ }
70
+ }
71
+
72
+ HTTP/1.1 201 Created
73
+ Content-Type: application/vnd.api+json
74
+
75
+ {
76
+ "data": {
77
+ "id": "1",
78
+ "type": "users",
79
+ "attributes": {
80
+ "email": "john.doe@example.com",
81
+ "first_name": "John",
82
+ "last_name": "Doe",
83
+ "display_name": "johndoe",
84
+ "status": "active",
85
+ "full_name": "John Doe"
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### Update User
92
+ ```http
93
+ PATCH /users/1
94
+ Content-Type: application/vnd.api+json
95
+
96
+ {
97
+ "data": {
98
+ "id": "1",
99
+ "type": "users",
100
+ "attributes": {
101
+ "first_name": "Jonathan"
102
+ }
103
+ }
104
+ }
105
+
106
+ HTTP/1.1 200 OK
107
+ Content-Type: application/vnd.api+json
108
+
109
+ {
110
+ "data": {
111
+ "id": "1",
112
+ "type": "users",
113
+ "attributes": {
114
+ "email": "john.doe@example.com",
115
+ "first_name": "Jonathan",
116
+ "last_name": "Doe",
117
+ "display_name": "johndoe",
118
+ "status": "active",
119
+ "full_name": "Jonathan Doe"
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Get User with All Attribute Configuration Types
126
+ ```http
127
+ GET /users/1
128
+ Accept: application/vnd.api+json
129
+
130
+ HTTP/1.1 200 OK
131
+ Content-Type: application/vnd.api+json
132
+
133
+ {
134
+ "data": {
135
+ "id": "1",
136
+ "type": "users",
137
+ "attributes": {
138
+ "email": "john.doe@example.com",
139
+ "first_name": "John",
140
+ "last_name": "Doe",
141
+ "display_name": "johndoe",
142
+ "status": "active",
143
+ "full_name": "John Doe"
144
+ }
145
+ }
146
+ }
147
+ ```
@@ -0,0 +1,244 @@
1
+ # Meta Field Configuration Example
2
+
3
+ This example demonstrates all the different ways to define and configure meta fields in JPie, focusing on the various configuration patterns and syntax options available.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Article model
8
+ ```ruby
9
+ class Article < ActiveRecord::Base
10
+ validates :title, presence: true
11
+ validates :content, presence: true
12
+ validates :status, inclusion: { in: %w[draft published archived] }
13
+
14
+ belongs_to :author, class_name: 'User'
15
+
16
+ def word_count
17
+ content.split.length
18
+ end
19
+
20
+ def reading_time_minutes
21
+ (word_count / 200.0).ceil
22
+ end
23
+ end
24
+ ```
25
+
26
+ ### 2. Resource with All Meta Field Configuration Types
27
+ ```ruby
28
+ class ArticleResource < JPie::Resource
29
+ # 1. Basic meta attributes - direct model access and custom methods
30
+ meta_attributes :created_at, :updated_at
31
+ meta_attribute :reading_time
32
+
33
+ # 2. Meta attribute with attr mapping (maps to different model attribute)
34
+ meta_attribute :author_name, attr: :author_email
35
+
36
+ # 3. Meta attribute with block (legacy style)
37
+ meta_attribute :word_count do
38
+ object.word_count
39
+ end
40
+
41
+ # 4. Meta attribute with proc block (alternative legacy style)
42
+ meta_attribute :character_count, block: proc { object.content.length }
43
+
44
+ # 5. Short alias syntax (modern style)
45
+ meta :api_version
46
+ metas :request_id, :cache_key
47
+
48
+ # 6. Custom meta method for dynamic metadata
49
+ def meta
50
+ # Start with declared meta attributes
51
+ base_meta = super
52
+
53
+ # Add dynamic metadata
54
+ dynamic_meta = {
55
+ timestamp: Time.current.iso8601,
56
+ resource_version: '2.1'
57
+ }
58
+
59
+ # Conditional metadata based on context
60
+ if context[:include_debug]
61
+ dynamic_meta[:debug_info] = {
62
+ object_class: object.class.name,
63
+ context_keys: context.keys
64
+ }
65
+ end
66
+
67
+ # Merge and return
68
+ base_meta.merge(dynamic_meta)
69
+ end
70
+
71
+ private
72
+
73
+ # Custom method for reading_time meta attribute
74
+ def reading_time
75
+ {
76
+ minutes: object.reading_time_minutes,
77
+ formatted: "#{object.reading_time_minutes} min read"
78
+ }
79
+ end
80
+
81
+ # Meta attribute accessing context
82
+ def user_role
83
+ context[:current_user]&.role || 'anonymous'
84
+ end
85
+
86
+ # Meta attribute with conditional logic
87
+ def edit_permissions
88
+ current_user = context[:current_user]
89
+ return false unless current_user
90
+
91
+ current_user.admin? || current_user == object.author
92
+ end
93
+
94
+ # Meta attributes using short alias syntax
95
+ def api_version
96
+ '1.0'
97
+ end
98
+
99
+ def request_id
100
+ context[:request_id] || SecureRandom.uuid
101
+ end
102
+
103
+ def cache_key
104
+ "article:#{object.id}:#{object.updated_at.to_i}"
105
+ end
106
+ end
107
+ ```
108
+
109
+ ## HTTP Examples
110
+
111
+ ### Create Article
112
+ ```http
113
+ POST /articles
114
+ Content-Type: application/vnd.api+json
115
+ Authorization: Bearer user_token
116
+
117
+ {
118
+ "data": {
119
+ "type": "articles",
120
+ "attributes": {
121
+ "title": "Meta Field Configuration Guide",
122
+ "content": "This guide demonstrates all the different ways to configure meta fields in JPie...",
123
+ "status": "draft"
124
+ }
125
+ }
126
+ }
127
+
128
+ HTTP/1.1 201 Created
129
+ Content-Type: application/vnd.api+json
130
+
131
+ {
132
+ "data": {
133
+ "id": "1",
134
+ "type": "articles",
135
+ "attributes": {
136
+ "title": "Meta Field Configuration Guide",
137
+ "content": "This guide demonstrates all the different ways to configure meta fields in JPie...",
138
+ "status": "draft"
139
+ },
140
+ "meta": {
141
+ "created_at": "2024-01-15T10:30:00Z",
142
+ "updated_at": "2024-01-15T10:30:00Z",
143
+ "reading_time": {
144
+ "minutes": 3,
145
+ "formatted": "3 min read"
146
+ },
147
+ "author_name": "john@example.com",
148
+ "word_count": 450,
149
+ "character_count": 2700,
150
+ "api_version": "1.0",
151
+ "request_id": "req_abc123def456",
152
+ "cache_key": "article:1:1705492800",
153
+ "timestamp": "2024-03-15T16:45:30Z",
154
+ "resource_version": "2.1"
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ### Update Article
161
+ ```http
162
+ PATCH /articles/1
163
+ Content-Type: application/vnd.api+json
164
+ Authorization: Bearer user_token
165
+
166
+ {
167
+ "data": {
168
+ "id": "1",
169
+ "type": "articles",
170
+ "attributes": {
171
+ "status": "published"
172
+ }
173
+ }
174
+ }
175
+
176
+ HTTP/1.1 200 OK
177
+ Content-Type: application/vnd.api+json
178
+
179
+ {
180
+ "data": {
181
+ "id": "1",
182
+ "type": "articles",
183
+ "attributes": {
184
+ "title": "Meta Field Configuration Guide",
185
+ "content": "This guide demonstrates all the different ways to configure meta fields in JPie...",
186
+ "status": "published"
187
+ },
188
+ "meta": {
189
+ "created_at": "2024-01-15T10:30:00Z",
190
+ "updated_at": "2024-01-16T14:20:00Z",
191
+ "reading_time": {
192
+ "minutes": 3,
193
+ "formatted": "3 min read"
194
+ },
195
+ "author_name": "john@example.com",
196
+ "word_count": 450,
197
+ "character_count": 2700,
198
+ "api_version": "1.0",
199
+ "request_id": "req_xyz789ghi012",
200
+ "cache_key": "article:1:1705492800",
201
+ "timestamp": "2024-03-16T14:20:00Z",
202
+ "resource_version": "2.1"
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ ### Get Article with All Meta Field Configurations
209
+ ```http
210
+ GET /articles/1
211
+ Accept: application/vnd.api+json
212
+ Authorization: Bearer user_token
213
+
214
+ HTTP/1.1 200 OK
215
+ Content-Type: application/vnd.api+json
216
+
217
+ {
218
+ "data": {
219
+ "id": "1",
220
+ "type": "articles",
221
+ "attributes": {
222
+ "title": "Meta Field Configuration Guide",
223
+ "content": "This guide demonstrates all the different ways to configure meta fields in JPie...",
224
+ "status": "published"
225
+ },
226
+ "meta": {
227
+ "created_at": "2024-01-15T10:30:00Z",
228
+ "updated_at": "2024-01-16T14:20:00Z",
229
+ "reading_time": {
230
+ "minutes": 3,
231
+ "formatted": "3 min read"
232
+ },
233
+ "author_name": "john@example.com",
234
+ "word_count": 450,
235
+ "character_count": 2700,
236
+ "api_version": "1.0",
237
+ "request_id": "req_abc123def456",
238
+ "cache_key": "article:1:1705492800",
239
+ "timestamp": "2024-03-16T14:20:00Z",
240
+ "resource_version": "2.1"
241
+ }
242
+ }
243
+ }
244
+ ```
@@ -0,0 +1,160 @@
1
+ # Single Table Inheritance (STI) Example
2
+
3
+ This example demonstrates the minimal setup required to implement Single Table Inheritance with JPie resources and controllers.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Base Model (`app/models/vehicle.rb`)
8
+ ```ruby
9
+ class Vehicle < ApplicationRecord
10
+ validates :name, presence: true
11
+ validates :brand, presence: true
12
+ validates :year, presence: true
13
+ end
14
+ ```
15
+
16
+ ### 2. STI Models (`app/models/car.rb`, `app/models/truck.rb`)
17
+ ```ruby
18
+ class Car < Vehicle
19
+ validates :engine_size, presence: true
20
+ end
21
+
22
+ class Truck < Vehicle
23
+ validates :cargo_capacity, presence: true
24
+ end
25
+ ```
26
+
27
+ ### 3. Base Resource (`app/resources/vehicle_resource.rb`)
28
+ ```ruby
29
+ class VehicleResource < JPie::Resource
30
+ attributes :name, :brand, :year
31
+ end
32
+ ```
33
+
34
+ ### 4. STI Resources (`app/resources/car_resource.rb`, `app/resources/truck_resource.rb`)
35
+ ```ruby
36
+ class CarResource < VehicleResource
37
+ attributes :engine_size
38
+ end
39
+
40
+ class TruckResource < VehicleResource
41
+ attributes :cargo_capacity
42
+ end
43
+ ```
44
+
45
+ ### 5. Controller (`app/controllers/vehicles_controller.rb`)
46
+ ```ruby
47
+ class VehiclesController < ApplicationController
48
+ include JPie::Controller
49
+ end
50
+ ```
51
+
52
+ ### 6. Routes (`config/routes.rb`)
53
+ ```ruby
54
+ Rails.application.routes.draw do
55
+ resources :vehicles
56
+ end
57
+ ```
58
+
59
+ ## HTTP Examples
60
+
61
+ ### Create Car
62
+ ```http
63
+ POST /vehicles
64
+ Content-Type: application/vnd.api+json
65
+
66
+ {
67
+ "data": {
68
+ "type": "cars",
69
+ "attributes": {
70
+ "name": "Civic",
71
+ "brand": "Honda",
72
+ "year": 2024,
73
+ "engine_size": 1500
74
+ }
75
+ }
76
+ }
77
+
78
+ HTTP/1.1 201 Created
79
+ Content-Type: application/vnd.api+json
80
+
81
+ {
82
+ "data": {
83
+ "id": "1",
84
+ "type": "cars",
85
+ "attributes": {
86
+ "name": "Civic",
87
+ "brand": "Honda",
88
+ "year": 2024,
89
+ "engine_size": 1500
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ### Update Car
96
+ ```http
97
+ PATCH /vehicles/1
98
+ Content-Type: application/vnd.api+json
99
+
100
+ {
101
+ "data": {
102
+ "id": "1",
103
+ "type": "cars",
104
+ "attributes": {
105
+ "name": "Civic Hybrid",
106
+ "engine_size": 1800
107
+ }
108
+ }
109
+ }
110
+
111
+ HTTP/1.1 200 OK
112
+ Content-Type: application/vnd.api+json
113
+
114
+ {
115
+ "data": {
116
+ "id": "1",
117
+ "type": "cars",
118
+ "attributes": {
119
+ "name": "Civic Hybrid",
120
+ "brand": "Honda",
121
+ "year": 2024,
122
+ "engine_size": 1800
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### Get Mixed Vehicles
129
+ ```http
130
+ GET /vehicles
131
+ Accept: application/vnd.api+json
132
+
133
+ HTTP/1.1 200 OK
134
+ Content-Type: application/vnd.api+json
135
+
136
+ {
137
+ "data": [
138
+ {
139
+ "id": "1",
140
+ "type": "cars",
141
+ "attributes": {
142
+ "name": "Civic",
143
+ "brand": "Honda",
144
+ "year": 2024,
145
+ "engine_size": 1500
146
+ }
147
+ },
148
+ {
149
+ "id": "2",
150
+ "type": "trucks",
151
+ "attributes": {
152
+ "name": "F-150",
153
+ "brand": "Ford",
154
+ "year": 2024,
155
+ "cargo_capacity": 1000
156
+ }
157
+ }
158
+ ]
159
+ }
160
+ ```
@@ -34,6 +34,8 @@ module JPie
34
34
 
35
35
  def define_index_method(resource_class)
36
36
  define_method :index do
37
+ validate_include_params
38
+ validate_sort_params
37
39
  resources = resource_class.scope(context)
38
40
  sort_fields = parse_sort_params
39
41
  resources = resource_class.sort(resources, sort_fields) if sort_fields.any?
@@ -43,6 +45,7 @@ module JPie
43
45
 
44
46
  def define_show_method(resource_class)
45
47
  define_method :show do
48
+ validate_include_params
46
49
  resource = resource_class.scope(context).find(params[:id])
47
50
  render_jsonapi(resource)
48
51
  end
@@ -50,6 +53,7 @@ module JPie
50
53
 
51
54
  def define_create_method(resource_class)
52
55
  define_method :create do
56
+ validate_json_api_request
53
57
  attributes = deserialize_params
54
58
  resource = resource_class.model.create!(attributes)
55
59
  render_jsonapi(resource, status: :created)
@@ -58,6 +62,7 @@ module JPie
58
62
 
59
63
  def define_update_method(resource_class)
60
64
  define_method :update do
65
+ validate_json_api_request
61
66
  resource = resource_class.scope(context).find(params[:id])
62
67
  attributes = deserialize_params
63
68
  resource.update!(attributes)
@@ -76,6 +81,8 @@ module JPie
76
81
 
77
82
  # These methods can still be called manually or used to override defaults
78
83
  def index
84
+ validate_include_params
85
+ validate_sort_params
79
86
  resources = resource_class.scope(context)
80
87
  sort_fields = parse_sort_params
81
88
  resources = resource_class.sort(resources, sort_fields) if sort_fields.any?
@@ -83,17 +90,20 @@ module JPie
83
90
  end
84
91
 
85
92
  def show
93
+ validate_include_params
86
94
  resource = resource_class.scope(context).find(params[:id])
87
95
  render_jsonapi(resource)
88
96
  end
89
97
 
90
98
  def create
99
+ validate_json_api_request
91
100
  attributes = deserialize_params
92
101
  resource = model_class.create!(attributes)
93
102
  render_jsonapi(resource, status: :created)
94
103
  end
95
104
 
96
105
  def update
106
+ validate_json_api_request
97
107
  resource = resource_class.scope(context).find(params[:id])
98
108
  attributes = deserialize_params
99
109
  resource.update!(attributes)