ddr-models 2.4.0.rc4 → 2.4.0.rc5

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.
@@ -1,17 +1,42 @@
1
+ require "virtus"
2
+
1
3
  module Ddr::Index
2
4
  class QueryClause
5
+ include Virtus.value_object
6
+
7
+ ANY_FIELD = Field.new('*').freeze
8
+ ANY_VALUE = "[* TO *]"
9
+ QUOTE = '"'
10
+
11
+ TERM_QUERY = "{!term f=%{field}}%{value}"
12
+ STANDARD_QUERY = "%{field}:%{value}"
13
+ NEGATIVE_QUERY = "-%{field}:%{value}"
14
+ DISJUNCTION = "{!lucene q.op=OR df=%{field}}%{value}"
15
+
16
+ values do
17
+ attribute :field, FieldAttribute
18
+ attribute :value, String
19
+ attribute :quote_value, Boolean, default: false
20
+ attribute :template, String, default: STANDARD_QUERY
21
+ end
22
+
23
+ def to_s
24
+ template % { field: field, value: quote_value ? quote(value) : value }
25
+ end
3
26
 
4
- PRESENT = "[* TO *]"
5
- TERM = "{!term f=%s}%s"
6
- BEFORE_DAYS = "[* TO NOW-%sDAYS]"
27
+ def quote(value)
28
+ self.class.quote(value)
29
+ end
7
30
 
8
31
  class << self
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
13
- def build(field, value)
14
- [field, value].join(":")
32
+
33
+ def quote(value)
34
+ # Derived from Blacklight::Solr::SearchBuilderBehavior#solr_param_quote
35
+ unless value =~ /\A[a-zA-Z0-9$_\-\^]+\z/
36
+ QUOTE + value.gsub("'", "\\\\\'").gsub('"', "\\\\\"") + QUOTE
37
+ else
38
+ value
39
+ end
15
40
  end
16
41
 
17
42
  # Builds a query clause to retrieve the index document by unique key.
@@ -20,62 +45,51 @@ module Ddr::Index
20
45
  end
21
46
  alias_method :id, :unique_key
22
47
 
48
+ def where(field, value)
49
+ if value.respond_to?(:each)
50
+ disjunction(field, value)
51
+ else
52
+ new(field: field, value: value, quote_value: true)
53
+ end
54
+ end
55
+
23
56
  # 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
27
57
  def negative(field, value)
28
- build("-#{field}", value)
58
+ new(field: field, value: value, template: NEGATIVE_QUERY, quote_value: true)
29
59
  end
30
60
 
31
61
  # 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
34
62
  def present(field)
35
- build(field, PRESENT)
63
+ new(field: field, value: ANY_VALUE)
36
64
  end
37
65
 
38
66
  # Builds a query clause to filter where field is NOT present (no values)
39
- # @param field [Field, String] field
40
- # @return [String] query clause
41
67
  def absent(field)
42
- negative(field, PRESENT)
68
+ new(field: "-#{field}", value: ANY_VALUE)
43
69
  end
44
70
 
45
71
  # 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
49
- def or_values(field, values)
50
- build(field, QueryValue.or_values(values))
72
+ def disjunction(field, values)
73
+ value = values.map { |v| quote(v) }.join(" ")
74
+ new(field: field, value: value, template: DISJUNCTION)
51
75
  end
52
76
 
53
77
  # 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
57
- def before(field, date_time)
58
- value = "[* TO %s]" % Ddr::Utils.solr_date(date_time)
59
- build(field, value)
78
+ def before(field, value)
79
+ new(field: field, value: "[* TO %s]" % Ddr::Utils.solr_date(value))
60
80
  end
81
+ alias_method :before_date_time, :before
61
82
 
62
83
  # 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
66
- def before_days(field, days)
67
- value = BEFORE_DAYS % days.to_i
68
- build(field, value)
84
+ def before_days(field, value)
85
+ new(field: field, value: "[* TO NOW-%iDAYS]" % value)
69
86
  end
70
87
 
71
88
  # 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
76
89
  def term(field, value)
77
- TERM % [field, value.gsub(/"/, '\"')]
90
+ new(field: field, value: value, template: TERM_QUERY)
78
91
  end
92
+
79
93
  end
80
94
 
81
95
  end
@@ -1,23 +1,39 @@
1
- require "delegate"
2
-
3
1
  module Ddr::Index
4
- class QueryParams < SimpleDelegator
2
+ class QueryParams
5
3
 
6
- attr_reader :params
4
+ attr_reader :query
7
5
 
8
6
  def initialize(query)
9
- super
10
- @params = {
11
- q: q,
12
- fq: fq,
13
- fl: fields.join(","),
14
- sort: sort.join(","),
7
+ @query = query
8
+ end
9
+
10
+ def params
11
+ { q: q_param,
12
+ fq: filter_queries,
13
+ fl: fields,
14
+ sort: sort,
15
15
  rows: rows,
16
16
  }.select { |k, v| v.present? }
17
17
  end
18
18
 
19
- def fq
20
- filters.map(&:clauses).flatten
19
+ def q_param
20
+ query.q.to_s
21
+ end
22
+
23
+ def filter_queries
24
+ query.filter_clauses.map(&:to_s)
25
+ end
26
+
27
+ def fields
28
+ query.fields.join(",")
29
+ end
30
+
31
+ def sort
32
+ query.sort.join(",")
33
+ end
34
+
35
+ def rows
36
+ query.rows
21
37
  end
22
38
 
23
39
  end
@@ -2,18 +2,26 @@ require "virtus"
2
2
 
3
3
  module Ddr::Index
4
4
  class SortOrder
5
- include Virtus.value_object(strict: true)
5
+ include Virtus.value_object
6
6
 
7
7
  ASC = "asc"
8
8
  DESC = "desc"
9
9
 
10
10
  values do
11
- attribute :field, String
11
+ attribute :field, FieldAttribute
12
12
  attribute :order, String
13
13
  end
14
14
 
15
15
  def to_s
16
- [field, order].join(" ")
16
+ [ field, order ].join(" ")
17
+ end
18
+
19
+ def self.asc(field)
20
+ new(field: field, order: ASC)
21
+ end
22
+
23
+ def self.desc(field)
24
+ new(field: field, order: DESC)
17
25
  end
18
26
 
19
27
  end
@@ -1,5 +1,5 @@
1
1
  module Ddr
2
2
  module Models
3
- VERSION = "2.4.0.rc4"
3
+ VERSION = "2.4.0.rc5"
4
4
  end
5
5
  end
@@ -6,59 +6,101 @@ module Ddr::Index
6
6
  describe "equality" do
7
7
  describe "when the other is a Filter instance" do
8
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"]) }
9
+ subject { described_class.new(clauses: ["foo:bar", "spam:eggs"]) }
10
+ let(:other) { described_class.new(clauses: ["foo:bar", "spam:eggs"]) }
11
11
  specify { expect(subject).to eq other }
12
12
  end
13
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"]) }
14
+ subject { described_class.new(clauses: ["foo:bar", "bam:baz"]) }
15
+ let(:other) { described_class.new(clauses: ["foo:bar", "spam:eggs"]) }
16
16
  specify { expect(subject).not_to eq other }
17
17
  end
18
18
  end
19
19
  describe "when the other is not a Filter instance" do
20
- subject { described_class.new(["foo:bar", "spam:eggs"]) }
20
+ subject { described_class.new(clauses: ["foo:bar", "spam:eggs"]) }
21
21
  let(:other) { double(clauses: ["foo:bar", "spam:eggs"]) }
22
22
  specify { expect(subject).not_to eq other }
23
23
  end
24
24
  end
25
25
 
26
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)"]) }
27
+ describe ".is_governed_by" do
28
+ describe "with an object" do
29
+ subject { described_class.is_governed_by(Item.new(pid: "test:1")) }
30
+ its(:clauses) {
31
+ are_expected.to eq([QueryClause.term(:is_governed_by, "info:fedora/test:1")])
32
+ }
33
+ end
34
+ describe "with an ID" do
35
+ subject { described_class.is_governed_by("test:1") }
36
+ its(:clauses) {
37
+ are_expected.to eq([QueryClause.term(:is_governed_by, "info:fedora/test:1")])
38
+ }
39
+ end
30
40
  end
31
- describe "#raw" do
32
- subject { Filter.raw("foo:bar", "spam:eggs") }
41
+ describe ".is_member_of_collection" do
42
+ describe "with an object" do
43
+ subject { described_class.is_member_of_collection(Item.new(pid: "test:1")) }
44
+ its(:clauses) {
45
+ are_expected.to eq([QueryClause.term(:is_member_of_collection, "info:fedora/test:1")])
46
+ }
47
+ end
48
+ describe "with an ID" do
49
+ subject { described_class.is_member_of_collection("test:1") }
50
+ its(:clauses) {
51
+ are_expected.to eq([QueryClause.term(:is_member_of_collection, "info:fedora/test:1")])
52
+ }
53
+ end
54
+ end
55
+ describe ".has_content" do
56
+ subject { described_class.has_content }
57
+ its(:clauses) { are_expected.to eq([QueryClause.where(:active_fedora_model, ["Component", "Attachment", "Target"])]) }
58
+ end
59
+ describe ".where" do
60
+ subject { described_class.where("foo"=>"bar", "spam"=>"eggs", "stuff"=>["dog", "cat", "bird"]) }
61
+ its(:clauses) {
62
+ are_expected.to eq([QueryClause.where("foo", "bar"),
63
+ QueryClause.where("spam", "eggs"),
64
+ QueryClause.where("stuff", ["dog", "cat", "bird"])
65
+ ])
66
+ }
67
+ end
68
+ describe ".raw" do
69
+ subject { described_class.raw("foo:bar", "spam:eggs") }
33
70
  its(:clauses) { are_expected.to eq(["foo:bar", "spam:eggs"]) }
34
71
  end
35
- describe "#negative" do
36
- subject { Filter.negative("foo", "bar") }
37
- its(:clauses) { are_expected.to eq(["-foo:bar"]) }
72
+ describe ".negative" do
73
+ subject { described_class.negative("foo", "bar") }
74
+ its(:clauses) { are_expected.to eq([QueryClause.negative("foo", "bar")]) }
38
75
  end
39
- describe "#present" do
40
- subject { Filter.present("foo") }
41
- its(:clauses) { are_expected.to eq(["foo:[* TO *]"]) }
76
+ describe ".present" do
77
+ subject { described_class.present("foo") }
78
+ its(:clauses) { are_expected.to eq([QueryClause.present("foo")]) }
42
79
  end
43
- describe "#absent" do
44
- subject { Filter.absent("foo") }
45
- its(:clauses) { are_expected.to eq(["-foo:[* TO *]"]) }
80
+ describe ".absent" do
81
+ subject { described_class.absent("foo") }
82
+ its(:clauses) { are_expected.to eq([QueryClause.absent("foo")]) }
46
83
  end
47
- describe "#before_days" do
48
- subject { Filter.before_days("foo", 60) }
49
- its(:clauses) { are_expected.to eq(["foo:[* TO NOW-60DAYS]"]) }
84
+ describe ".before_days" do
85
+ subject { described_class.before_days("foo", 60) }
86
+ its(:clauses) { are_expected.to eq([QueryClause.before_days("foo", 60)]) }
50
87
  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]"]) }
88
+ describe ".before" do
89
+ subject { described_class.before("foo", DateTime.parse("Thu, 27 Aug 2015 17:42:34 -0400")) }
90
+ its(:clauses) {
91
+ are_expected.to eq([QueryClause.before("foo", DateTime.parse("Thu, 27 Aug 2015 17:42:34 -0400"))])
92
+ }
54
93
  end
55
94
  end
56
95
 
57
- describe "instance methods" do
96
+ describe "API methods" do
58
97
  describe "#where" do
59
98
  it "adds raw query filters for the hash of conditions" do
60
99
  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)"])
100
+ expect(subject.clauses).to eq([QueryClause.where("foo", "bar"),
101
+ QueryClause.where("spam", "eggs"),
102
+ QueryClause.where("stuff", ["dog", "cat", "bird"])
103
+ ])
62
104
  end
63
105
  end
64
106
  describe "#raw" do
@@ -70,31 +112,31 @@ module Ddr::Index
70
112
  describe "#negative" do
71
113
  it "adds a negation query clause" do
72
114
  subject.negative("foo", "bar")
73
- expect(subject.clauses).to eq(["-foo:bar"])
115
+ expect(subject.clauses).to eq([QueryClause.negative("foo", "bar")])
74
116
  end
75
117
  end
76
118
  describe "#present" do
77
119
  it "adds a \"field present\" query clause" do
78
120
  subject.present("foo")
79
- expect(subject.clauses).to eq(["foo:[* TO *]"])
121
+ expect(subject.clauses).to eq([QueryClause.present("foo")])
80
122
  end
81
123
  end
82
124
  describe "#absent" do
83
125
  it "adds a \"field not present\" query clause" do
84
126
  subject.absent("foo")
85
- expect(subject.clauses).to eq(["-foo:[* TO *]"])
127
+ expect(subject.clauses).to eq([QueryClause.absent("foo")])
86
128
  end
87
129
  end
88
130
  describe "#before_days" do
89
131
  it "adds a date range query clause" do
90
132
  subject.before_days("foo", 60)
91
- expect(subject.clauses).to eq(["foo:[* TO NOW-60DAYS]"])
133
+ expect(subject.clauses).to eq([QueryClause.before_days("foo", 60)])
92
134
  end
93
135
  end
94
136
  describe "#before" do
95
137
  it "adds a date range query clause" do
96
138
  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]"])
139
+ expect(subject.clauses).to eq([QueryClause.before("foo", DateTime.parse("Thu, 27 Aug 2015 17:42:34 -0400"))])
98
140
  end
99
141
  end
100
142
  end
@@ -6,90 +6,111 @@ module Ddr::Index
6
6
  subject { described_class.new { id "test:1" } }
7
7
  specify {
8
8
  expect(subject.query.rows).to eq 1
9
- expect(subject.query.q).to eq "{!term f=id}test:1"
9
+ expect(subject.query.q).to eq QueryClause.id("test:1")
10
10
  }
11
11
  end
12
-
13
12
  describe "q" do
14
13
  subject { described_class.new { q "foo:bar" } }
15
14
  specify { expect(subject.query.q).to eq "foo:bar" }
16
15
  end
17
-
18
16
  describe "asc" do
19
- subject { described_class.new { asc "foo" } }
20
- specify { expect(subject.query.sort).to eq [SortOrder.new(field: "foo", order: "asc")] }
17
+ subject { described_class.new { asc "foo", "bar" } }
18
+ specify {
19
+ expect(subject.query.sort).to eq [SortOrder.asc("foo"), SortOrder.asc("bar")]
20
+ }
21
21
  end
22
-
23
22
  describe "desc" do
24
- subject { described_class.new { desc "foo" } }
25
- specify { expect(subject.query.sort).to eq [SortOrder.new(field: "foo", order: "desc")] }
23
+ subject { described_class.new { desc "foo", "bar" } }
24
+ specify {
25
+ expect(subject.query.sort).to eq [SortOrder.desc("foo"), SortOrder.desc("bar")]
26
+ }
26
27
  end
27
-
28
28
  describe "filter" do
29
29
  subject { described_class.new { filter Filter.where("foo"=>"bar") } }
30
30
  specify { expect(subject.query.filters).to eq [Filter.where("foo"=>"bar")] }
31
31
  end
32
-
32
+ describe "filters" do
33
+ subject {
34
+ described_class.new { filters Filter.where("foo"=>"bar"), Filter.where("bing"=>"bang") }
35
+ }
36
+ specify {
37
+ expect(subject.query.filters).to eq([Filter.where("foo"=>"bar"),
38
+ Filter.where("bing"=>"bang")
39
+ ])
40
+ }
41
+ end
33
42
  describe "field" do
34
43
  subject { described_class.new { field "foo", "bar" } }
35
44
  specify { expect(subject.query.fields).to include("foo", "bar") }
36
45
  end
37
-
38
46
  describe "fields" do
39
47
  subject { described_class.new { fields "foo", "bar" } }
40
48
  specify { expect(subject.query.fields).to include("foo", "bar") }
41
49
  end
42
-
43
50
  describe "sort" do
44
51
  subject { described_class.new { sort "foo"=>"asc", "bar"=>"desc" } }
45
- specify { expect(subject.query.sort).to eq [SortOrder.new(field: "foo", order: "asc"), SortOrder.new(field: "bar", order: "desc")] }
52
+ specify {
53
+ expect(subject.query.sort).to eq([SortOrder.new(field: "foo", order: "asc"),
54
+ SortOrder.new(field: "bar", order: "desc")
55
+ ])
56
+ }
46
57
  end
47
-
48
58
  describe "order_by" do
49
59
  subject { described_class.new { order_by "foo"=>"asc", "bar"=>"desc" } }
50
60
  specify { expect(subject.query.sort).to eq [SortOrder.new(field: "foo", order: "asc"), SortOrder.new(field: "bar", order: "desc")] }
51
61
  end
52
-
53
62
  describe "limit" do
54
63
  subject { described_class.new { limit 5 } }
55
64
  specify { expect(subject.query.rows).to eq 5 }
56
65
  end
57
-
58
66
  describe "rows" do
59
67
  subject { described_class.new { rows 5 } }
60
68
  specify { expect(subject.query.rows).to eq 5 }
61
69
  end
62
-
63
70
  describe "raw" do
64
71
  subject { described_class.new { raw "foo:bar" } }
65
72
  specify { expect(subject.query.filters).to eq [Filter.raw("foo:bar")] }
66
73
  end
67
-
68
74
  describe "where" do
69
75
  subject { described_class.new { where "foo"=>"bar" } }
70
76
  specify { expect(subject.query.filters).to eq [Filter.where("foo"=>"bar")] }
71
77
  end
72
-
73
78
  describe "absent" do
74
79
  subject { described_class.new { absent "foo" } }
75
80
  specify { expect(subject.query.filters).to eq [Filter.absent("foo")] }
76
81
  end
77
-
78
82
  describe "present" do
79
83
  subject { described_class.new { present "foo" } }
80
84
  specify { expect(subject.query.filters).to eq [Filter.present("foo")] }
81
85
  end
82
-
83
86
  describe "before" do
84
- subject { described_class.new { before "foo", DateTime.parse("2015-12-14T20:40:06Z") } }
85
- specify { expect(subject.query.filters).to eq [Filter.before("foo", DateTime.parse("2015-12-14T20:40:06Z"))] }
87
+ subject {
88
+ described_class.new { before "foo", DateTime.parse("2015-12-14T20:40:06Z") }
89
+ }
90
+ specify {
91
+ expect(subject.query.filters).to eq [Filter.before("foo", DateTime.parse("2015-12-14T20:40:06Z"))]
92
+ }
86
93
  end
87
-
88
94
  describe "before_days" do
89
95
  subject { described_class.new { before_days "foo", 7 } }
90
96
  specify { expect(subject.query.filters).to eq [Filter.before_days("foo", 7)] }
91
97
  end
92
98
  end
93
99
 
100
+ describe "using static filters" do
101
+ describe "has_content" do
102
+ before { subject.has_content }
103
+ specify { expect(subject.query.filters).to eq [Filter.has_content] }
104
+ end
105
+ describe "is_governed_by" do
106
+ before { subject.is_governed_by "test:1" }
107
+ specify { expect(subject.query.filters).to eq [Filter.is_governed_by("test:1")] }
108
+ end
109
+ describe "is_governed_by" do
110
+ before { subject.is_member_of_collection "test:1" }
111
+ specify { expect(subject.query.filters).to eq [Filter.is_member_of_collection("test:1")] }
112
+ end
113
+ end
114
+
94
115
  end
95
116
  end