better_controller 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/.DS_Store +0 -0
- data/.rspec +3 -0
- data/.rubocop.yml +95 -0
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +624 -0
- data/Rakefile +12 -0
- data/examples/api_controller.rb +31 -0
- data/examples/application_controller.rb +51 -0
- data/examples/products_controller.rb +25 -0
- data/examples/user_serializer.rb +15 -0
- data/examples/user_service.rb +31 -0
- data/examples/users_controller.rb +86 -0
- data/lib/better_controller/action_helpers.rb +76 -0
- data/lib/better_controller/base.rb +61 -0
- data/lib/better_controller/configuration.rb +69 -0
- data/lib/better_controller/logging.rb +101 -0
- data/lib/better_controller/method_not_overridden_error.rb +13 -0
- data/lib/better_controller/pagination.rb +44 -0
- data/lib/better_controller/parameter_validation.rb +86 -0
- data/lib/better_controller/params_helpers.rb +109 -0
- data/lib/better_controller/railtie.rb +18 -0
- data/lib/better_controller/resources_controller.rb +263 -0
- data/lib/better_controller/response_helpers.rb +74 -0
- data/lib/better_controller/serializer.rb +85 -0
- data/lib/better_controller/service.rb +94 -0
- data/lib/better_controller/version.rb +5 -0
- data/lib/better_controller.rb +55 -0
- data/lib/generators/better_controller/controller_generator.rb +65 -0
- data/lib/generators/better_controller/install_generator.rb +22 -0
- data/lib/generators/better_controller/templates/README +87 -0
- data/lib/generators/better_controller/templates/controller.rb +78 -0
- data/lib/generators/better_controller/templates/initializer.rb +31 -0
- data/lib/generators/better_controller/templates/serializer.rb +32 -0
- data/lib/generators/better_controller/templates/service.rb +57 -0
- data/lib/tasks/better_controller_tasks.rake +61 -0
- data/sig/better_controller.rbs +4 -0
- metadata +226 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Module providing enhanced parameter handling for controllers
|
5
|
+
module ParamsHelpers
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Get parameters with type casting
|
9
|
+
# @param key [Symbol, String] The parameter key
|
10
|
+
# @param type [Class] The type to cast to
|
11
|
+
# @param default [Object] The default value if parameter is missing
|
12
|
+
# @return [Object] The parameter value
|
13
|
+
def param(key, type: nil, default: nil)
|
14
|
+
value = params[key]
|
15
|
+
return default if value.nil?
|
16
|
+
|
17
|
+
case type
|
18
|
+
when :integer, Integer
|
19
|
+
value.to_i
|
20
|
+
when :float, Float
|
21
|
+
value.to_f
|
22
|
+
when :string, String
|
23
|
+
value.to_s
|
24
|
+
when :boolean, :bool
|
25
|
+
ActiveModel::Type::Boolean.new.cast(value)
|
26
|
+
when :date, Date
|
27
|
+
begin
|
28
|
+
Date.parse(value)
|
29
|
+
rescue ArgumentError
|
30
|
+
default
|
31
|
+
end
|
32
|
+
when :datetime, DateTime
|
33
|
+
begin
|
34
|
+
DateTime.parse(value)
|
35
|
+
rescue ArgumentError
|
36
|
+
default
|
37
|
+
end
|
38
|
+
when :array, Array
|
39
|
+
value.is_a?(Array) ? value : [value]
|
40
|
+
when :hash, Hash
|
41
|
+
value.is_a?(Hash) ? value : default
|
42
|
+
when :json, :JSON
|
43
|
+
begin
|
44
|
+
value.is_a?(Hash) ? value : JSON.parse(value)
|
45
|
+
rescue JSON::ParserError
|
46
|
+
default
|
47
|
+
end
|
48
|
+
else
|
49
|
+
value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get a boolean parameter
|
54
|
+
# @param key [Symbol, String] The parameter key
|
55
|
+
# @param default [Boolean] The default value if parameter is missing
|
56
|
+
# @return [Boolean] The parameter value
|
57
|
+
def boolean_param(key, default: false)
|
58
|
+
param(key, type: :boolean, default: default)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get an integer parameter
|
62
|
+
# @param key [Symbol, String] The parameter key
|
63
|
+
# @param default [Integer] The default value if parameter is missing
|
64
|
+
# @return [Integer] The parameter value
|
65
|
+
def integer_param(key, default: nil)
|
66
|
+
param(key, type: :integer, default: default)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get a float parameter
|
70
|
+
# @param key [Symbol, String] The parameter key
|
71
|
+
# @param default [Float] The default value if parameter is missing
|
72
|
+
# @return [Float] The parameter value
|
73
|
+
def float_param(key, default: nil)
|
74
|
+
param(key, type: :float, default: default)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get a date parameter
|
78
|
+
# @param key [Symbol, String] The parameter key
|
79
|
+
# @param default [Date] The default value if parameter is missing
|
80
|
+
# @return [Date] The parameter value
|
81
|
+
def date_param(key, default: nil)
|
82
|
+
param(key, type: :date, default: default)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get a datetime parameter
|
86
|
+
# @param key [Symbol, String] The parameter key
|
87
|
+
# @param default [DateTime] The default value if parameter is missing
|
88
|
+
# @return [DateTime] The parameter value
|
89
|
+
def datetime_param(key, default: nil)
|
90
|
+
param(key, type: :datetime, default: default)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get an array parameter
|
94
|
+
# @param key [Symbol, String] The parameter key
|
95
|
+
# @param default [Array] The default value if parameter is missing
|
96
|
+
# @return [Array] The parameter value
|
97
|
+
def array_param(key, default: [])
|
98
|
+
param(key, type: :array, default: default)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get a JSON parameter
|
102
|
+
# @param key [Symbol, String] The parameter key
|
103
|
+
# @param default [Hash] The default value if parameter is missing
|
104
|
+
# @return [Hash] The parameter value
|
105
|
+
def json_param(key, default: {})
|
106
|
+
param(key, type: :json, default: default)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Railtie for Rails integration
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer 'better_controller.configure_rails_initialization' do
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
# Make BetterController modules available to ActionController
|
9
|
+
ActiveSupport.on_load(:action_controller) { include BetterController }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add custom rake tasks if needed
|
14
|
+
rake_tasks do
|
15
|
+
load 'tasks/better_controller_tasks.rake'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Module providing standardized RESTful resource controller functionality
|
5
|
+
module ResourcesController
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Include the base BetterController modules
|
10
|
+
include BetterController::Base
|
11
|
+
include BetterController::ResponseHelpers
|
12
|
+
include BetterController::ParameterValidation
|
13
|
+
include BetterController::Pagination
|
14
|
+
|
15
|
+
# Configure default pagination
|
16
|
+
configure_pagination enabled: true, per_page: 25
|
17
|
+
end
|
18
|
+
|
19
|
+
# Index action to list all resources
|
20
|
+
def index
|
21
|
+
execute_action do
|
22
|
+
collection = resource_collection_resolver
|
23
|
+
|
24
|
+
# Apply pagination if enabled
|
25
|
+
if self.class.pagination_options[:enabled] && collection.respond_to?(:page)
|
26
|
+
@resource_collection = paginate(collection,
|
27
|
+
page: params[:page],
|
28
|
+
per_page: params[:per_page] || self.class.pagination_options[:per_page])
|
29
|
+
else
|
30
|
+
@resource_collection = collection
|
31
|
+
end
|
32
|
+
|
33
|
+
data = serialize_resource(@resource_collection, index_serializer)
|
34
|
+
respond_with_success(data, options: { meta: meta })
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Show action to display a specific resource
|
39
|
+
def show
|
40
|
+
execute_action do
|
41
|
+
@resource = resource_finder
|
42
|
+
|
43
|
+
data = serialize_resource(@resource, show_serializer)
|
44
|
+
respond_with_success(data, options: { meta: meta })
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create action to create a new resource
|
49
|
+
def create
|
50
|
+
execute_action do
|
51
|
+
@resource = resource_creator
|
52
|
+
|
53
|
+
if @resource.errors.any?
|
54
|
+
respond_with_error(@resource.errors, status: :unprocessable_entity)
|
55
|
+
else
|
56
|
+
data = serialize_resource(@resource, create_serializer)
|
57
|
+
respond_with_success(data, status: :created, options: {
|
58
|
+
message: create_message,
|
59
|
+
meta: meta,
|
60
|
+
})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Update action to update an existing resource
|
66
|
+
def update
|
67
|
+
execute_action do
|
68
|
+
@resource = resource_updater
|
69
|
+
|
70
|
+
if @resource.errors.any?
|
71
|
+
respond_with_error(@resource.errors, status: :unprocessable_entity)
|
72
|
+
else
|
73
|
+
data = serialize_resource(@resource, update_serializer)
|
74
|
+
respond_with_success(data, options: {
|
75
|
+
message: update_message,
|
76
|
+
meta: meta,
|
77
|
+
})
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Destroy action to delete a resource
|
83
|
+
def destroy
|
84
|
+
execute_action do
|
85
|
+
@resource = resource_destroyer
|
86
|
+
|
87
|
+
if @resource.errors.any?
|
88
|
+
respond_with_error(@resource.errors, status: :unprocessable_entity)
|
89
|
+
else
|
90
|
+
data = serialize_resource(@resource, destroy_serializer)
|
91
|
+
respond_with_success(data, options: {
|
92
|
+
message: destroy_message,
|
93
|
+
meta: meta,
|
94
|
+
})
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
# Serialize a resource using the specified serializer
|
102
|
+
# @param resource [Object] The resource to serialize
|
103
|
+
# @param serializer_class [Class] The serializer class
|
104
|
+
# @return [Hash, Array<Hash>] The serialized resource
|
105
|
+
def serialize_resource(resource, serializer_class)
|
106
|
+
return resource unless serializer_class && defined?(serializer_class)
|
107
|
+
|
108
|
+
serializer = serializer_class.new
|
109
|
+
serializer.serialize(resource, serialization_options)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Get serialization options
|
113
|
+
# @return [Hash] The serialization options
|
114
|
+
def serialization_options
|
115
|
+
{}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Get the serializer for index action
|
119
|
+
# @return [Class] The serializer class
|
120
|
+
def index_serializer
|
121
|
+
resource_serializer
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get the serializer for show action
|
125
|
+
# @return [Class] The serializer class
|
126
|
+
def show_serializer
|
127
|
+
resource_serializer
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get the serializer for create action
|
131
|
+
# @return [Class] The serializer class
|
132
|
+
def create_serializer
|
133
|
+
resource_serializer
|
134
|
+
end
|
135
|
+
|
136
|
+
# Get the serializer for update action
|
137
|
+
# @return [Class] The serializer class
|
138
|
+
def update_serializer
|
139
|
+
resource_serializer
|
140
|
+
end
|
141
|
+
|
142
|
+
# Get the serializer for destroy action
|
143
|
+
# @return [Class] The serializer class
|
144
|
+
def destroy_serializer
|
145
|
+
resource_serializer
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get the resource serializer class
|
149
|
+
# @return [Class] The serializer class
|
150
|
+
def resource_serializer
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
# Add metadata to the response
|
155
|
+
# @param key [Symbol] The metadata key
|
156
|
+
# @param value [Object] The metadata value
|
157
|
+
def add_meta(key, value)
|
158
|
+
meta[key] = value
|
159
|
+
end
|
160
|
+
|
161
|
+
# Get the metadata hash
|
162
|
+
# @return [Hash] The metadata
|
163
|
+
def meta
|
164
|
+
@meta ||= {}
|
165
|
+
end
|
166
|
+
|
167
|
+
# Get the resource service
|
168
|
+
# @return [Object] The resource service
|
169
|
+
def resource_service
|
170
|
+
resource_service_class.new
|
171
|
+
end
|
172
|
+
|
173
|
+
# Get the resource service class
|
174
|
+
# @return [Class] The resource service class
|
175
|
+
def resource_service_class
|
176
|
+
raise NotImplementedError, "#{self.class.name} must implement #{__method__}"
|
177
|
+
end
|
178
|
+
|
179
|
+
# Get the root key for resource parameters
|
180
|
+
# @return [Symbol] The root key
|
181
|
+
def resource_params_root_key
|
182
|
+
raise NotImplementedError, "#{self.class.name} must implement #{__method__}"
|
183
|
+
end
|
184
|
+
|
185
|
+
# Get the message for create action
|
186
|
+
# @return [String, nil] The message
|
187
|
+
def create_message
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
|
191
|
+
# Get the message for update action
|
192
|
+
# @return [String, nil] The message
|
193
|
+
def update_message
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
|
197
|
+
# Get the message for destroy action
|
198
|
+
# @return [String, nil] The message
|
199
|
+
def destroy_message
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
|
203
|
+
# Get the resource parameters
|
204
|
+
# @return [Hash] The resource parameters
|
205
|
+
def resource_params
|
206
|
+
@resource_params ||= params.require(resource_params_root_key).permit!
|
207
|
+
end
|
208
|
+
|
209
|
+
# Resolve the resource collection
|
210
|
+
# @return [Object] The resource collection
|
211
|
+
def resource_collection_resolver
|
212
|
+
resource_service.all(ancestry_key_params)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Find a specific resource
|
216
|
+
# @return [Object] The resource
|
217
|
+
def resource_finder
|
218
|
+
resource_service.find(ancestry_key_params, resource_key_param)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Create a new resource
|
222
|
+
# @return [Object] The created resource
|
223
|
+
def resource_creator
|
224
|
+
resource_service.create(ancestry_key_params, resource_create_params)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Update an existing resource
|
228
|
+
# @return [Object] The updated resource
|
229
|
+
def resource_updater
|
230
|
+
resource_service.update(ancestry_key_params, resource_key_param, resource_update_params)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Destroy a resource
|
234
|
+
# @return [Object] The destroyed resource
|
235
|
+
def resource_destroyer
|
236
|
+
resource_service.destroy(ancestry_key_params, resource_key_param)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Get the resource key parameter
|
240
|
+
# @return [String, Integer] The resource key
|
241
|
+
def resource_key_param
|
242
|
+
params[:id]
|
243
|
+
end
|
244
|
+
|
245
|
+
# Get the ancestry key parameters
|
246
|
+
# @return [Hash] The ancestry key parameters
|
247
|
+
def ancestry_key_params
|
248
|
+
{}
|
249
|
+
end
|
250
|
+
|
251
|
+
# Get the parameters for creating a resource
|
252
|
+
# @return [Hash] The create parameters
|
253
|
+
def resource_create_params
|
254
|
+
resource_params
|
255
|
+
end
|
256
|
+
|
257
|
+
# Get the parameters for updating a resource
|
258
|
+
# @return [Hash] The update parameters
|
259
|
+
def resource_update_params
|
260
|
+
resource_params
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Module providing helper methods for standardized API responses
|
5
|
+
module ResponseHelpers
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Respond with a success response
|
9
|
+
# @param data [Object] The data to include in the response
|
10
|
+
# @param status [Symbol, Integer] The HTTP status code
|
11
|
+
# @param options [Hash] Additional options for the response
|
12
|
+
# @return [Object] The formatted response
|
13
|
+
def respond_with_success(data = nil, status: :ok, options: {})
|
14
|
+
response = {
|
15
|
+
success: true,
|
16
|
+
data: data,
|
17
|
+
}.merge(options)
|
18
|
+
|
19
|
+
if defined?(render)
|
20
|
+
render json: response, status: status
|
21
|
+
else
|
22
|
+
response
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Respond with an error response
|
27
|
+
# @param error [Exception, String] The error or error message
|
28
|
+
# @param status [Symbol, Integer] The HTTP status code
|
29
|
+
# @param options [Hash] Additional options for the response
|
30
|
+
# @return [Object] The formatted error response
|
31
|
+
def respond_with_error(error = nil, status: :unprocessable_entity, options: {})
|
32
|
+
error_message = error.is_a?(Exception) ? error.message : error.to_s
|
33
|
+
error_type = error.is_a?(Exception) ? error.class.name : 'Error'
|
34
|
+
|
35
|
+
response = {
|
36
|
+
success: false,
|
37
|
+
error: {
|
38
|
+
type: error_type,
|
39
|
+
message: error_message,
|
40
|
+
},
|
41
|
+
}.merge(options)
|
42
|
+
|
43
|
+
if defined?(render)
|
44
|
+
render json: response, status: status
|
45
|
+
else
|
46
|
+
response
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Respond with a paginated collection
|
51
|
+
# @param collection [Object] The collection to paginate
|
52
|
+
# @param options [Hash] Pagination options
|
53
|
+
# @return [Object] The paginated response
|
54
|
+
def respond_with_pagination(collection, options = {})
|
55
|
+
page = (options[:page] || 1).to_i
|
56
|
+
per_page = (options[:per_page] || 25).to_i
|
57
|
+
|
58
|
+
paginated = collection.page(page).per(per_page)
|
59
|
+
|
60
|
+
respond_with_success(
|
61
|
+
paginated,
|
62
|
+
options: {
|
63
|
+
meta: {
|
64
|
+
pagination: {
|
65
|
+
current_page: paginated.current_page,
|
66
|
+
total_pages: paginated.total_pages,
|
67
|
+
total_count: paginated.total_count,
|
68
|
+
},
|
69
|
+
},
|
70
|
+
}
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Base module for serializing resources
|
5
|
+
module Serializer
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
# Define attributes to be included in the serialized output
|
10
|
+
# @param attrs [Array<Symbol>] The attributes to include
|
11
|
+
def attributes(*attrs)
|
12
|
+
@attributes ||= []
|
13
|
+
@attributes.concat(attrs) if attrs.any?
|
14
|
+
@attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define associations to be included in the serialized output
|
18
|
+
# @param assocs [Hash] The associations to include
|
19
|
+
def associations(assocs = {})
|
20
|
+
@associations ||= {}
|
21
|
+
@associations.merge!(assocs) if assocs.any?
|
22
|
+
@associations
|
23
|
+
end
|
24
|
+
|
25
|
+
# Define methods to be included in the serialized output
|
26
|
+
# @param meths [Array<Symbol>] The methods to include
|
27
|
+
def methods(*meths)
|
28
|
+
@methods ||= []
|
29
|
+
@methods.concat(meths) if meths.any?
|
30
|
+
@methods
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Serialize a resource
|
35
|
+
# @param resource [Object] The resource to serialize
|
36
|
+
# @param options [Hash] Serialization options
|
37
|
+
# @return [Hash] The serialized resource
|
38
|
+
def serialize(resource, options = {})
|
39
|
+
return nil if resource.nil?
|
40
|
+
|
41
|
+
if resource.respond_to?(:each) && !resource.is_a?(Hash)
|
42
|
+
serialize_collection(resource, options)
|
43
|
+
else
|
44
|
+
serialize_resource(resource, options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Serialize a collection of resources
|
49
|
+
# @param collection [Array, ActiveRecord::Relation] The collection to serialize
|
50
|
+
# @param options [Hash] Serialization options
|
51
|
+
# @return [Array<Hash>] The serialized collection
|
52
|
+
def serialize_collection(collection, options = {})
|
53
|
+
collection.map { |resource| serialize_resource(resource, options) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Serialize a single resource
|
57
|
+
# @param resource [Object] The resource to serialize
|
58
|
+
# @param options [Hash] Serialization options
|
59
|
+
# @return [Hash] The serialized resource
|
60
|
+
def serialize_resource(resource, options = {})
|
61
|
+
result = {}
|
62
|
+
|
63
|
+
# Add attributes
|
64
|
+
self.class.attributes.each do |attr|
|
65
|
+
result[attr] = resource.send(attr) if resource.respond_to?(attr)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Add methods
|
69
|
+
self.class.methods.each do |meth|
|
70
|
+
result[meth] = resource.send(meth) if resource.respond_to?(meth)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add associations
|
74
|
+
self.class.associations.each do |name, serializer_class|
|
75
|
+
next unless resource.respond_to?(name)
|
76
|
+
|
77
|
+
association = resource.send(name)
|
78
|
+
serializer = serializer_class.new
|
79
|
+
result[name] = serializer.serialize(association, options)
|
80
|
+
end
|
81
|
+
|
82
|
+
result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterController
|
4
|
+
# Base service class for resource operations
|
5
|
+
class Service
|
6
|
+
# Get all resources
|
7
|
+
# @param ancestry_params [Hash] Ancestry parameters for nested resources
|
8
|
+
# @return [ActiveRecord::Relation] Collection of resources
|
9
|
+
def all(ancestry_params = {})
|
10
|
+
resource_scope(ancestry_params).all
|
11
|
+
end
|
12
|
+
|
13
|
+
# Find a specific resource
|
14
|
+
# @param ancestry_params [Hash] Ancestry parameters for nested resources
|
15
|
+
# @param id [Integer, String] The resource ID
|
16
|
+
# @return [Object] The found resource
|
17
|
+
def find(ancestry_params = {}, id)
|
18
|
+
resource_scope(ancestry_params).find(id)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Create a new resource
|
22
|
+
# @param ancestry_params [Hash] Ancestry parameters for nested resources
|
23
|
+
# @param attributes [Hash] The resource attributes
|
24
|
+
# @return [Object] The created resource
|
25
|
+
def create(ancestry_params = {}, attributes)
|
26
|
+
resource = resource_class.new(prepare_attributes(attributes, ancestry_params))
|
27
|
+
|
28
|
+
resource.save if resource.respond_to?(:save)
|
29
|
+
|
30
|
+
resource
|
31
|
+
end
|
32
|
+
|
33
|
+
# Update an existing resource
|
34
|
+
# @param ancestry_params [Hash] Ancestry parameters for nested resources
|
35
|
+
# @param id [Integer, String] The resource ID
|
36
|
+
# @param attributes [Hash] The resource attributes
|
37
|
+
# @return [Object] The updated resource
|
38
|
+
def update(ancestry_params = {}, id, attributes)
|
39
|
+
resource = find(ancestry_params, id)
|
40
|
+
|
41
|
+
resource.update(prepare_attributes(attributes, ancestry_params)) if resource.respond_to?(:update)
|
42
|
+
|
43
|
+
resource
|
44
|
+
end
|
45
|
+
|
46
|
+
# Destroy a resource
|
47
|
+
# @param ancestry_params [Hash] Ancestry parameters for nested resources
|
48
|
+
# @param id [Integer, String] The resource ID
|
49
|
+
# @return [Object] The destroyed resource
|
50
|
+
def destroy(ancestry_params = {}, id)
|
51
|
+
resource = find(ancestry_params, id)
|
52
|
+
|
53
|
+
resource.destroy if resource.respond_to?(:destroy)
|
54
|
+
|
55
|
+
resource
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
# Get the resource class
|
61
|
+
# @return [Class] The resource class
|
62
|
+
def resource_class
|
63
|
+
raise NotImplementedError, "#{self.class.name} must implement #{__method__}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get the resource scope
|
67
|
+
# @param ancestry_params [Hash] Ancestry parameters for nested resources
|
68
|
+
# @return [ActiveRecord::Relation] The resource scope
|
69
|
+
def resource_scope(ancestry_params = {})
|
70
|
+
scope = resource_class
|
71
|
+
|
72
|
+
ancestry_params.each do |key, value|
|
73
|
+
key.to_s.sub(/_id$/, '')
|
74
|
+
scope = scope.where("#{key}": value) if resource_class.column_names.include?(key.to_s)
|
75
|
+
end
|
76
|
+
|
77
|
+
scope
|
78
|
+
end
|
79
|
+
|
80
|
+
# Prepare attributes for create/update
|
81
|
+
# @param attributes [Hash] The resource attributes
|
82
|
+
# @param ancestry_params [Hash] Ancestry parameters for nested resources
|
83
|
+
# @return [Hash] The prepared attributes
|
84
|
+
def prepare_attributes(attributes, ancestry_params = {})
|
85
|
+
prepared = attributes.dup
|
86
|
+
|
87
|
+
ancestry_params.each do |key, value|
|
88
|
+
prepared[key] = value if resource_class.column_names.include?(key.to_s)
|
89
|
+
end
|
90
|
+
|
91
|
+
prepared
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zeitwerk'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
|
7
|
+
# Set up Zeitwerk autoloading
|
8
|
+
loader = Zeitwerk::Loader.for_gem
|
9
|
+
loader.setup
|
10
|
+
|
11
|
+
# Manually require core files
|
12
|
+
require_relative 'better_controller/version'
|
13
|
+
require_relative 'better_controller/method_not_overridden_error'
|
14
|
+
require_relative 'better_controller/configuration'
|
15
|
+
require_relative 'better_controller/logging'
|
16
|
+
require_relative 'better_controller/base'
|
17
|
+
require_relative 'better_controller/response_helpers'
|
18
|
+
require_relative 'better_controller/parameter_validation'
|
19
|
+
require_relative 'better_controller/action_helpers'
|
20
|
+
require_relative 'better_controller/service'
|
21
|
+
require_relative 'better_controller/serializer'
|
22
|
+
require_relative 'better_controller/pagination'
|
23
|
+
require_relative 'better_controller/params_helpers'
|
24
|
+
require_relative 'better_controller/resources_controller'
|
25
|
+
|
26
|
+
# Rails integration
|
27
|
+
require_relative 'better_controller/railtie' if defined?(Rails)
|
28
|
+
|
29
|
+
module BetterController
|
30
|
+
class Error < StandardError; end
|
31
|
+
|
32
|
+
# Configure BetterController
|
33
|
+
# @yield [config] The configuration block
|
34
|
+
def self.configure
|
35
|
+
yield(Configuration) if block_given?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the current configuration
|
39
|
+
# @return [BetterController::Configuration] The current configuration
|
40
|
+
def self.config
|
41
|
+
Configuration
|
42
|
+
end
|
43
|
+
|
44
|
+
# Include BetterController modules in a controller
|
45
|
+
# @param base [Class] The controller class
|
46
|
+
def self.included(base)
|
47
|
+
base.include(BetterController::Base)
|
48
|
+
base.include(BetterController::ResponseHelpers)
|
49
|
+
base.include(BetterController::ParameterValidation)
|
50
|
+
base.include(BetterController::ParamsHelpers)
|
51
|
+
base.include(BetterController::Logging)
|
52
|
+
base.extend(BetterController::ActionHelpers::ClassMethods)
|
53
|
+
base.extend(BetterController::Logging::ClassMethods)
|
54
|
+
end
|
55
|
+
end
|