ddr-models 2.4.0.rc2 → 2.4.0.rc3
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 +4 -4
- data/config/locales/ddr-models.en.yml +74 -0
- data/ddr-models.gemspec +1 -0
- data/lib/ddr/index.rb +4 -1
- data/lib/ddr/index/csv_query_result.rb +1 -1
- data/lib/ddr/index/field.rb +11 -1
- data/lib/ddr/index/fields.rb +15 -11
- data/lib/ddr/index/filter.rb +27 -15
- data/lib/ddr/index/filters.rb +28 -8
- data/lib/ddr/index/queries.rb +20 -0
- data/lib/ddr/index/query.rb +12 -8
- data/lib/ddr/index/query_builder.rb +68 -31
- data/lib/ddr/index/query_clause.rb +38 -8
- data/lib/ddr/index/query_params.rb +24 -0
- data/lib/ddr/index/query_value.rb +5 -0
- data/lib/ddr/index/sort_order.rb +20 -0
- data/lib/ddr/models/version.rb +1 -1
- data/spec/index/fields_spec.rb +175 -0
- data/spec/index/filter_spec.rb +86 -30
- data/spec/index/filters_spec.rb +25 -8
- data/spec/index/queries_spec.rb +21 -0
- data/spec/index/query_builder_spec.rb +95 -0
- data/spec/index/query_clause_spec.rb +46 -0
- data/spec/index/query_spec.rb +19 -15
- metadata +28 -2
@@ -6,43 +6,73 @@ module Ddr::Index
|
|
6
6
|
BEFORE_DAYS = "[* TO NOW-%sDAYS]"
|
7
7
|
|
8
8
|
class << self
|
9
|
-
#
|
9
|
+
# Builds a standard query clause, no escaping applied.
|
10
|
+
# @param field [Field, String] field
|
11
|
+
# @param value [String] query value
|
12
|
+
# @return [String] query clause
|
10
13
|
def build(field, value)
|
11
14
|
[field, value].join(":")
|
12
15
|
end
|
13
16
|
|
17
|
+
# Builds a query clause to retrieve the index document by unique key.
|
14
18
|
def unique_key(value)
|
15
19
|
term(UniqueKeyField.instance, value)
|
16
20
|
end
|
17
21
|
alias_method :id, :unique_key
|
18
|
-
alias_method :pid, :unique_key
|
19
22
|
|
23
|
+
# Builds a query clause to filter where field does not have the given value.
|
24
|
+
# @param field [Field, String] field
|
25
|
+
# @param value [String] query value
|
26
|
+
# @return [String] query clause
|
20
27
|
def negative(field, value)
|
21
|
-
build
|
28
|
+
build("-#{field}", value)
|
22
29
|
end
|
23
30
|
|
31
|
+
# Builds a query clause to filter where field is present (i.e, has any value)
|
32
|
+
# @param field [Field, String] field
|
33
|
+
# @return [String] query clause
|
24
34
|
def present(field)
|
25
|
-
build
|
35
|
+
build(field, PRESENT)
|
26
36
|
end
|
27
37
|
|
38
|
+
# Builds a query clause to filter where field is NOT present (no values)
|
39
|
+
# @param field [Field, String] field
|
40
|
+
# @return [String] query clause
|
28
41
|
def absent(field)
|
29
|
-
negative
|
42
|
+
negative(field, PRESENT)
|
30
43
|
end
|
31
44
|
|
45
|
+
# Builds a query clause to filter where field contains at least one of a set of values.
|
46
|
+
# @param field [Field, String] field
|
47
|
+
# @param values [Array<String>] query values
|
48
|
+
# @return [String] query clause
|
32
49
|
def or_values(field, values)
|
33
|
-
build
|
50
|
+
build(field, QueryValue.or_values(values))
|
34
51
|
end
|
35
52
|
|
53
|
+
# Builds a query clause to filter where date field value is earlier than a date/time value.
|
54
|
+
# @param field [Field, String] field
|
55
|
+
# @param value [Object] query value, must be coercible to a Solr date string.
|
56
|
+
# @return [String] query clause
|
36
57
|
def before(field, date_time)
|
37
58
|
value = "[* TO %s]" % Ddr::Utils.solr_date(date_time)
|
38
|
-
build
|
59
|
+
build(field, value)
|
39
60
|
end
|
40
61
|
|
62
|
+
# Builds a query clause to filter where date field value is earlier than a number of days before now.
|
63
|
+
# @param field [Field, String] field
|
64
|
+
# @param value [String, Fixnum] query value, must be coercible to integer.
|
65
|
+
# @return [String] query clause
|
41
66
|
def before_days(field, days)
|
42
67
|
value = BEFORE_DAYS % days.to_i
|
43
|
-
build
|
68
|
+
build(field, value)
|
44
69
|
end
|
45
70
|
|
71
|
+
# Builds a "term query" clause to filter where field contains value.
|
72
|
+
# Double quotes are escaped.
|
73
|
+
# @param field [Field, String] field
|
74
|
+
# @param value [String] query value
|
75
|
+
# @return [String] Solr term query
|
46
76
|
def term(field, value)
|
47
77
|
TERM % [field, value.gsub(/"/, '\"')]
|
48
78
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Ddr::Index
|
4
|
+
class QueryParams < SimpleDelegator
|
5
|
+
|
6
|
+
attr_reader :params
|
7
|
+
|
8
|
+
def initialize(query)
|
9
|
+
super
|
10
|
+
@params = {
|
11
|
+
q: q,
|
12
|
+
fq: fq,
|
13
|
+
fl: fields.join(","),
|
14
|
+
sort: sort.join(","),
|
15
|
+
rows: rows,
|
16
|
+
}.select { |k, v| v.present? }
|
17
|
+
end
|
18
|
+
|
19
|
+
def fq
|
20
|
+
filters.map(&:clauses).flatten
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -2,11 +2,16 @@ module Ddr::Index
|
|
2
2
|
class QueryValue
|
3
3
|
|
4
4
|
class << self
|
5
|
+
# Returns an escaped query value
|
6
|
+
# @param value [String] the unescaped value
|
7
|
+
# @return [String] the escaped value
|
5
8
|
def build(value)
|
6
9
|
RSolr.solr_escape(value)
|
7
10
|
end
|
8
11
|
|
12
|
+
# Builds an escaped query value for use in filtering on a field for any value in the list of values
|
9
13
|
# @param values [Enumerable<String>]
|
14
|
+
# @return [String] query value
|
10
15
|
def or_values(values)
|
11
16
|
unless values.is_a?(::Enumerable) && values.present?
|
12
17
|
raise ArgumentError, "`#{self.name}.or_values` requires a non-empty enumerable of strings."
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "virtus"
|
2
|
+
|
3
|
+
module Ddr::Index
|
4
|
+
class SortOrder
|
5
|
+
include Virtus.value_object(strict: true)
|
6
|
+
|
7
|
+
ASC = "asc"
|
8
|
+
DESC = "desc"
|
9
|
+
|
10
|
+
values do
|
11
|
+
attribute :field, String
|
12
|
+
attribute :order, String
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
[field, order].join(" ")
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/lib/ddr/models/version.rb
CHANGED
@@ -0,0 +1,175 @@
|
|
1
|
+
module Ddr::Index
|
2
|
+
RSpec.describe Fields do
|
3
|
+
|
4
|
+
describe "constants" do
|
5
|
+
describe "ID" do
|
6
|
+
subject { Fields::ID }
|
7
|
+
its(:label) { is_expected.to eq "Fedora PID" }
|
8
|
+
its(:heading) { is_expected.to eq "pid" }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "PID" do
|
12
|
+
subject { Fields::PID }
|
13
|
+
its(:label) { is_expected.to eq "Fedora PID" }
|
14
|
+
its(:heading) { is_expected.to eq "pid" }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "ACCESS_ROLE" do
|
18
|
+
subject { Fields::ACCESS_ROLE }
|
19
|
+
its(:label) { is_expected.to eq "Access Role" }
|
20
|
+
its(:heading) { is_expected.to eq "access_role" }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "ACTIVE_FEDORA_MODEL" do
|
24
|
+
subject { Fields::ACTIVE_FEDORA_MODEL }
|
25
|
+
its(:label) { is_expected.to eq "Model" }
|
26
|
+
its(:heading) { is_expected.to eq "model" }
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "ADMIN_SET" do
|
30
|
+
subject { Fields::ADMIN_SET }
|
31
|
+
its(:label) { is_expected.to eq "Admin Set" }
|
32
|
+
its(:heading) { is_expected.to eq "admin_set" }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "ASPACE_ID" do
|
36
|
+
subject { Fields::ASPACE_ID }
|
37
|
+
its(:label) { is_expected.to eq "ArchivesSpace ID" }
|
38
|
+
its(:heading) { is_expected.to eq "aspace_id" }
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "DOI" do
|
42
|
+
subject { Fields::DOI }
|
43
|
+
its(:label) { is_expected.to eq "DOI" }
|
44
|
+
its(:heading) { is_expected.to eq "doi" }
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "EAD_ID" do
|
48
|
+
subject { Fields::EAD_ID }
|
49
|
+
its(:label) { is_expected.to eq "EAD ID" }
|
50
|
+
its(:heading) { is_expected.to eq "ead_id" }
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "LOCAL_ID" do
|
54
|
+
subject { Fields::LOCAL_ID }
|
55
|
+
its(:label) { is_expected.to eq "Local ID" }
|
56
|
+
its(:heading) { is_expected.to eq "local_id" }
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "OBJECT_CREATE_DATE" do
|
60
|
+
subject { Fields::OBJECT_CREATE_DATE }
|
61
|
+
its(:label) { is_expected.to eq "Creation Date" }
|
62
|
+
its(:heading) { is_expected.to eq "creation_date" }
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "OBJECT_MODIFIED_DATE" do
|
66
|
+
subject { Fields::OBJECT_MODIFIED_DATE }
|
67
|
+
its(:label) { is_expected.to eq "Modification Date" }
|
68
|
+
its(:heading) { is_expected.to eq "modification_date" }
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "PERMANENT_ID" do
|
72
|
+
subject { Fields::PERMANENT_ID }
|
73
|
+
its(:label) { is_expected.to eq "Permanent ID" }
|
74
|
+
its(:heading) { is_expected.to eq "permanent_id" }
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "PERMANENT_URL" do
|
78
|
+
subject { Fields::PERMANENT_URL }
|
79
|
+
its(:label) { is_expected.to eq "Permanent URL" }
|
80
|
+
its(:heading) { is_expected.to eq "permanent_url" }
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "TECHMD_COLOR_SPACE" do
|
84
|
+
subject { Fields::TECHMD_COLOR_SPACE }
|
85
|
+
its(:label) { is_expected.to eq "Color Space" }
|
86
|
+
its(:heading) { is_expected.to eq "color_space" }
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "TECHMD_CREATING_APPLICATION" do
|
90
|
+
subject { Fields::TECHMD_CREATING_APPLICATION }
|
91
|
+
its(:label) { is_expected.to eq "Creating Application" }
|
92
|
+
its(:heading) { is_expected.to eq "creating_application" }
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "TECHMD_CREATION_TIME" do
|
96
|
+
subject { Fields::TECHMD_CREATION_TIME }
|
97
|
+
its(:label) { is_expected.to eq "Creation Time" }
|
98
|
+
its(:heading) { is_expected.to eq "creation_time" }
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "TECHMD_FILE_SIZE" do
|
102
|
+
subject { Fields::TECHMD_FILE_SIZE }
|
103
|
+
its(:label) { is_expected.to eq "File Size" }
|
104
|
+
its(:heading) { is_expected.to eq "file_size" }
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "TECHMD_FITS_VERSION" do
|
108
|
+
subject { Fields::TECHMD_FITS_VERSION }
|
109
|
+
its(:label) { is_expected.to eq "FITS Version" }
|
110
|
+
its(:heading) { is_expected.to eq "fits_version" }
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "TECHMD_FITS_DATETIME" do
|
114
|
+
subject { Fields::TECHMD_FITS_DATETIME }
|
115
|
+
its(:label) { is_expected.to eq "FITS Run At" }
|
116
|
+
its(:heading) { is_expected.to eq "fits_datetime" }
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "TECHMD_FORMAT_LABEL" do
|
120
|
+
subject { Fields::TECHMD_FORMAT_LABEL }
|
121
|
+
its(:label) { is_expected.to eq "Format Label" }
|
122
|
+
its(:heading) { is_expected.to eq "format_label" }
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "TECHMD_FORMAT_VERSION" do
|
126
|
+
subject { Fields::TECHMD_FORMAT_VERSION }
|
127
|
+
its(:label) { is_expected.to eq "Format Version" }
|
128
|
+
its(:heading) { is_expected.to eq "format_version" }
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "TECHMD_IMAGE_HEIGHT" do
|
132
|
+
subject { Fields::TECHMD_IMAGE_HEIGHT }
|
133
|
+
its(:label) { is_expected.to eq "Image Height" }
|
134
|
+
its(:heading) { is_expected.to eq "image_height" }
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "TECHMD_IMAGE_WIDTH" do
|
138
|
+
subject { Fields::TECHMD_IMAGE_WIDTH }
|
139
|
+
its(:label) { is_expected.to eq "Image Width" }
|
140
|
+
its(:heading) { is_expected.to eq "image_width" }
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "TECHMD_MEDIA_TYPE" do
|
144
|
+
subject { Fields::TECHMD_MEDIA_TYPE }
|
145
|
+
its(:label) { is_expected.to eq "Media Type" }
|
146
|
+
its(:heading) { is_expected.to eq "media_type" }
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "TECHMD_MODIFICATION_TIME" do
|
150
|
+
subject { Fields::TECHMD_MODIFICATION_TIME }
|
151
|
+
its(:label) { is_expected.to eq "Modification Time" }
|
152
|
+
its(:heading) { is_expected.to eq "modification_time" }
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "TECHMD_PRONOM_IDENTIFIER" do
|
156
|
+
subject { Fields::TECHMD_PRONOM_IDENTIFIER }
|
157
|
+
its(:label) { is_expected.to eq "PRONOM Unique ID" }
|
158
|
+
its(:heading) { is_expected.to eq "pronom_uid" }
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "TECHMD_VALID" do
|
162
|
+
subject { Fields::TECHMD_VALID }
|
163
|
+
its(:label) { is_expected.to eq "Valid" }
|
164
|
+
its(:heading) { is_expected.to eq "valid" }
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "TECHMD_WELL_FORMED" do
|
168
|
+
subject { Fields::TECHMD_WELL_FORMED }
|
169
|
+
its(:label) { is_expected.to eq "Well-formed" }
|
170
|
+
its(:heading) { is_expected.to eq "well_formed" }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
data/spec/index/filter_spec.rb
CHANGED
@@ -1,45 +1,101 @@
|
|
1
1
|
module Ddr::Index
|
2
2
|
RSpec.describe Filter do
|
3
3
|
|
4
|
-
|
5
|
-
it "should add raw query filters for the field, value hash of conditions" do
|
6
|
-
subject.where("foo"=>"bar", "spam"=>"eggs", "stuff"=>["dog", "cat", "bird"])
|
7
|
-
expect(subject.clauses).to eq(["{!term f=foo}bar", "{!term f=spam}eggs", "stuff:(dog OR cat OR bird)"])
|
8
|
-
end
|
9
|
-
end
|
4
|
+
its(:clauses) { are_expected.to eq [] }
|
10
5
|
|
11
|
-
describe "
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
describe "equality" do
|
7
|
+
describe "when the other is a Filter instance" do
|
8
|
+
describe "and the clauses are equal" do
|
9
|
+
subject { described_class.new(["foo:bar", "spam:eggs"]) }
|
10
|
+
let(:other) { described_class.new(["foo:bar", "spam:eggs"]) }
|
11
|
+
specify { expect(subject).to eq other }
|
12
|
+
end
|
13
|
+
describe "and the clauses are not equal" do
|
14
|
+
subject { described_class.new(["foo:bar", "bam:baz"]) }
|
15
|
+
let(:other) { described_class.new(["foo:bar", "spam:eggs"]) }
|
16
|
+
specify { expect(subject).not_to eq other }
|
17
|
+
end
|
15
18
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
subject.present("foo")
|
21
|
-
expect(subject.clauses).to eq(["foo:[* TO *]"])
|
19
|
+
describe "when the other is not a Filter instance" do
|
20
|
+
subject { described_class.new(["foo:bar", "spam:eggs"]) }
|
21
|
+
let(:other) { double(clauses: ["foo:bar", "spam:eggs"]) }
|
22
|
+
specify { expect(subject).not_to eq other }
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
describe "
|
26
|
-
|
27
|
-
subject.
|
28
|
-
|
26
|
+
describe "class methods" do
|
27
|
+
describe ".where" do
|
28
|
+
subject { Filter.where("foo"=>"bar", "spam"=>"eggs", "stuff"=>["dog", "cat", "bird"]) }
|
29
|
+
its(:clauses) { are_expected.to eq (["{!term f=foo}bar", "{!term f=spam}eggs", "stuff:(dog OR cat OR bird)"]) }
|
29
30
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
describe "#raw" do
|
32
|
+
subject { Filter.raw("foo:bar", "spam:eggs") }
|
33
|
+
its(:clauses) { are_expected.to eq(["foo:bar", "spam:eggs"]) }
|
34
|
+
end
|
35
|
+
describe "#negative" do
|
36
|
+
subject { Filter.negative("foo", "bar") }
|
37
|
+
its(:clauses) { are_expected.to eq(["-foo:bar"]) }
|
38
|
+
end
|
39
|
+
describe "#present" do
|
40
|
+
subject { Filter.present("foo") }
|
41
|
+
its(:clauses) { are_expected.to eq(["foo:[* TO *]"]) }
|
42
|
+
end
|
43
|
+
describe "#absent" do
|
44
|
+
subject { Filter.absent("foo") }
|
45
|
+
its(:clauses) { are_expected.to eq(["-foo:[* TO *]"]) }
|
46
|
+
end
|
47
|
+
describe "#before_days" do
|
48
|
+
subject { Filter.before_days("foo", 60) }
|
49
|
+
its(:clauses) { are_expected.to eq(["foo:[* TO NOW-60DAYS]"]) }
|
50
|
+
end
|
51
|
+
describe "#before" do
|
52
|
+
subject { Filter.before("foo", DateTime.parse("Thu, 27 Aug 2015 17:42:34 -0400")) }
|
53
|
+
its(:clauses) { are_expected.to eq(["foo:[* TO 2015-08-27T21:42:34Z]"]) }
|
36
54
|
end
|
37
55
|
end
|
38
56
|
|
39
|
-
describe "
|
40
|
-
|
41
|
-
|
42
|
-
|
57
|
+
describe "instance methods" do
|
58
|
+
describe "#where" do
|
59
|
+
it "adds raw query filters for the hash of conditions" do
|
60
|
+
subject.where("foo"=>"bar", "spam"=>"eggs", "stuff"=>["dog", "cat", "bird"])
|
61
|
+
expect(subject.clauses).to eq(["{!term f=foo}bar", "{!term f=spam}eggs", "stuff:(dog OR cat OR bird)"])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
describe "#raw" do
|
65
|
+
it "adds the query clauses w/o escaping" do
|
66
|
+
subject.raw("foo:bar", "spam:eggs")
|
67
|
+
expect(subject.clauses).to eq(["foo:bar", "spam:eggs"])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
describe "#negative" do
|
71
|
+
it "adds a negation query clause" do
|
72
|
+
subject.negative("foo", "bar")
|
73
|
+
expect(subject.clauses).to eq(["-foo:bar"])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
describe "#present" do
|
77
|
+
it "adds a \"field present\" query clause" do
|
78
|
+
subject.present("foo")
|
79
|
+
expect(subject.clauses).to eq(["foo:[* TO *]"])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
describe "#absent" do
|
83
|
+
it "adds a \"field not present\" query clause" do
|
84
|
+
subject.absent("foo")
|
85
|
+
expect(subject.clauses).to eq(["-foo:[* TO *]"])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
describe "#before_days" do
|
89
|
+
it "adds a date range query clause" do
|
90
|
+
subject.before_days("foo", 60)
|
91
|
+
expect(subject.clauses).to eq(["foo:[* TO NOW-60DAYS]"])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
describe "#before" do
|
95
|
+
it "adds a date range query clause" do
|
96
|
+
subject.before("foo", DateTime.parse("Thu, 27 Aug 2015 17:42:34 -0400"))
|
97
|
+
expect(subject.clauses).to eq(["foo:[* TO 2015-08-27T21:42:34Z]"])
|
98
|
+
end
|
43
99
|
end
|
44
100
|
end
|
45
101
|
|