praxis 2.0.pre.10 → 2.0.pre.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -3
- data/CHANGELOG.md +9 -0
- data/bin/praxis +59 -2
- data/lib/praxis/bootloader_stages/environment.rb +1 -0
- data/lib/praxis/docs/open_api_generator.rb +1 -1
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +57 -8
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
- data/lib/praxis/extensions/pagination.rb +5 -32
- data/lib/praxis/mapper/active_model_compat.rb +4 -0
- data/lib/praxis/mapper/resource.rb +18 -2
- data/lib/praxis/mapper/selector_generator.rb +1 -0
- data/lib/praxis/mapper/sequel_compat.rb +7 -0
- data/lib/praxis/plugins/mapper_plugin.rb +22 -13
- data/lib/praxis/plugins/pagination_plugin.rb +34 -4
- data/lib/praxis/tasks/api_docs.rb +4 -1
- data/lib/praxis/version.rb +1 -1
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +15 -2
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
- data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
- data/tasks/thor/example.rb +12 -6
- data/tasks/thor/model.rb +40 -0
- data/tasks/thor/scaffold.rb +117 -0
- data/tasks/thor/templates/generator/empty_app/config/environment.rb +1 -0
- data/tasks/thor/templates/generator/example_app/Rakefile +9 -2
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
- data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +2 -2
- data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +7 -28
- data/tasks/thor/templates/generator/example_app/config.ru +1 -2
- data/tasks/thor/templates/generator/example_app/config/environment.rb +2 -1
- data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +3 -2
- data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
- data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +4 -4
- data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +1 -6
- data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +4 -2
- data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +2 -2
- data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +2 -2
- data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
- data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
- data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
- data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
- data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7bbd374311046cf8d12c68d564382de6cbfd0c1032c45bbc8c1ac00d7a02e68a
|
4
|
+
data.tar.gz: e33676f45266facdbcae595c2c05ca94ae8dfa34134aa9e4113e5a5d9d729052
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5db86e95bd0b723560036435ded99b893fd92bd55f20dc4b49e326d96e7c9422549124ba986eb3248ff746d6bf928a60e6677b854c8761a34c026c7430a578bb
|
7
|
+
data.tar.gz: 39677f252617f79d3d054fd2286562d3355310d8161096c7c73294d2b242cee4ebdf749b462d7f1c5261fc487e80ba473dcd258dcb77a510c47db7e698275cad
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 2.0.pre.11
|
6
|
+
|
7
|
+
- Remove MapperPlugin's `set_selectors` (made `selector_generator` lazy instead), and ensure it includes the rendering extensions to the Controllers. Less things to configure if you opt into the Mapper way.
|
8
|
+
- Built scaffolding generator for quickly creating a new API endpoint in the praxis binary (it builds endpoint+mediatype+controller+resource at one, with useful base code and comments)
|
9
|
+
- Dropped support for Ruby 2.4 and 2.5 as some of the newest dependent gems are dropping it as well.
|
10
|
+
- Simplify filters_mapping definition, by not requiring to define same-name mappings if the underlying model has an attribute with the same exact name. i.e., a `name: :name` entry is not necessary if the model has a `:name` attribute.
|
11
|
+
|
12
|
+
## 2.0.pre.10
|
13
|
+
|
5
14
|
- Simple, but pervasive breaking change: Rename `ResourceDefinition` to `EndpointDefinition` (but same functionality).
|
6
15
|
- Remove all deprecated features (and raise error describing it's not supported yet)
|
7
16
|
- Remove `Links` and `LinkBuilder`. Those seem unnecessary from a Framework point of view as they aren't clear most
|
data/bin/praxis
CHANGED
@@ -46,7 +46,7 @@ class PraxisGenerator < Thor
|
|
46
46
|
def routes
|
47
47
|
end
|
48
48
|
|
49
|
-
desc "docs [generate|browser|package]",
|
49
|
+
desc "docs [generate|browser|package]", <<~EOF
|
50
50
|
Generates API documentation and a Web App to inspect it
|
51
51
|
generate - Generates the JSON docs
|
52
52
|
browser - (default) Generates JSON docs, and automatically starts a Web app to browse them.
|
@@ -81,7 +81,64 @@ class PraxisGenerator < Thor
|
|
81
81
|
gen = ::PraxisGen::Example.new([app_name])
|
82
82
|
gen.destination_root = app_name
|
83
83
|
gen.invoke(:example)
|
84
|
-
end
|
84
|
+
end
|
85
|
+
|
86
|
+
desc_for "g COLLECTION_NAME", ::PraxisGen::Scaffold, :g
|
87
|
+
# Cannot use the argument below or it will apply to all commands (the action in the class has it)
|
88
|
+
# argument :collection_name, required: false
|
89
|
+
# The options, however, since they're optional are fine (But need to be duplicated from the class :( )
|
90
|
+
option :version, required: false, default: '1',
|
91
|
+
desc: 'Version string for the API endpoint. This also dictates the directory structure (i.e., v1/endpoints/...))'
|
92
|
+
option :design, type: :boolean, default: true,
|
93
|
+
desc: 'Include the Endpoint and MediaType files for the collection'
|
94
|
+
option :implementation, type: :boolean, default: true,
|
95
|
+
desc: 'Include the Controller and (possibly the) Resource files for the collection (see --no-resource)'
|
96
|
+
option :resource, type: :boolean, default: true,
|
97
|
+
desc: 'Disable (or enable) the creation of the Resource files when generating implementation'
|
98
|
+
option :model, type: :string, enum: ['activerecord','sequel'],
|
99
|
+
desc: 'It also generates a model for the given ORM. An empty --model flag will default to activerecord'
|
100
|
+
option :actions, type: :string, default: 'crud', enum: ['cr','cru','crud','u','ud','d'],
|
101
|
+
desc: 'Specifies the actions to generate for the API. cr=create, u=update, d=delete. Index and show actions are always generated'
|
102
|
+
def g(*args)
|
103
|
+
# Because we cannot share the :collection_name argument, we need to do this check here, before
|
104
|
+
# we "parse" it and pass it to the g command
|
105
|
+
unless args.size == 1
|
106
|
+
::PraxisGen::Scaffold.command_help(shell,:g)
|
107
|
+
exit 1
|
108
|
+
end
|
109
|
+
|
110
|
+
collection_name,_ = args
|
111
|
+
::PraxisGen::Scaffold.new([collection_name],options).invoke(:g)
|
112
|
+
if options[:model]
|
113
|
+
# Make it easy to be able to both enable or not enable the creation of the model, by passing --model=...
|
114
|
+
# but also make it easy so that if there is no value for it, it default to activerecord
|
115
|
+
opts = {orm: options[:model] }
|
116
|
+
opts[:orm] = 'activerecord' if opts[:orm] == 'model' # value is model param passed by no value
|
117
|
+
::PraxisGen::Model.new([collection_name.singularize],opts).invoke(:g)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Initially, the idea was to build some quick model generator, but I think it's better to keep it
|
122
|
+
# simple and just use the scaffold generator with `--no-implementation --no-design --model` instead
|
123
|
+
# Left here in case we want to rescue it
|
124
|
+
# desc_for "gmodel MODEL_NAME", ::PraxisGen::Model, :g
|
125
|
+
# # Cannot use the argument below or it will apply to all commands (the action in the class has it)
|
126
|
+
# # argument :collection_name, required: false
|
127
|
+
# # The options, however, since they're optional are fine (But need to be duplicated from the class :( )
|
128
|
+
# option :orm, required: false, default: 'activerecord', enum: ['activerecord','sequel'],
|
129
|
+
# desc: 'Type of ORM model to create.'
|
130
|
+
# def gmodel(*args)
|
131
|
+
# # Because we cannot share the :collection_name argument, we need to do this check here, before
|
132
|
+
# # we "parse" it and pass it to the g command
|
133
|
+
# unless args.size == 1
|
134
|
+
# ::PraxisGen::Model.command_help(shell,:g)
|
135
|
+
# exit 1
|
136
|
+
# end
|
137
|
+
|
138
|
+
# model_name,_ = args
|
139
|
+
# ::PraxisGen::Model.new([model_name],options).invoke(:g)
|
140
|
+
# end
|
141
|
+
|
85
142
|
end
|
86
143
|
|
87
144
|
PraxisGenerator.start(ARGV)
|
@@ -244,7 +244,7 @@ module Praxis
|
|
244
244
|
resources_by_version.keys.each do |version|
|
245
245
|
FileUtils.mkdir_p @doc_root_dir + '/' + version
|
246
246
|
end
|
247
|
-
FileUtils.mkdir_p @doc_root_dir + '/unversioned'
|
247
|
+
FileUtils.mkdir_p @doc_root_dir + '/unversioned' if resources_by_version.keys.include?('n/a')
|
248
248
|
end
|
249
249
|
|
250
250
|
def normalize_media_types( mtis )
|
@@ -5,27 +5,43 @@ module Praxis
|
|
5
5
|
module AttributeFiltering
|
6
6
|
ALIAS_TABLE_PREFIX = ''
|
7
7
|
require_relative 'active_record_patches'
|
8
|
+
# Helper class that can present an SqlLiteral string which we have already quoted
|
9
|
+
# ... but! that can properly provide a "to_sym" that has the value unquoted
|
10
|
+
# This is necessary as (the latest AR code):
|
11
|
+
# * does not carry over "references" in joins if they are not SqlLiterals
|
12
|
+
# * but, at the same time, it indexes the references using the .to_sym value (which is really expected to be the normal string, without quotes)
|
13
|
+
# If we pass a normal SqlLiteral, instead of our wrapper, without quoting the table, the current AR code will never quote it to form the
|
14
|
+
# SQL string, as it's already a literal...so our "/" type separators as names won't work without quoting.
|
15
|
+
class QuasiSqlLiteral < Arel::Nodes::SqlLiteral
|
16
|
+
def initialize(quoted:, symbolized:)
|
17
|
+
@symbolized = symbolized
|
18
|
+
super(quoted)
|
19
|
+
end
|
20
|
+
def to_sym
|
21
|
+
@symbolized
|
22
|
+
end
|
23
|
+
end
|
8
24
|
|
9
25
|
class ActiveRecordFilterQueryBuilder
|
10
|
-
attr_reader :query, :model, :
|
26
|
+
attr_reader :query, :model, :filters_map
|
11
27
|
|
12
28
|
# Base query to build upon
|
13
29
|
def initialize(query: , model:, filters_map:, debug: false)
|
14
30
|
@query = query
|
15
31
|
@model = model
|
16
|
-
@
|
32
|
+
@filters_map = filters_map
|
17
33
|
@logger = debug ? Logger.new(STDOUT) : nil
|
18
34
|
end
|
19
35
|
|
20
|
-
def
|
21
|
-
@logger
|
36
|
+
def debug_query(msg, query)
|
37
|
+
@logger.info(msg + query.to_sql) if @logger
|
22
38
|
end
|
23
39
|
|
24
40
|
def generate(filters)
|
25
41
|
# Resolve the names and values first, based on filters_map
|
26
42
|
root_node = _convert_to_treenode(filters)
|
27
43
|
craft_filter_query(root_node, for_model: @model)
|
28
|
-
|
44
|
+
debug_query("SQL due to filters: ", @query.all)
|
29
45
|
@query
|
30
46
|
end
|
31
47
|
|
@@ -45,13 +61,30 @@ module Praxis
|
|
45
61
|
|
46
62
|
private
|
47
63
|
|
64
|
+
def _mapped_filter(name)
|
65
|
+
target = @filters_map[name]
|
66
|
+
unless target
|
67
|
+
if @model.attribute_names.include?(name.to_s)
|
68
|
+
# Cache it in the filters mapping (to avoid later lookups), and return it.
|
69
|
+
@filters_map[name] = name
|
70
|
+
target = name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return target
|
74
|
+
end
|
75
|
+
|
48
76
|
# Resolve and convert from filters, to a more manageable and param-type-independent structure
|
49
77
|
def _convert_to_treenode(filters)
|
50
78
|
# Resolve the names and values first, based on filters_map
|
51
79
|
resolved_array = []
|
52
80
|
filters.parsed_array.each do |filter|
|
53
|
-
mapped_value =
|
54
|
-
|
81
|
+
mapped_value = _mapped_filter(filter[:name])
|
82
|
+
unless mapped_value
|
83
|
+
msg = "Filtering by #{filter[:name]} is not allowed. No implementation mapping defined for it has been found \
|
84
|
+
and there is not a model attribute with this name either.\n" \
|
85
|
+
"Please add a mapping for #{filter[:name]} in the `filters_mapping` method of the appropriate Resource class"
|
86
|
+
raise msg
|
87
|
+
end
|
55
88
|
bindings_array = \
|
56
89
|
if mapped_value.is_a?(Proc)
|
57
90
|
result = mapped_value.call(filter)
|
@@ -85,7 +118,7 @@ module Praxis
|
|
85
118
|
end
|
86
119
|
|
87
120
|
def add_clause(column_prefix:, column_object:, op:, value:)
|
88
|
-
@query = @query.references(column_prefix) #Mark where clause referencing the appropriate alias
|
121
|
+
@query = @query.references(build_reference_value(column_prefix)) #Mark where clause referencing the appropriate alias
|
89
122
|
likeval = get_like_value(value)
|
90
123
|
case op
|
91
124
|
when '!' # name! means => name IS NOT NULL (and the incoming value is nil)
|
@@ -165,6 +198,22 @@ module Praxis
|
|
165
198
|
likeval
|
166
199
|
end
|
167
200
|
end
|
201
|
+
|
202
|
+
# The value that we need to stick in the references method is different in the latest Rails
|
203
|
+
maj, min, _ = ActiveRecord.gem_version.segments
|
204
|
+
if maj == 5 || (maj == 6 && min == 0)
|
205
|
+
# In AR 6 (and 6.0) the references are simple strings
|
206
|
+
def build_reference_value(column_prefix)
|
207
|
+
column_prefix
|
208
|
+
end
|
209
|
+
else
|
210
|
+
# The latest AR versions discard passing references to joins when they're not SqlLiterals ... so let's wrap it
|
211
|
+
# with our class, so that it is a literal (already quoted), but that can still provide the expected "symbol" without quotes
|
212
|
+
# so that our aliasing code can match it.
|
213
|
+
def build_reference_value(column_prefix)
|
214
|
+
QuasiSqlLiteral.new(quoted: query.connection.quote_table_name(column_prefix), symbolized: column_prefix.to_sym)
|
215
|
+
end
|
216
|
+
end
|
168
217
|
end
|
169
218
|
end
|
170
219
|
end
|
@@ -9,7 +9,7 @@ module Praxis
|
|
9
9
|
class << self
|
10
10
|
def for(definition)
|
11
11
|
Class.new(self) do
|
12
|
-
@
|
12
|
+
@filters_map = case definition
|
13
13
|
when Hash
|
14
14
|
definition
|
15
15
|
when Array
|
@@ -18,7 +18,7 @@ module Praxis
|
|
18
18
|
raise "Cannot use FilterQueryBuilder.of without passing an array or a hash (Got: #{definition.class.name})"
|
19
19
|
end
|
20
20
|
class << self
|
21
|
-
attr_reader :
|
21
|
+
attr_reader :filters_map
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -33,13 +33,18 @@ module Praxis
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# By default we'll simply use the incoming op and value, and will map
|
36
|
-
# the attribute based on what's on the `
|
36
|
+
# the attribute based on what's on the `filters_map` definition
|
37
37
|
def generate(filters)
|
38
38
|
raise "Not refactored yet!"
|
39
39
|
seen_associations = Set.new
|
40
40
|
filters.each do |(attr, spec)|
|
41
|
-
column_name =
|
42
|
-
|
41
|
+
column_name = _mapped_filter(attr)
|
42
|
+
unless column_name
|
43
|
+
msg = "Filtering by #{attr} is not allowed. No implementation mapping defined for it has been found \
|
44
|
+
and there is not a model attribute with this name either.\n" \
|
45
|
+
"Please add a mapping for #{attr} in the `filters_mapping` method of the appropriate Resource class"
|
46
|
+
raise msg
|
47
|
+
end
|
43
48
|
if column_name.is_a?(Proc)
|
44
49
|
bindings = column_name.call(spec)
|
45
50
|
# A hash of bindings, consisting of a key with column name and a value to the query value
|
@@ -64,9 +69,16 @@ module Praxis
|
|
64
69
|
add_clause(attr: column_name, op: op, value: value)
|
65
70
|
end
|
66
71
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
72
|
+
def _mapped_filter(name)
|
73
|
+
target = self.class.filters_map[name]
|
74
|
+
unless target
|
75
|
+
if @model.attribute_names.include?(name.to_s)
|
76
|
+
# Cache it in the filters mapping (to avoid later lookups), and return it.
|
77
|
+
self.class.filters_map[name] = name
|
78
|
+
target = name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return target
|
70
82
|
end
|
71
83
|
|
72
84
|
# Private to try to funnel all column names through `generate` that restricts
|
@@ -14,10 +14,9 @@ module Praxis
|
|
14
14
|
module Pagination
|
15
15
|
extend ActiveSupport::Concern
|
16
16
|
# This PaginatedController concern should be added to controllers that have actions that define the
|
17
|
-
# pagination and order parameters so that
|
18
|
-
#
|
19
|
-
# This
|
20
|
-
# can be easily applied to other chainable query proxies.
|
17
|
+
# pagination and order parameters so that one can call the domain model to craft the query
|
18
|
+
# `domain_model.craft_pagination_query(base_query, pagination: _pagination)`
|
19
|
+
# This will handle all the required logic for paginating, ordering and generating the Link and TotalCount headers.
|
21
20
|
#
|
22
21
|
# Here's a simple example on how to use it for a fake Items controller
|
23
22
|
# class Items < V1::Controllers::BaseController
|
@@ -29,7 +28,8 @@ module Praxis
|
|
29
28
|
#
|
30
29
|
# def index(filters: nil, pagination: nil, order: nil, **_args)
|
31
30
|
# items = current_user.items.all
|
32
|
-
#
|
31
|
+
# domain_model = self.media_type.domain_model
|
32
|
+
# items = domain_model.craft_pagination_query( query: items, pagination: _pagination)
|
33
33
|
#
|
34
34
|
# display(items)
|
35
35
|
# end
|
@@ -71,33 +71,6 @@ module Praxis
|
|
71
71
|
@_pagination = PaginationStruct.new(pagination[:paginator], pagination[:order])
|
72
72
|
end
|
73
73
|
|
74
|
-
# Main entrypoint: Handles all pagination pieces
|
75
|
-
# takes:
|
76
|
-
# * the query to build from and the table
|
77
|
-
# * the request (for link header generation)
|
78
|
-
# * requires the _pagination variable to be there (set by this module) to return the pagination struct
|
79
|
-
def _craft_pagination_query(query:, type: :active_record)
|
80
|
-
handler_klass = \
|
81
|
-
case type
|
82
|
-
when :active_record
|
83
|
-
ActiveRecordPaginationHandler
|
84
|
-
when :sequel
|
85
|
-
SequelPaginationHandler
|
86
|
-
else
|
87
|
-
raise "Attempting to use pagination but Active Record or Sequel gems found"
|
88
|
-
end
|
89
|
-
|
90
|
-
# Gather and save the count if required
|
91
|
-
if _pagination.paginator&.total_count
|
92
|
-
_pagination.total_count = handler_klass.count(query.dup)
|
93
|
-
end
|
94
|
-
|
95
|
-
query = handler_klass.order(query, _pagination.order)
|
96
|
-
# Maybe this is a class instance instead of a class method?...(of the appropriate AR/Sequel type)...
|
97
|
-
# self.class.paginate(query, table, _pagination)
|
98
|
-
handler_klass.paginate(query, _pagination)
|
99
|
-
end
|
100
|
-
|
101
74
|
def build_pagination_headers(pagination:, current_url:, current_query_params:)
|
102
75
|
links = if pagination.paginator.by
|
103
76
|
# We're assuming that the last element has a "symbol/string" field with the same name of the "by" pagination.
|
@@ -23,6 +23,10 @@ module Praxis
|
|
23
23
|
Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector
|
24
24
|
end
|
25
25
|
|
26
|
+
def _pagination_query_builder_class
|
27
|
+
Praxis::Extensions::Pagination::ActiveRecordPaginationHandler
|
28
|
+
end
|
29
|
+
|
26
30
|
def _praxis_associations
|
27
31
|
orig = self.reflections.clone
|
28
32
|
|
@@ -30,6 +30,7 @@ module Praxis::Mapper
|
|
30
30
|
end
|
31
31
|
|
32
32
|
@properties = self.superclass.properties.clone
|
33
|
+
@_filters_map = {}
|
33
34
|
end
|
34
35
|
|
35
36
|
end
|
@@ -197,7 +198,7 @@ module Praxis::Mapper
|
|
197
198
|
|
198
199
|
# TODO: this shouldn't be needed if we incorporate it with the properties of the mapper...
|
199
200
|
# ...maybe what this means is that we can change it for a better DSL in the resource?
|
200
|
-
def self.filters_mapping(definition)
|
201
|
+
def self.filters_mapping(definition={})
|
201
202
|
@_filters_map = \
|
202
203
|
case definition
|
203
204
|
when Hash
|
@@ -211,7 +212,9 @@ module Praxis::Mapper
|
|
211
212
|
|
212
213
|
def self.craft_filter_query(base_query, filters:) # rubocop:disable Metrics/AbcSize
|
213
214
|
if filters
|
214
|
-
|
215
|
+
unless @_filters_map
|
216
|
+
raise "To use API filtering, you must define the mapping of api-names to resource properties (using the `filters_mapping` method in #{self})"
|
217
|
+
end
|
215
218
|
debug = Praxis::Application.instance.config.mapper.debug_queries
|
216
219
|
base_query = model._filter_query_builder_class.new(query: base_query, model: model, filters_map: @_filters_map, debug: debug).generate(filters)
|
217
220
|
end
|
@@ -228,6 +231,19 @@ module Praxis::Mapper
|
|
228
231
|
base_query
|
229
232
|
end
|
230
233
|
|
234
|
+
def self.craft_pagination_query(base_query, pagination: ) # rubocop:disable Metrics/AbcSize
|
235
|
+
handler_klass = model._pagination_query_builder_class
|
236
|
+
return base_query unless (handler_klass && (pagination.paginator || pagination.order))
|
237
|
+
|
238
|
+
# Gather and save the count if required
|
239
|
+
if pagination.paginator&.total_count
|
240
|
+
pagination.total_count = handler_klass.count(base_query.dup)
|
241
|
+
end
|
242
|
+
|
243
|
+
base_query = handler_klass.order(base_query, pagination.order)
|
244
|
+
handler_klass.paginate(base_query, pagination)
|
245
|
+
end
|
246
|
+
|
231
247
|
def initialize(record)
|
232
248
|
@record = record
|
233
249
|
end
|
@@ -7,6 +7,9 @@ module Praxis::Mapper
|
|
7
7
|
|
8
8
|
included do
|
9
9
|
attr_accessor :_resource
|
10
|
+
class <<self
|
11
|
+
alias_method :find_by, :find # Easy way to be method compatible with AR
|
12
|
+
end
|
10
13
|
end
|
11
14
|
|
12
15
|
module ClassMethods
|
@@ -19,6 +22,10 @@ module Praxis::Mapper
|
|
19
22
|
Praxis::Extensions::FieldSelection::SequelQuerySelector
|
20
23
|
end
|
21
24
|
|
25
|
+
def _pagination_query_builder_class
|
26
|
+
Praxis::Extensions::Pagination::SequelPaginationHandler
|
27
|
+
end
|
28
|
+
|
22
29
|
def _praxis_associations
|
23
30
|
orig = self.association_reflections.clone
|
24
31
|
orig.each do |k,v|
|