rom-ldap 0.2.2
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/CHANGELOG.md +251 -0
- data/CONTRIBUTING.md +18 -0
- data/README.md +172 -0
- data/TODO.md +33 -0
- data/config/responses.yml +328 -0
- data/lib/dry/monitor/ldap/colorizers/default.rb +17 -0
- data/lib/dry/monitor/ldap/colorizers/rouge.rb +31 -0
- data/lib/dry/monitor/ldap/logger.rb +58 -0
- data/lib/rom-ldap.rb +1 -0
- data/lib/rom/ldap.rb +22 -0
- data/lib/rom/ldap/alias.rb +30 -0
- data/lib/rom/ldap/associations.rb +6 -0
- data/lib/rom/ldap/associations/core.rb +23 -0
- data/lib/rom/ldap/associations/many_to_many.rb +18 -0
- data/lib/rom/ldap/associations/many_to_one.rb +22 -0
- data/lib/rom/ldap/associations/one_to_many.rb +32 -0
- data/lib/rom/ldap/associations/one_to_one.rb +19 -0
- data/lib/rom/ldap/associations/self_ref.rb +35 -0
- data/lib/rom/ldap/attribute.rb +327 -0
- data/lib/rom/ldap/client.rb +185 -0
- data/lib/rom/ldap/client/authentication.rb +118 -0
- data/lib/rom/ldap/client/operations.rb +233 -0
- data/lib/rom/ldap/commands.rb +6 -0
- data/lib/rom/ldap/commands/create.rb +41 -0
- data/lib/rom/ldap/commands/delete.rb +17 -0
- data/lib/rom/ldap/commands/update.rb +35 -0
- data/lib/rom/ldap/constants.rb +193 -0
- data/lib/rom/ldap/dataset.rb +286 -0
- data/lib/rom/ldap/dataset/conversion.rb +62 -0
- data/lib/rom/ldap/dataset/dsl.rb +299 -0
- data/lib/rom/ldap/dataset/persistence.rb +44 -0
- data/lib/rom/ldap/directory.rb +126 -0
- data/lib/rom/ldap/directory/capabilities.rb +71 -0
- data/lib/rom/ldap/directory/entry.rb +200 -0
- data/lib/rom/ldap/directory/env.rb +155 -0
- data/lib/rom/ldap/directory/operations.rb +282 -0
- data/lib/rom/ldap/directory/password.rb +122 -0
- data/lib/rom/ldap/directory/root.rb +187 -0
- data/lib/rom/ldap/directory/tokenization.rb +66 -0
- data/lib/rom/ldap/directory/transactions.rb +31 -0
- data/lib/rom/ldap/directory/vendors/active_directory.rb +129 -0
- data/lib/rom/ldap/directory/vendors/apache_ds.rb +27 -0
- data/lib/rom/ldap/directory/vendors/e_directory.rb +16 -0
- data/lib/rom/ldap/directory/vendors/open_directory.rb +12 -0
- data/lib/rom/ldap/directory/vendors/open_dj.rb +25 -0
- data/lib/rom/ldap/directory/vendors/open_ldap.rb +35 -0
- data/lib/rom/ldap/directory/vendors/three_eight_nine.rb +16 -0
- data/lib/rom/ldap/directory/vendors/unknown.rb +22 -0
- data/lib/rom/ldap/dsl.rb +76 -0
- data/lib/rom/ldap/errors.rb +47 -0
- data/lib/rom/ldap/expression.rb +77 -0
- data/lib/rom/ldap/expression_encoder.rb +174 -0
- data/lib/rom/ldap/extensions.rb +50 -0
- data/lib/rom/ldap/extensions/active_support_notifications.rb +26 -0
- data/lib/rom/ldap/extensions/compatibility.rb +11 -0
- data/lib/rom/ldap/extensions/dsml.rb +165 -0
- data/lib/rom/ldap/extensions/msgpack.rb +23 -0
- data/lib/rom/ldap/extensions/optimised_json.rb +25 -0
- data/lib/rom/ldap/extensions/rails_log_subscriber.rb +38 -0
- data/lib/rom/ldap/formatter.rb +26 -0
- data/lib/rom/ldap/functions.rb +207 -0
- data/lib/rom/ldap/gateway.rb +145 -0
- data/lib/rom/ldap/ldif.rb +74 -0
- data/lib/rom/ldap/ldif/exporter.rb +77 -0
- data/lib/rom/ldap/ldif/importer.rb +95 -0
- data/lib/rom/ldap/mapper_compiler.rb +19 -0
- data/lib/rom/ldap/matchers.rb +69 -0
- data/lib/rom/ldap/message_queue.rb +7 -0
- data/lib/rom/ldap/oid.rb +101 -0
- data/lib/rom/ldap/parsers/abstract_syntax.rb +91 -0
- data/lib/rom/ldap/parsers/attribute.rb +290 -0
- data/lib/rom/ldap/parsers/filter_syntax.rb +133 -0
- data/lib/rom/ldap/pdu.rb +285 -0
- data/lib/rom/ldap/plugin/pagination.rb +145 -0
- data/lib/rom/ldap/plugins.rb +7 -0
- data/lib/rom/ldap/projection_dsl.rb +38 -0
- data/lib/rom/ldap/relation.rb +135 -0
- data/lib/rom/ldap/relation/exporting.rb +72 -0
- data/lib/rom/ldap/relation/reading.rb +461 -0
- data/lib/rom/ldap/relation/writing.rb +64 -0
- data/lib/rom/ldap/responses.rb +17 -0
- data/lib/rom/ldap/restriction_dsl.rb +45 -0
- data/lib/rom/ldap/schema.rb +123 -0
- data/lib/rom/ldap/schema/attributes_inferrer.rb +59 -0
- data/lib/rom/ldap/schema/dsl.rb +13 -0
- data/lib/rom/ldap/schema/inferrer.rb +50 -0
- data/lib/rom/ldap/schema/type_builder.rb +133 -0
- data/lib/rom/ldap/scope.rb +19 -0
- data/lib/rom/ldap/search_request.rb +249 -0
- data/lib/rom/ldap/socket.rb +210 -0
- data/lib/rom/ldap/tasks/ldap.rake +103 -0
- data/lib/rom/ldap/tasks/ldif.rake +80 -0
- data/lib/rom/ldap/transaction.rb +29 -0
- data/lib/rom/ldap/type_map.rb +88 -0
- data/lib/rom/ldap/types.rb +158 -0
- data/lib/rom/ldap/version.rb +17 -0
- data/lib/rom/plugins/relation/ldap/active_directory.rb +182 -0
- data/lib/rom/plugins/relation/ldap/auto_restrictions.rb +69 -0
- data/lib/rom/plugins/relation/ldap/e_directory.rb +27 -0
- data/lib/rom/plugins/relation/ldap/instrumentation.rb +35 -0
- data/lib/rouge/lexers/ldap.rb +72 -0
- data/lib/rouge/themes/ldap.rb +49 -0
- metadata +231 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'strscan'
|
|
4
|
+
|
|
5
|
+
module ROM
|
|
6
|
+
module LDAP
|
|
7
|
+
module Parsers
|
|
8
|
+
#
|
|
9
|
+
# Parses a filter into an AST
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
class FilterSyntax
|
|
13
|
+
|
|
14
|
+
extend Initializer
|
|
15
|
+
|
|
16
|
+
param :filter, type: Types::Filter
|
|
17
|
+
|
|
18
|
+
param :attributes, type: Types.Array(Types::Hash)
|
|
19
|
+
|
|
20
|
+
attr_accessor :result
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
if open_statement
|
|
24
|
+
if joined_statement
|
|
25
|
+
join_type = encode_constructor
|
|
26
|
+
|
|
27
|
+
branches = []
|
|
28
|
+
|
|
29
|
+
while (branch = call)
|
|
30
|
+
branches << branch
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
self.result = [join_type, branches]
|
|
34
|
+
|
|
35
|
+
elsif negated_statement
|
|
36
|
+
negator = encode_constructor
|
|
37
|
+
self.result = [negator, call]
|
|
38
|
+
|
|
39
|
+
else
|
|
40
|
+
skip_whitespace
|
|
41
|
+
scan_attribute
|
|
42
|
+
attribute = encode_attribute
|
|
43
|
+
|
|
44
|
+
skip_whitespace
|
|
45
|
+
scan_operator
|
|
46
|
+
op = encode_operator
|
|
47
|
+
|
|
48
|
+
skip_whitespace
|
|
49
|
+
scan_value
|
|
50
|
+
value = encode_value
|
|
51
|
+
|
|
52
|
+
self.result = [op, attribute, value]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
close_statement
|
|
56
|
+
|
|
57
|
+
result
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# @return [StringScanner]
|
|
64
|
+
#
|
|
65
|
+
def scanner
|
|
66
|
+
@scanner ||= StringScanner.new(filter)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def skip_whitespace
|
|
70
|
+
scanner.scan(/\s*/)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def close_statement
|
|
74
|
+
scanner.scan(/\s*\)\s*/)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def open_statement
|
|
78
|
+
scanner.scan(/\s*\(\s*/)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @return [String, NilClass] "!"
|
|
82
|
+
#
|
|
83
|
+
def negated_statement
|
|
84
|
+
scanner.scan(/\s*\!\s*/)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [String, NilClass] "&" or "|"
|
|
88
|
+
#
|
|
89
|
+
def joined_statement
|
|
90
|
+
scanner.scan(CONSTRUCTOR_REGEX)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def scan_value
|
|
94
|
+
scanner.scan(VAL_REGEX)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def scan_operator
|
|
98
|
+
scanner.scan(OPERATOR_REGEX)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def scan_attribute
|
|
102
|
+
scanner.scan(/[-\w:.]*[\w]/)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [String,Symbol] formatted
|
|
106
|
+
#
|
|
107
|
+
def encode_attribute
|
|
108
|
+
attr = attributes.find { |a| a[:canonical].eql?(scanner.matched) }
|
|
109
|
+
attr ? attr[:name] : scanner.matched
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @return [Mixed]
|
|
113
|
+
#
|
|
114
|
+
def encode_value
|
|
115
|
+
Functions[:identify_value].call(scanner.matched)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @return [Symbol]
|
|
119
|
+
#
|
|
120
|
+
def encode_constructor
|
|
121
|
+
CONSTRUCTORS.invert[scanner.matched]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @return [Symbol]
|
|
125
|
+
#
|
|
126
|
+
def encode_operator
|
|
127
|
+
OPERATORS.invert[scanner.matched]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
data/lib/rom/ldap/pdu.rb
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ostruct'
|
|
4
|
+
require 'dry/core/class_attributes'
|
|
5
|
+
require 'rom/initializer'
|
|
6
|
+
|
|
7
|
+
module ROM
|
|
8
|
+
module LDAP
|
|
9
|
+
# @see PDU
|
|
10
|
+
#
|
|
11
|
+
# @return [Array<Symbol>]
|
|
12
|
+
#
|
|
13
|
+
SUCCESS_CODES = %i[
|
|
14
|
+
success
|
|
15
|
+
time_limit_exceeded
|
|
16
|
+
size_limit_exceeded
|
|
17
|
+
compare_true
|
|
18
|
+
compare_false
|
|
19
|
+
referral
|
|
20
|
+
sasl_bind_in_progress
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
# LDAP Message Protocol Data Unit (PDU)
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
class PDU
|
|
27
|
+
|
|
28
|
+
extend Initializer
|
|
29
|
+
|
|
30
|
+
param :message_id, proc(&:to_i), reader: true, type: Types::Strict::Integer
|
|
31
|
+
|
|
32
|
+
# @!attribute [r] tag
|
|
33
|
+
# @return [Array]
|
|
34
|
+
param :tag, reader: :private, type: Types::Strict::Array
|
|
35
|
+
|
|
36
|
+
param :ctrls, reader: :private, type: Types::Strict::Array, default: -> { EMPTY_ARRAY }
|
|
37
|
+
|
|
38
|
+
# @return [Integer]
|
|
39
|
+
#
|
|
40
|
+
def app_tag
|
|
41
|
+
tag.ber_identifier & 0x1f
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Integer]
|
|
45
|
+
#
|
|
46
|
+
def result_code
|
|
47
|
+
tag[0]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# @return [String]
|
|
52
|
+
#
|
|
53
|
+
def matched_dn
|
|
54
|
+
tag[1]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @example
|
|
58
|
+
# "Matchingrule is required for sorting by the attribute cn"
|
|
59
|
+
#
|
|
60
|
+
# @return [String]
|
|
61
|
+
#
|
|
62
|
+
def advice
|
|
63
|
+
tag[2]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def search_referrals
|
|
67
|
+
return unless search_referral?
|
|
68
|
+
|
|
69
|
+
tag[3] || EMPTY_ARRAY
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def result_server_sasl_creds
|
|
73
|
+
return unless bind_result? && gteq4?
|
|
74
|
+
|
|
75
|
+
tag[3]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [Array] => [version, username, password]
|
|
79
|
+
#
|
|
80
|
+
#
|
|
81
|
+
def bind_result
|
|
82
|
+
return unless bind_result?
|
|
83
|
+
raise ResponseTypeInvalidError, 'Invalid bind_result' unless gteq_3?
|
|
84
|
+
|
|
85
|
+
tag
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
#
|
|
89
|
+
# @return [OpenStruct] => :version, :name, :authentication
|
|
90
|
+
#
|
|
91
|
+
def bind_parameters
|
|
92
|
+
return unless bind_request?
|
|
93
|
+
|
|
94
|
+
s = ::OpenStruct.new
|
|
95
|
+
s.version, s.name, s.authentication = tag
|
|
96
|
+
s
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# @return [OpenStruct, NilClass] => :version, :name, :authentication
|
|
101
|
+
#
|
|
102
|
+
def search_parameters
|
|
103
|
+
return unless search_request?
|
|
104
|
+
|
|
105
|
+
s = ::OpenStruct.new
|
|
106
|
+
s.base_object,
|
|
107
|
+
s.scope,
|
|
108
|
+
s.deref_aliases,
|
|
109
|
+
s.size_limit,
|
|
110
|
+
s.time_limit,
|
|
111
|
+
s.types_only,
|
|
112
|
+
s.filter,
|
|
113
|
+
s.attributes = tag
|
|
114
|
+
s
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Message
|
|
118
|
+
#
|
|
119
|
+
# @example => "No attribute with the name false exists in the server's schema"
|
|
120
|
+
#
|
|
121
|
+
# @return [String, NilClass]
|
|
122
|
+
#
|
|
123
|
+
def extended_response
|
|
124
|
+
raise ResponseTypeInvalidError, 'Invalid extended_response' unless gteq_3?
|
|
125
|
+
|
|
126
|
+
tag[3] if extended_response?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# ["dn", [ entry... ]]
|
|
130
|
+
#
|
|
131
|
+
# @return [Array, NilClass]
|
|
132
|
+
#
|
|
133
|
+
def search_entry
|
|
134
|
+
raise ResponseTypeInvalidError, 'Invalid search_entry' unless gteq_2?
|
|
135
|
+
|
|
136
|
+
tag if search_result?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# RFC-2251, an LDAP 'control' is a sequence of tuples, each consisting of
|
|
140
|
+
# - an OID
|
|
141
|
+
# - a boolean criticality flag defaulting FALSE
|
|
142
|
+
# - and an optional Octet String
|
|
143
|
+
#
|
|
144
|
+
# If only two fields are given, the second one may be either criticality
|
|
145
|
+
# or data, since criticality has a default value. RFC-2696 is a good example.
|
|
146
|
+
#
|
|
147
|
+
# @see Connection::Read#search
|
|
148
|
+
#
|
|
149
|
+
# @return [Array<OpenStruct>] => :oid, :criticality, :value
|
|
150
|
+
#
|
|
151
|
+
def result_controls
|
|
152
|
+
ctrls.map do |control|
|
|
153
|
+
oid, level, value = control
|
|
154
|
+
value, level = level, false if level&.is_a?(String)
|
|
155
|
+
::OpenStruct.new(oid: oid, criticality: level, value: value)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# logging ======================= #
|
|
160
|
+
|
|
161
|
+
# First item from responses.yaml
|
|
162
|
+
#
|
|
163
|
+
# @return [String]
|
|
164
|
+
#
|
|
165
|
+
def message
|
|
166
|
+
detailed_response[0]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Grep for error message
|
|
170
|
+
#
|
|
171
|
+
# @return [String, FalseClass]
|
|
172
|
+
#
|
|
173
|
+
def error_message
|
|
174
|
+
tag[3][/comment: (.*), data/, 1] if tag[3]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @return [String]
|
|
178
|
+
#
|
|
179
|
+
def info
|
|
180
|
+
detailed_response[1]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# @return [String]
|
|
184
|
+
#
|
|
185
|
+
def flag
|
|
186
|
+
detailed_response[2]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# predicates ======================= #
|
|
190
|
+
|
|
191
|
+
# @return [Boolean]
|
|
192
|
+
#
|
|
193
|
+
def add_response?
|
|
194
|
+
pdu_type == :add_response
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @see https://tools.ietf.org/html/rfc4511#section-4.2.1
|
|
198
|
+
#
|
|
199
|
+
# @return [Boolean]
|
|
200
|
+
#
|
|
201
|
+
def bind_request?
|
|
202
|
+
pdu_type == :bind_request
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# A successful operation is indicated by a BindResponse with a resultCode set to success.
|
|
206
|
+
#
|
|
207
|
+
# @see https://tools.ietf.org/html/rfc4511#section-4.2.2
|
|
208
|
+
#
|
|
209
|
+
# @return [Boolean]
|
|
210
|
+
#
|
|
211
|
+
def bind_result?
|
|
212
|
+
pdu_type.eql?(:bind_result)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# @return [Boolean]
|
|
216
|
+
#
|
|
217
|
+
def search_request?
|
|
218
|
+
pdu_type.eql?(:search_request)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @return [Boolean]
|
|
222
|
+
#
|
|
223
|
+
def search_result?
|
|
224
|
+
pdu_type.eql?(:search_returned_data)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @return [Boolean]
|
|
228
|
+
#
|
|
229
|
+
def search_referral?
|
|
230
|
+
pdu_type == :search_result_referral
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# @return [Boolean]
|
|
234
|
+
#
|
|
235
|
+
def extended_response?
|
|
236
|
+
pdu_type == :extended_response
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# @return [Boolean]
|
|
240
|
+
#
|
|
241
|
+
def success?
|
|
242
|
+
SUCCESS_CODES.include?(result_code_symbol)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @return [Boolean]
|
|
246
|
+
#
|
|
247
|
+
def failure?
|
|
248
|
+
!success?
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# conversion ======================= #
|
|
252
|
+
|
|
253
|
+
# @return [Symbol]
|
|
254
|
+
#
|
|
255
|
+
def pdu_type
|
|
256
|
+
BER.fetch(:response, app_tag) || raise(ResponseTypeInvalidError, "Unknown pdu_type: #{app_tag}")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# @return [Symbol]
|
|
260
|
+
#
|
|
261
|
+
def result_code_symbol
|
|
262
|
+
BER.fetch(:result, result_code)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private
|
|
266
|
+
|
|
267
|
+
def detailed_response
|
|
268
|
+
RESPONSES[result_code_symbol]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# @return [Boolean]
|
|
272
|
+
#
|
|
273
|
+
def gteq_2?
|
|
274
|
+
tag.length >= 2
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# @return [Boolean]
|
|
278
|
+
#
|
|
279
|
+
def gteq_3?
|
|
280
|
+
tag.length >= 3
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rom/initializer'
|
|
4
|
+
require 'dry/equalizer'
|
|
5
|
+
|
|
6
|
+
module ROM
|
|
7
|
+
module LDAP
|
|
8
|
+
module Plugin
|
|
9
|
+
# Pagination plugin for Relations
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
module Pagination
|
|
13
|
+
# Pager object provides the underlying pagination API for relations
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
16
|
+
class Pager
|
|
17
|
+
|
|
18
|
+
extend Initializer
|
|
19
|
+
|
|
20
|
+
# @!parse include Dry::Equalizer
|
|
21
|
+
include Dry::Equalizer(:dataset, :options)
|
|
22
|
+
|
|
23
|
+
# @!attribute [r] dataset
|
|
24
|
+
# @return [LDAP::Dataset] Relation's dataset
|
|
25
|
+
param :dataset
|
|
26
|
+
|
|
27
|
+
# @!attribute [r] current_page
|
|
28
|
+
# @return [Integer] Current page number
|
|
29
|
+
option :current_page, default: -> { 1 }
|
|
30
|
+
|
|
31
|
+
# @!attribute [r] per_page
|
|
32
|
+
# @return [Integer] Current per-page number
|
|
33
|
+
option :per_page
|
|
34
|
+
|
|
35
|
+
# Return next page number
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# users.page(2).pager.next_page
|
|
39
|
+
# # => 3
|
|
40
|
+
#
|
|
41
|
+
# @return [Integer]
|
|
42
|
+
#
|
|
43
|
+
# @api public
|
|
44
|
+
def next_page
|
|
45
|
+
num = current_page + 1
|
|
46
|
+
num if total_pages >= num
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Return previous page number
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# users.page(2).pager.prev_page
|
|
53
|
+
# # => 1
|
|
54
|
+
#
|
|
55
|
+
# @return [Integer]
|
|
56
|
+
#
|
|
57
|
+
# @api public
|
|
58
|
+
def prev_page
|
|
59
|
+
num = current_page - 1
|
|
60
|
+
num if num.positive?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Return total number of tuples
|
|
64
|
+
#
|
|
65
|
+
# @return [Integer]
|
|
66
|
+
#
|
|
67
|
+
# @api public
|
|
68
|
+
def total
|
|
69
|
+
dataset.total
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Return total number of pages
|
|
73
|
+
#
|
|
74
|
+
# @return [Integer]
|
|
75
|
+
#
|
|
76
|
+
# @api public
|
|
77
|
+
def total_pages
|
|
78
|
+
(total / per_page.to_f).ceil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @api private
|
|
82
|
+
def at(dataset, current_page, per_page = self.per_page)
|
|
83
|
+
current_page = current_page.to_i
|
|
84
|
+
per_page = per_page.to_i
|
|
85
|
+
offset = (current_page - 1) * per_page
|
|
86
|
+
|
|
87
|
+
self.class.new(
|
|
88
|
+
dataset.with(offset: offset, limit: per_page),
|
|
89
|
+
current_page: current_page, per_page: per_page
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
alias_method :limit_value, :per_page
|
|
94
|
+
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @api private
|
|
98
|
+
def self.included(klass)
|
|
99
|
+
super
|
|
100
|
+
|
|
101
|
+
klass.class_eval do
|
|
102
|
+
defines :per_page
|
|
103
|
+
|
|
104
|
+
option :pager, default: -> {
|
|
105
|
+
Pager.new(dataset, per_page: self.class.per_page)
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Paginate a relation
|
|
111
|
+
#
|
|
112
|
+
# @example
|
|
113
|
+
# users.page(1)
|
|
114
|
+
# users.pager # => info about pagination
|
|
115
|
+
#
|
|
116
|
+
# @return [Relation]
|
|
117
|
+
#
|
|
118
|
+
# @api public
|
|
119
|
+
def page(num)
|
|
120
|
+
next_pager = pager.at(dataset, num)
|
|
121
|
+
new(next_pager.dataset, pager: next_pager)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Set limit for pagination
|
|
125
|
+
#
|
|
126
|
+
# @example
|
|
127
|
+
# users.per_page(10).page(2)
|
|
128
|
+
#
|
|
129
|
+
# @return [Relation]
|
|
130
|
+
#
|
|
131
|
+
# @api public
|
|
132
|
+
def per_page(num)
|
|
133
|
+
next_pager = pager.at(dataset, pager.current_page, num)
|
|
134
|
+
new(next_pager.dataset, pager: next_pager)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
ROM.plugins do
|
|
142
|
+
adapter :ldap do
|
|
143
|
+
register :pagination, ROM::LDAP::Plugin::Pagination, type: :relation
|
|
144
|
+
end
|
|
145
|
+
end
|