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,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rom/plugins/relation/ldap/instrumentation'
|
|
4
|
+
require 'rom/plugins/relation/ldap/auto_restrictions'
|
|
5
|
+
require 'rom/plugins/relation/ldap/active_directory'
|
|
6
|
+
require 'rom/plugins/relation/ldap/e_directory'
|
|
7
|
+
require 'rom/ldap/plugin/pagination'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rom/ldap/dsl'
|
|
4
|
+
|
|
5
|
+
module ROM
|
|
6
|
+
module LDAP
|
|
7
|
+
# Projection DSL used in reading API (`select`, `select_append` etc.)
|
|
8
|
+
#
|
|
9
|
+
# @see LDAP::Schema#project
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
class ProjectionDSL < DSL
|
|
13
|
+
|
|
14
|
+
# @api private
|
|
15
|
+
def respond_to_missing?(name, include_private = false)
|
|
16
|
+
super || type(name)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# @api private
|
|
22
|
+
def method_missing(meth, *args, &block)
|
|
23
|
+
if schema.key?(meth)
|
|
24
|
+
schema[meth]
|
|
25
|
+
else
|
|
26
|
+
type = type(meth)
|
|
27
|
+
|
|
28
|
+
if type
|
|
29
|
+
::ROM::LDAP::Attribute[type].value(args[0])
|
|
30
|
+
else
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rom/ldap/types'
|
|
4
|
+
require 'rom/ldap/schema'
|
|
5
|
+
require 'rom/ldap/dataset'
|
|
6
|
+
require 'rom/ldap/attribute'
|
|
7
|
+
require 'rom/ldap/relation/reading'
|
|
8
|
+
require 'rom/ldap/relation/writing'
|
|
9
|
+
require 'rom/ldap/relation/exporting'
|
|
10
|
+
require 'rom/ldap/transaction'
|
|
11
|
+
|
|
12
|
+
module ROM
|
|
13
|
+
module LDAP
|
|
14
|
+
class Relation < ROM::Relation
|
|
15
|
+
|
|
16
|
+
adapter :ldap
|
|
17
|
+
|
|
18
|
+
include LDAP
|
|
19
|
+
include Reading
|
|
20
|
+
include Writing
|
|
21
|
+
include Exporting
|
|
22
|
+
|
|
23
|
+
# @!method self.base
|
|
24
|
+
# Per relation override for the search base.
|
|
25
|
+
#
|
|
26
|
+
# @overload base
|
|
27
|
+
# Return search base value
|
|
28
|
+
# @return [String]
|
|
29
|
+
#
|
|
30
|
+
# @overload base(value)
|
|
31
|
+
# Set search base value
|
|
32
|
+
defines :base
|
|
33
|
+
|
|
34
|
+
# @!method self.branches
|
|
35
|
+
# Alternative search bases by name.
|
|
36
|
+
#
|
|
37
|
+
# @overload branches
|
|
38
|
+
# Return hash of search branches.
|
|
39
|
+
# @return [Hash]
|
|
40
|
+
#
|
|
41
|
+
# @overload branches(value)
|
|
42
|
+
# Set hash of search branches.
|
|
43
|
+
defines :branches
|
|
44
|
+
branches EMPTY_HASH
|
|
45
|
+
|
|
46
|
+
extend Notifications::Listener
|
|
47
|
+
|
|
48
|
+
subscribe('configuration.relations.schema.set', adapter: :ldap) do |event|
|
|
49
|
+
relation = event[:relation]
|
|
50
|
+
relation.dataset do
|
|
51
|
+
# @return [Dataset]
|
|
52
|
+
#
|
|
53
|
+
# @override Dataset#base
|
|
54
|
+
#
|
|
55
|
+
# Set dataset search base using either class-level value or gateway config.
|
|
56
|
+
with(base: relation.base || directory.base)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
schema_class LDAP::Schema
|
|
61
|
+
schema_attr_class LDAP::Attribute
|
|
62
|
+
schema_inferrer LDAP::Schema::Inferrer.new.freeze
|
|
63
|
+
schema_dsl LDAP::Schema::DSL
|
|
64
|
+
|
|
65
|
+
forward(*Dataset.dsl)
|
|
66
|
+
|
|
67
|
+
# Fallsback to 'entrydn' operational value.
|
|
68
|
+
#
|
|
69
|
+
# @return [Symbol]
|
|
70
|
+
#
|
|
71
|
+
# @api public
|
|
72
|
+
def primary_key
|
|
73
|
+
attribute = schema.find(&:primary_key?)
|
|
74
|
+
|
|
75
|
+
if attribute
|
|
76
|
+
attribute.alias || attribute.name
|
|
77
|
+
else
|
|
78
|
+
DEFAULT_PK
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Expose the search base currently in use.
|
|
83
|
+
#
|
|
84
|
+
# @return [String] current base
|
|
85
|
+
#
|
|
86
|
+
# @api public
|
|
87
|
+
def base
|
|
88
|
+
dataset.opts[:base]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Current dataset in LDAP filter format.
|
|
92
|
+
#
|
|
93
|
+
# @return [String]
|
|
94
|
+
#
|
|
95
|
+
# @api public
|
|
96
|
+
def to_filter
|
|
97
|
+
dataset.opts[:filter]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @api public
|
|
101
|
+
def self.associations
|
|
102
|
+
schema.associations
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [Relation]
|
|
106
|
+
#
|
|
107
|
+
# @api public
|
|
108
|
+
def assoc(name)
|
|
109
|
+
associations[name].call
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# LDAP Transactions (LDAPTXN) is an experimental RFC.
|
|
113
|
+
# The latest revision can be found at http://tools.ietf.org/rfc/rfc5805.txt
|
|
114
|
+
#
|
|
115
|
+
# @see https://directory.fedoraproject.org/docs/389ds/design/ldap-transactions.html
|
|
116
|
+
#
|
|
117
|
+
# @yield [t] Transaction
|
|
118
|
+
#
|
|
119
|
+
# @return [Mixed]
|
|
120
|
+
#
|
|
121
|
+
# @api public
|
|
122
|
+
def transaction(opts = EMPTY_OPTS, &block)
|
|
123
|
+
Transaction.new(dataset.directory).run(opts, &block)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
# @api private
|
|
128
|
+
# def join(source_table, join_keys)
|
|
129
|
+
# binding.pry
|
|
130
|
+
# __registry__[source_table].where(join_keys)
|
|
131
|
+
# end
|
|
132
|
+
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'rom/ldap/ldif'
|
|
6
|
+
|
|
7
|
+
module ROM
|
|
8
|
+
module LDAP
|
|
9
|
+
class Relation < ROM::Relation
|
|
10
|
+
|
|
11
|
+
# LDIF, JSON, YAML and if loading extensions MsgPack and DSML.
|
|
12
|
+
#
|
|
13
|
+
module Exporting
|
|
14
|
+
using LDIF
|
|
15
|
+
|
|
16
|
+
# Export the relation as LDIF
|
|
17
|
+
#
|
|
18
|
+
# @return [String]
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# relation.to_ldif
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
def to_ldif
|
|
25
|
+
export.to_ldif
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Export the relation as JSON
|
|
29
|
+
#
|
|
30
|
+
# @param _opts [Mixed] compatibility with JSON.generate
|
|
31
|
+
#
|
|
32
|
+
# @return [String]
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# relation.to_json
|
|
36
|
+
# JSON.generate(relation)
|
|
37
|
+
#
|
|
38
|
+
# @api public
|
|
39
|
+
def to_json(_opts = nil)
|
|
40
|
+
export.to_json
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Export the relation as YAML
|
|
44
|
+
#
|
|
45
|
+
# @return [String]
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# relation.to_yaml
|
|
49
|
+
#
|
|
50
|
+
# @api public
|
|
51
|
+
def to_yaml
|
|
52
|
+
export.to_yaml
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Serialize the selected dataset attributes in a formatted string.
|
|
58
|
+
#
|
|
59
|
+
# @example i.e. YAML, JSON, LDIF, BINARY
|
|
60
|
+
# #=> relation.export.to_format
|
|
61
|
+
#
|
|
62
|
+
# @return [Hash, Array<Hash>]
|
|
63
|
+
#
|
|
64
|
+
# @api public
|
|
65
|
+
def export
|
|
66
|
+
dataset.respond_to?(:export) ? dataset.export : dataset
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
module LDAP
|
|
5
|
+
class Relation < ROM::Relation
|
|
6
|
+
|
|
7
|
+
module Reading
|
|
8
|
+
# Specify an alternative search base.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# relation.with_base("cn=department,ou=users,dc=org")
|
|
12
|
+
#
|
|
13
|
+
# @return [Relation] Defaults to class attribute
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
16
|
+
def with_base(alt_base)
|
|
17
|
+
new(dataset.with(base: alt_base))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Change the search base to search the whole directory tree.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# relation.whole_tree
|
|
24
|
+
#
|
|
25
|
+
# @return [Relation]
|
|
26
|
+
#
|
|
27
|
+
# @api public
|
|
28
|
+
def whole_tree
|
|
29
|
+
with_base(EMPTY_STRING)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# An alternative search base selected from a class level hash.
|
|
33
|
+
#
|
|
34
|
+
# @param key [Symbol]
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# Relation.branches { custom: '(attribute=value)' }
|
|
38
|
+
#
|
|
39
|
+
# relation.branch(:custom)
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
42
|
+
def branch(key)
|
|
43
|
+
with_base(self.class.branches[key])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Remove additional search criteria and return to initial filter.
|
|
47
|
+
#
|
|
48
|
+
# @return [Relation]
|
|
49
|
+
#
|
|
50
|
+
# @api public
|
|
51
|
+
def unfiltered
|
|
52
|
+
new(dataset.unfiltered)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Include internal operational attributes in the tuples.
|
|
56
|
+
#
|
|
57
|
+
# @return [Relation]
|
|
58
|
+
#
|
|
59
|
+
# @api public
|
|
60
|
+
def operational
|
|
61
|
+
new(dataset.with(attrs: ALL_ATTRS))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Replace the relation filter with a new query.
|
|
65
|
+
#
|
|
66
|
+
# @param new_filter [String] Valid LDAP filter string
|
|
67
|
+
#
|
|
68
|
+
# @return [Relation]
|
|
69
|
+
#
|
|
70
|
+
# @api public
|
|
71
|
+
def search(new_filter)
|
|
72
|
+
new(dataset.with(name: new_filter))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns True if the filtered entity can bind.
|
|
76
|
+
#
|
|
77
|
+
# @return [Boolean]
|
|
78
|
+
#
|
|
79
|
+
# @api public
|
|
80
|
+
def authenticate(password)
|
|
81
|
+
dataset.bind(password)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Map tuples from the relation
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# users.map { |user| user[:id] }
|
|
88
|
+
# # => [1, 2, 3]
|
|
89
|
+
#
|
|
90
|
+
# users.map(:id).to_a
|
|
91
|
+
# # => [1, 2, 3]
|
|
92
|
+
#
|
|
93
|
+
# @param key [Symbol] An optional name of the key for extracting values
|
|
94
|
+
# from tuples
|
|
95
|
+
#
|
|
96
|
+
# @return [Array<Array>]
|
|
97
|
+
#
|
|
98
|
+
# @api public
|
|
99
|
+
def map(key = nil, &block)
|
|
100
|
+
dataset.map(key, &block)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Array of values for an :attribute from all tuples.
|
|
104
|
+
#
|
|
105
|
+
# @param field [Symbol] formatted or canonical attribute key
|
|
106
|
+
#
|
|
107
|
+
# @example
|
|
108
|
+
# relation.by_sn('Hamilton').list(:given_name)
|
|
109
|
+
#
|
|
110
|
+
# @return [Array<Mixed>]
|
|
111
|
+
#
|
|
112
|
+
# @raise [ROM::Struct::MissingAttribute] If auto_struct? and field not present.
|
|
113
|
+
#
|
|
114
|
+
# @api public
|
|
115
|
+
def list(field)
|
|
116
|
+
if auto_struct?
|
|
117
|
+
to_a.flat_map(&field)
|
|
118
|
+
else
|
|
119
|
+
map(field).to_a.compact.flatten
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Count the number of entries selected from the paginated dataset.
|
|
124
|
+
#
|
|
125
|
+
# @return [Integer]
|
|
126
|
+
#
|
|
127
|
+
# @api public
|
|
128
|
+
def count
|
|
129
|
+
dataset.__send__(__method__)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Count the number of entries in the dataset.
|
|
133
|
+
#
|
|
134
|
+
# @return [Integer]
|
|
135
|
+
#
|
|
136
|
+
# @api public
|
|
137
|
+
def total
|
|
138
|
+
dataset.__send__(__method__)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @return [Boolean]
|
|
142
|
+
#
|
|
143
|
+
# @api public
|
|
144
|
+
def one?
|
|
145
|
+
dataset.__send__(__method__)
|
|
146
|
+
end
|
|
147
|
+
alias_method :distinct?, :one?
|
|
148
|
+
alias_method :unique?, :one?
|
|
149
|
+
|
|
150
|
+
# @return [Boolean]
|
|
151
|
+
#
|
|
152
|
+
# @api public
|
|
153
|
+
def any?(&block)
|
|
154
|
+
dataset.__send__(__method__, &block)
|
|
155
|
+
end
|
|
156
|
+
alias_method :exist?, :any?
|
|
157
|
+
|
|
158
|
+
# @return [Boolean]
|
|
159
|
+
#
|
|
160
|
+
# @api public
|
|
161
|
+
def none?(&block)
|
|
162
|
+
dataset.__send__(__method__, &block)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @return [Boolean]
|
|
166
|
+
#
|
|
167
|
+
# @api public
|
|
168
|
+
def all?(&block)
|
|
169
|
+
dataset.__send__(__method__, &block)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Find tuples by primary_key which defaults to :entry_dn
|
|
173
|
+
# Method is required by commands.
|
|
174
|
+
#
|
|
175
|
+
# @param pks [Integer, String]
|
|
176
|
+
#
|
|
177
|
+
# @example
|
|
178
|
+
# relation.by_pk(1001, 1002, 1003, 1004)
|
|
179
|
+
# relation.by_pk('uid=test1,ou=users,dc=example,dc=com')
|
|
180
|
+
#
|
|
181
|
+
# @return [Relation]
|
|
182
|
+
def by_pk(*pks)
|
|
183
|
+
where(primary_key => pks)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Fetch a tuple identified by the pk
|
|
187
|
+
#
|
|
188
|
+
# @param pk [String, Integer]
|
|
189
|
+
#
|
|
190
|
+
# @example
|
|
191
|
+
# users.fetch(1001) # => {:id => 1, name: "Peter"}
|
|
192
|
+
#
|
|
193
|
+
# @return [Hash]
|
|
194
|
+
#
|
|
195
|
+
# @raise [ROM::TupleCountMismatchError] When 0 or more than 1 tuples were found
|
|
196
|
+
#
|
|
197
|
+
# @api public
|
|
198
|
+
def fetch(*pk)
|
|
199
|
+
by_pk(*pk).one!
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# First tuple from the relation
|
|
203
|
+
#
|
|
204
|
+
# @example
|
|
205
|
+
# relation.where(sn: 'smith').first
|
|
206
|
+
#
|
|
207
|
+
# @return [Hash]
|
|
208
|
+
#
|
|
209
|
+
# @api public
|
|
210
|
+
def first
|
|
211
|
+
dataset.first.to_h
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Last tuple from the relation
|
|
215
|
+
#
|
|
216
|
+
# @example
|
|
217
|
+
# relation.where(sn: 'smith').last
|
|
218
|
+
#
|
|
219
|
+
# @return [Hash]
|
|
220
|
+
#
|
|
221
|
+
# @api public
|
|
222
|
+
def last
|
|
223
|
+
dataset.reverse_each.first.to_h
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Use server-side sorting if available.
|
|
227
|
+
#
|
|
228
|
+
# Orders the dataset by a given attribute using the coerced value.
|
|
229
|
+
#
|
|
230
|
+
# SortResult ::= SEQUENCE {
|
|
231
|
+
# sortResult ENUMERATED {
|
|
232
|
+
# success (0), -- results are sorted
|
|
233
|
+
# operationsError (1), -- server internal failure
|
|
234
|
+
# timeLimitExceeded (3), -- timelimit reached before
|
|
235
|
+
# -- sorting was completed
|
|
236
|
+
# strongAuthRequired (8), -- refused to return sorted
|
|
237
|
+
# -- results via insecure
|
|
238
|
+
# -- protocol
|
|
239
|
+
# adminLimitExceeded (11), -- too many matching entries
|
|
240
|
+
# -- for the server to sort
|
|
241
|
+
# noSuchAttribute (16), -- unrecognized attribute
|
|
242
|
+
# -- type in sort key
|
|
243
|
+
# inappropriateMatching (18), -- unrecognized or inappro-
|
|
244
|
+
# -- priate matching rule in
|
|
245
|
+
# -- sort key
|
|
246
|
+
# insufficientAccessRights (50), -- refused to return sorted
|
|
247
|
+
# -- results to this client
|
|
248
|
+
# busy (51), -- too busy to process
|
|
249
|
+
# unwillingToPerform (53), -- unable to sort
|
|
250
|
+
# other (80)
|
|
251
|
+
# },
|
|
252
|
+
# attributeType [0] AttributeType OPTIONAL }
|
|
253
|
+
#
|
|
254
|
+
# @param attribute [Symbol]
|
|
255
|
+
#
|
|
256
|
+
# @example
|
|
257
|
+
# relation.order(:uid_number).to_a =>
|
|
258
|
+
# [
|
|
259
|
+
# {uid_number: 101},
|
|
260
|
+
# {uid_number: 202},
|
|
261
|
+
# {uid_number: 303}
|
|
262
|
+
# ]
|
|
263
|
+
#
|
|
264
|
+
# @return [Relation]
|
|
265
|
+
#
|
|
266
|
+
# @see https://tools.ietf.org/html/rfc2891
|
|
267
|
+
#
|
|
268
|
+
# @api public
|
|
269
|
+
def order(*attribute)
|
|
270
|
+
new(dataset.with(sort_attrs: attribute))
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Reverses the dataset.
|
|
274
|
+
# Use server-side sorting if available.
|
|
275
|
+
#
|
|
276
|
+
# @example
|
|
277
|
+
# relation.reverse
|
|
278
|
+
#
|
|
279
|
+
# @return [Relation]
|
|
280
|
+
#
|
|
281
|
+
# @api public
|
|
282
|
+
def reverse
|
|
283
|
+
new(dataset.with(direction: :desc))
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Limits the dataset to a number of tuples
|
|
287
|
+
#
|
|
288
|
+
# @example
|
|
289
|
+
# relation.limit(6)
|
|
290
|
+
#
|
|
291
|
+
# @return [Relation]
|
|
292
|
+
#
|
|
293
|
+
# @api public
|
|
294
|
+
def limit(number)
|
|
295
|
+
new(dataset.with(limit: number))
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Shuffles the dataset.
|
|
299
|
+
#
|
|
300
|
+
# @example
|
|
301
|
+
# relation.random
|
|
302
|
+
#
|
|
303
|
+
# @return [Relation]
|
|
304
|
+
#
|
|
305
|
+
# @api public
|
|
306
|
+
def random
|
|
307
|
+
new(dataset.with(random: true))
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Searches attributes of the projected schema for a match.
|
|
311
|
+
#
|
|
312
|
+
# @param value [String]
|
|
313
|
+
#
|
|
314
|
+
# @return [Relation]
|
|
315
|
+
#
|
|
316
|
+
# @example
|
|
317
|
+
# relation.find('eo')
|
|
318
|
+
#
|
|
319
|
+
# @api public
|
|
320
|
+
def grep(value)
|
|
321
|
+
meta_fields = schema.attributes.select { |a| a.meta[:grep] }
|
|
322
|
+
fields = meta_fields.any? ? meta_fields : schema
|
|
323
|
+
|
|
324
|
+
new(dataset.grep(fields.map(&:name).sort, value))
|
|
325
|
+
end
|
|
326
|
+
alias_method :find, :grep
|
|
327
|
+
|
|
328
|
+
# Overwrites forwarding to Dataset#where
|
|
329
|
+
#
|
|
330
|
+
# A Hash argument is passed straight to Dataset#equal.
|
|
331
|
+
# Otherwise the RestrictionDSL builds abstract queries
|
|
332
|
+
#
|
|
333
|
+
# @param args [Array<Array, Hash>] AST queries or an attr/val hash.
|
|
334
|
+
#
|
|
335
|
+
#
|
|
336
|
+
# @example
|
|
337
|
+
# users.where { id.is(1) }
|
|
338
|
+
# users.where { id == 1 }
|
|
339
|
+
# users.where { id > 1 }
|
|
340
|
+
# users.where { id.gte(1) }
|
|
341
|
+
#
|
|
342
|
+
# users.where(users[:id].is(1))
|
|
343
|
+
# users.where(users[:id].lt(1))
|
|
344
|
+
#
|
|
345
|
+
# @api public
|
|
346
|
+
def where(*args, &block)
|
|
347
|
+
if block
|
|
348
|
+
where(args).where(schema.restriction(&block))
|
|
349
|
+
elsif args.size == 1 && args[0].is_a?(Hash)
|
|
350
|
+
new(dataset.equal(args[0]))
|
|
351
|
+
elsif !args.empty?
|
|
352
|
+
new(dataset.join(args))
|
|
353
|
+
else
|
|
354
|
+
self
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Pluck value(s) from specific attribute(s)
|
|
359
|
+
# unwrapped only if all are lone results.
|
|
360
|
+
#
|
|
361
|
+
# @example Single value
|
|
362
|
+
# users.pluck(:uidnumber)
|
|
363
|
+
# # ["1", "2"]
|
|
364
|
+
#
|
|
365
|
+
# users.pluck(:cn)
|
|
366
|
+
# # [["Cat", "House Cat"], ["Mouse"]]
|
|
367
|
+
#
|
|
368
|
+
# @example Multiple values
|
|
369
|
+
# users.pluck(:gidnumber, :uid)
|
|
370
|
+
# # [["1", "Jane"] ["2", "Joe"]]
|
|
371
|
+
#
|
|
372
|
+
# @param names [Symbol, String, Array<String, Symbol>]
|
|
373
|
+
#
|
|
374
|
+
# @return [Array<String, Array>]
|
|
375
|
+
#
|
|
376
|
+
# @api public
|
|
377
|
+
def pluck(*names)
|
|
378
|
+
raise ArgumentError, 'no attributes provided' if names.empty?
|
|
379
|
+
|
|
380
|
+
map do |entry|
|
|
381
|
+
results = values = names.map { |n| entry[n] }
|
|
382
|
+
results = values.map(&:pop) if values.map(&:one?).all?
|
|
383
|
+
results.one? ? results.pop : results
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Returns tuples with popped values.
|
|
388
|
+
#
|
|
389
|
+
# @return [LDAP::Relation]
|
|
390
|
+
#
|
|
391
|
+
def unwrap
|
|
392
|
+
new Functions[:map_array, Functions[:map_values, :pop]].call(self)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Select specific attributes
|
|
396
|
+
#
|
|
397
|
+
# @overload select(*attributes)
|
|
398
|
+
# Project relation using schema attributes
|
|
399
|
+
#
|
|
400
|
+
# @example using attributes
|
|
401
|
+
# users.select(:id, :name).first
|
|
402
|
+
# # {:id => 1, :name => "Jane"}
|
|
403
|
+
#
|
|
404
|
+
# @example using schema
|
|
405
|
+
# users.select(*schema.project(:id)).first
|
|
406
|
+
# # {:id => 1}
|
|
407
|
+
#
|
|
408
|
+
# @param [Array<LDAP::Attribute>] columns A list of schema attributes
|
|
409
|
+
#
|
|
410
|
+
# @overload select(&block)
|
|
411
|
+
# Project relation using projection DSL
|
|
412
|
+
#
|
|
413
|
+
# @example using attributes
|
|
414
|
+
# users.select { cn.as(:user_name) }
|
|
415
|
+
# # {:user_name => "Peter Hamilton"}
|
|
416
|
+
#
|
|
417
|
+
# users.select { [uidnumber, sn] }
|
|
418
|
+
# # {:uidnumber => 501, :name => "Hamilton"}
|
|
419
|
+
#
|
|
420
|
+
# @param [Array<LDAP::Attribute>] columns A list of schema attributes
|
|
421
|
+
#
|
|
422
|
+
# @return [Relation]
|
|
423
|
+
#
|
|
424
|
+
# @api public
|
|
425
|
+
def select(*args, &block)
|
|
426
|
+
schema.project(*args, &block).call(self)
|
|
427
|
+
end
|
|
428
|
+
alias_method :project, :select
|
|
429
|
+
|
|
430
|
+
# Rename attributes in a relation
|
|
431
|
+
#
|
|
432
|
+
# This method is intended to be used internally within a relation object
|
|
433
|
+
#
|
|
434
|
+
# @example
|
|
435
|
+
# users.rename(name: :user_name).first
|
|
436
|
+
# # {:id => 1, :user_name => "Jane", ... }
|
|
437
|
+
#
|
|
438
|
+
# @param mapping [Hash<Symbol=>Symbol>] A name => new_name map
|
|
439
|
+
#
|
|
440
|
+
# @return [Relation]
|
|
441
|
+
#
|
|
442
|
+
# @api public
|
|
443
|
+
def rename(mapping)
|
|
444
|
+
schema.rename(mapping).call(self)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Append specific columns to select clause
|
|
448
|
+
#
|
|
449
|
+
# @see Relation#select
|
|
450
|
+
#
|
|
451
|
+
# @return [Relation]
|
|
452
|
+
#
|
|
453
|
+
# @api public
|
|
454
|
+
def select_append(*args, &block)
|
|
455
|
+
schema.merge(schema.canonical.project(*args, &block)).call(self)
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|