safety_pin 0.0.1
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.
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +18 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/lib/safety_pin/jcr.rb +42 -0
- data/lib/safety_pin/node.rb +242 -0
- data/lib/safety_pin/query/where_condition.rb +25 -0
- data/lib/safety_pin/query.rb +70 -0
- data/lib/safety_pin/version.rb +3 -0
- data/lib/safety_pin.rb +10 -0
- data/safety_pin.gemspec +17 -0
- data/spec/jcr_spec.rb +23 -0
- data/spec/jcr_sql2_spec.rb +93 -0
- data/spec/node_spec.rb +494 -0
- data/spec/query_spec.rb +126 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/where_condition_spec.rb +20 -0
- metadata +79 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--format progress --color
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create jruby-1.6.7@safety_pin
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.3)
|
5
|
+
rspec (2.10.0)
|
6
|
+
rspec-core (~> 2.10.0)
|
7
|
+
rspec-expectations (~> 2.10.0)
|
8
|
+
rspec-mocks (~> 2.10.0)
|
9
|
+
rspec-core (2.10.0)
|
10
|
+
rspec-expectations (2.10.0)
|
11
|
+
diff-lcs (~> 1.1.3)
|
12
|
+
rspec-mocks (2.10.1)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
ruby
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
rspec (~> 2.10.0)
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Jordan Raine
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# SafetyPin
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'safety_pin'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install safety_pin
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module SafetyPin
|
2
|
+
class JCR
|
3
|
+
include_class('java.lang.String') {|package,name| "J#{name}" }
|
4
|
+
include_class 'javax.jcr.SimpleCredentials'
|
5
|
+
include_class 'org.apache.jackrabbit.commons.JcrUtils'
|
6
|
+
|
7
|
+
def self.login(opts = {})
|
8
|
+
repository = JcrUtils.get_repository(parse_hostname(opts[:hostname]))
|
9
|
+
creds = SimpleCredentials.new(opts[:username], JString.new(opts[:password]).to_char_array)
|
10
|
+
@@session = repository.login(creds)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logout
|
14
|
+
session.logout
|
15
|
+
not session.live?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.logged_in?
|
19
|
+
session.live?
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.logged_out?
|
23
|
+
not logged_in?
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.session
|
27
|
+
@@session
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_hostname(hostname)
|
31
|
+
if hostname.end_with?("/crx/server")
|
32
|
+
hostname
|
33
|
+
else
|
34
|
+
hostname + "/crx/server"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.dev_login
|
39
|
+
login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
module SafetyPin
|
2
|
+
class Node
|
3
|
+
include_class 'javax.jcr.PropertyType'
|
4
|
+
include_class 'java.util.Calendar'
|
5
|
+
include_class 'java.util.Date'
|
6
|
+
|
7
|
+
attr_reader :j_node
|
8
|
+
|
9
|
+
def self.find(path)
|
10
|
+
raise ArgumentError unless path.to_s.start_with?("/")
|
11
|
+
Node.new(session.get_node(path.to_s))
|
12
|
+
rescue javax.jcr.PathNotFoundException
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.session
|
17
|
+
JCR.session
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.build(path, node_type = nil)
|
21
|
+
node_type ||= "nt:unstructured"
|
22
|
+
rel_path = nil
|
23
|
+
if path.start_with?("/")
|
24
|
+
rel_path = path.sub("/","")
|
25
|
+
else
|
26
|
+
raise ArgumentError.new("Given path not absolute: #{path}")
|
27
|
+
end
|
28
|
+
|
29
|
+
if session.root_node.has_node(rel_path)
|
30
|
+
raise NodeError.new("Node already exists at path: #{path}")
|
31
|
+
else
|
32
|
+
self.new(session.root_node.add_node(rel_path, node_type))
|
33
|
+
end
|
34
|
+
rescue javax.jcr.PathNotFoundException => e
|
35
|
+
raise NodeError.new("Cannot add a new node to a non-existing parent at #{path}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.create(path, node_type = nil)
|
39
|
+
node = self.build(path, node_type)
|
40
|
+
node.save
|
41
|
+
node
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(j_node)
|
45
|
+
@j_node = j_node
|
46
|
+
end
|
47
|
+
|
48
|
+
def path
|
49
|
+
@path ||= j_node.path
|
50
|
+
end
|
51
|
+
|
52
|
+
def session
|
53
|
+
@session ||= JCR.session
|
54
|
+
end
|
55
|
+
|
56
|
+
def children
|
57
|
+
child_nodes = []
|
58
|
+
j_node.get_nodes.each do |child_j_node|
|
59
|
+
child_nodes << Node.new(child_j_node)
|
60
|
+
end
|
61
|
+
child_nodes
|
62
|
+
end
|
63
|
+
|
64
|
+
def child(relative_path)
|
65
|
+
child_j_node = j_node.get_node(relative_path)
|
66
|
+
Node.new(child_j_node)
|
67
|
+
rescue javax.jcr.PathNotFoundException
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def name
|
72
|
+
@name ||= j_node.name
|
73
|
+
end
|
74
|
+
|
75
|
+
def read_attribute(name)
|
76
|
+
property = j_node.get_property(name)
|
77
|
+
if property_is_multi_valued?(property)
|
78
|
+
retrieve_property_multi_value(property)
|
79
|
+
else
|
80
|
+
retrieve_property_value(property)
|
81
|
+
end
|
82
|
+
rescue javax.jcr.PathNotFoundException
|
83
|
+
raise NilPropertyError.new("#{name} property not found on node")
|
84
|
+
end
|
85
|
+
|
86
|
+
def property_is_multi_valued?(property)
|
87
|
+
property.values
|
88
|
+
true
|
89
|
+
rescue javax.jcr.ValueFormatException
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
def retrieve_property_multi_value(property)
|
94
|
+
property.values.map {|value| retrieve_value(value) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def retrieve_property_value(property)
|
98
|
+
retrieve_value(property.value)
|
99
|
+
end
|
100
|
+
|
101
|
+
def retrieve_value(value)
|
102
|
+
property_type = PropertyType.name_from_value(value.type)
|
103
|
+
case property_type
|
104
|
+
when "String"
|
105
|
+
value.string
|
106
|
+
when "Boolean"
|
107
|
+
value.boolean
|
108
|
+
when "Double"
|
109
|
+
value.double
|
110
|
+
when "Long"
|
111
|
+
value.long
|
112
|
+
when "Date"
|
113
|
+
Time.at(value.date.time.time / 1000)
|
114
|
+
when "Name"
|
115
|
+
value.string # Not sure if these should be handled differently
|
116
|
+
else
|
117
|
+
raise PropertyTypeError.new("Unknown property type: #{property_type}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def write_attribute(name, value)
|
122
|
+
raise PropertyError.new("Illegal operation: cannot change jcr:primaryType property") if name == "jcr:primaryType"
|
123
|
+
|
124
|
+
if value.is_a? Array
|
125
|
+
values = value
|
126
|
+
val_fact = value_factory
|
127
|
+
j_values = []
|
128
|
+
values.each do |value|
|
129
|
+
j_values << val_fact.create_value(value.to_java)
|
130
|
+
end
|
131
|
+
j_node.set_property(name, j_values.to_java(Java::JavaxJcr::Value))
|
132
|
+
elsif value.is_a? Time or value.is_a? Date
|
133
|
+
calendar_value = Calendar.instance
|
134
|
+
calendar_value.set_time(value.to_java)
|
135
|
+
j_node.set_property(name, calendar_value)
|
136
|
+
else
|
137
|
+
j_node.set_property(name, value)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def save
|
142
|
+
if new?
|
143
|
+
j_node.parent.save
|
144
|
+
else
|
145
|
+
j_node.save
|
146
|
+
end
|
147
|
+
|
148
|
+
not changed?
|
149
|
+
end
|
150
|
+
|
151
|
+
def reload
|
152
|
+
j_node.refresh(false)
|
153
|
+
end
|
154
|
+
|
155
|
+
def [](name)
|
156
|
+
read_attribute(name)
|
157
|
+
end
|
158
|
+
|
159
|
+
def []=(name, value)
|
160
|
+
write_attribute(name, value)
|
161
|
+
end
|
162
|
+
|
163
|
+
def changed?
|
164
|
+
j_node.modified?
|
165
|
+
end
|
166
|
+
|
167
|
+
def new?
|
168
|
+
j_node.new?
|
169
|
+
end
|
170
|
+
|
171
|
+
def properties
|
172
|
+
props = {}
|
173
|
+
prop_iter = j_node.properties
|
174
|
+
while prop_iter.has_next
|
175
|
+
prop = prop_iter.next_property
|
176
|
+
unless prop.definition.protected?
|
177
|
+
prop_name = prop.name
|
178
|
+
props[prop_name] = self[prop_name]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
props
|
182
|
+
end
|
183
|
+
|
184
|
+
def protected_properties
|
185
|
+
props = {}
|
186
|
+
prop_iter = j_node.properties
|
187
|
+
while prop_iter.has_next
|
188
|
+
prop = prop_iter.next_property
|
189
|
+
if prop.definition.protected?
|
190
|
+
prop_name = prop.name
|
191
|
+
props[prop_name] = self[prop_name]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
props
|
195
|
+
end
|
196
|
+
|
197
|
+
def properties=(new_props)
|
198
|
+
# props.each do |name, value|
|
199
|
+
# self[name] = value
|
200
|
+
# end
|
201
|
+
property_names = (properties.keys + new_props.keys).uniq
|
202
|
+
property_names.each do |name|
|
203
|
+
self[name] = new_props[name]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def value_factory
|
208
|
+
session.value_factory
|
209
|
+
end
|
210
|
+
|
211
|
+
def destroy
|
212
|
+
path = self.path
|
213
|
+
parent_j_node = j_node.parent
|
214
|
+
j_node.remove
|
215
|
+
parent_j_node.save
|
216
|
+
# raise NodeError.new("Unable to destroy #{path} node") unless self.class.find(path).nil?
|
217
|
+
rescue javax.jcr.RepositoryException => e
|
218
|
+
raise NodeError.new("Unable to destroy #{path} node: #{e.message}")
|
219
|
+
end
|
220
|
+
|
221
|
+
def mixin_types
|
222
|
+
j_node.mixin_node_types.map(&:name)
|
223
|
+
end
|
224
|
+
|
225
|
+
def add_mixin(mixin_name)
|
226
|
+
j_node.add_mixin(mixin_name)
|
227
|
+
end
|
228
|
+
|
229
|
+
def remove_mixin(mixin_name)
|
230
|
+
j_node.remove_mixin(mixin_name)
|
231
|
+
end
|
232
|
+
|
233
|
+
def primary_type
|
234
|
+
self["jcr:primaryType"]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
class NodeError < Exception; end
|
239
|
+
class PropertyTypeError < Exception; end
|
240
|
+
class NilPropertyError < Exception; end
|
241
|
+
class PropertyError < Exception; end
|
242
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SafetyPin
|
2
|
+
class Query
|
3
|
+
class WhereCondition
|
4
|
+
attr_reader :name, :value, :comparator
|
5
|
+
|
6
|
+
def initialize(name, value, comparator = "=")
|
7
|
+
@name = name
|
8
|
+
@value = value
|
9
|
+
@comparator = comparator
|
10
|
+
end
|
11
|
+
|
12
|
+
def sql_fragment
|
13
|
+
"[#{name}] #{comparator} '#{value}'"
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(object)
|
17
|
+
self.name == object.name and self.value == object.value and self.comparator == object.comparator
|
18
|
+
end
|
19
|
+
|
20
|
+
def eql?(object)
|
21
|
+
self == object
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module SafetyPin
|
2
|
+
class Query
|
3
|
+
def self.execute(query_string)
|
4
|
+
query = query_manager.create_query(query_string, "JCR-SQL2")
|
5
|
+
node_iter = query.execute.nodes
|
6
|
+
nodes = []
|
7
|
+
while node_iter.has_next
|
8
|
+
nodes << Node.new(node_iter.next_node)
|
9
|
+
end
|
10
|
+
nodes
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.query_manager
|
14
|
+
JCR.session.workspace.query_manager
|
15
|
+
end
|
16
|
+
|
17
|
+
def sql
|
18
|
+
[select_statement, where_statement].compact.join(" ")
|
19
|
+
end
|
20
|
+
|
21
|
+
def type(type)
|
22
|
+
@type = type
|
23
|
+
end
|
24
|
+
|
25
|
+
def where(properties)
|
26
|
+
self.where_conditions = where_conditions + properties.map {|name, value| WhereCondition.new(name, value) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def within(path)
|
30
|
+
@within ||= []
|
31
|
+
if path.is_a? String
|
32
|
+
@within << path
|
33
|
+
elsif path.is_a? Array
|
34
|
+
@within += path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def where_conditions
|
39
|
+
@where_conditions ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
def where_conditions=(where_conditions)
|
43
|
+
@where_conditions = where_conditions
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def select_statement
|
48
|
+
type = @type || "nt:base"
|
49
|
+
"SELECT * FROM [#{type}]"
|
50
|
+
end
|
51
|
+
|
52
|
+
def where_statement
|
53
|
+
"WHERE #{where_sql.join(" AND ")}" unless where_conditions.empty? and within_path_conditions.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def where_sql
|
57
|
+
(where_conditions + within_path_conditions).map(&:sql_fragment)
|
58
|
+
end
|
59
|
+
|
60
|
+
def within_path_conditions
|
61
|
+
unless @within.nil?
|
62
|
+
@within.map {|path| WhereCondition.new("jcr:path", "#{path}%", "LIKE") }
|
63
|
+
else
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class InvalidQuery < Exception; end
|
70
|
+
end
|
data/lib/safety_pin.rb
ADDED
data/safety_pin.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/safety_pin/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Jordan Raine"]
|
6
|
+
gem.email = ["jnraine@gmail.com"]
|
7
|
+
gem.description = %q{An easy-to-use JCR connector for JRuby}
|
8
|
+
gem.summary = %q{JCR connector for JRuby}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "safety_pin"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = SafetyPin::VERSION
|
17
|
+
end
|
data/spec/jcr_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe SafetyPin::JCR do
|
4
|
+
it "should login to a remote SafetyPin::JCR" do
|
5
|
+
SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
|
6
|
+
SafetyPin::JCR.session.should be_a(Java::JavaxJcr::Session)
|
7
|
+
SafetyPin::JCR.should be_logged_in
|
8
|
+
SafetyPin::JCR.logout
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should logout of a remote SafetyPin::JCR" do
|
12
|
+
SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
|
13
|
+
SafetyPin::JCR.logout
|
14
|
+
SafetyPin::JCR.should be_logged_out
|
15
|
+
end
|
16
|
+
|
17
|
+
context ".parse_hostname" do
|
18
|
+
it "ensures the hostname ends with /crx/server" do
|
19
|
+
hostname = SafetyPin::JCR.parse_hostname("http://localhost:4502")
|
20
|
+
hostname.end_with?("/crx/server").should be_true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe "JCR-SQL2 example queries" do
|
4
|
+
before(:all) do
|
5
|
+
SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
|
6
|
+
end
|
7
|
+
|
8
|
+
before do
|
9
|
+
SafetyPin::JCR.session.refresh(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:all) do
|
13
|
+
SafetyPin::JCR.logout
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
@node = SafetyPin::Node.create("/content/foo")
|
18
|
+
@node.properties = {"bar" => "baz", "qux" => "qax"}
|
19
|
+
@node.save
|
20
|
+
end
|
21
|
+
|
22
|
+
after { @node.destroy }
|
23
|
+
|
24
|
+
it "can lookup a node based on property presence" do
|
25
|
+
nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE [nt:base].bar IS NOT NULL")
|
26
|
+
nodes.first["bar"].should_not be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can lookup a node based on a property's value" do
|
30
|
+
nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE bar = 'baz'")
|
31
|
+
values = nodes.map {|e| e["bar"] }.uniq
|
32
|
+
values.length.should eql(1)
|
33
|
+
values.first.should eql("baz")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can lookup a node based on multiple property values" do
|
37
|
+
nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE bar = 'baz' AND qux = 'qax'")
|
38
|
+
bar_values = nodes.map {|e| e["bar"] }.uniq
|
39
|
+
qux_values = nodes.map {|e| e["qux"] }.uniq
|
40
|
+
bar_values.length.should eql(1)
|
41
|
+
qux_values.length.should eql(1)
|
42
|
+
bar_values.first.should eql("baz")
|
43
|
+
qux_values.first.should eql("qax")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "can lookup a node based on node name" do
|
47
|
+
nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE NAME([nt:base]) = 'foo'")
|
48
|
+
names = nodes.map(&:name).uniq
|
49
|
+
names.length.should eql(1)
|
50
|
+
names.first.should eql("foo")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "can lookup a node based on a path" do
|
54
|
+
pending "can't do this yet"
|
55
|
+
nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] AS base WHERE base.[jcr:path] LIKE '/content/%'")
|
56
|
+
nodes.length.should be > 0
|
57
|
+
nodes.map(&:path).each {|name| name.starts_with?("/content").should be_true }
|
58
|
+
end
|
59
|
+
|
60
|
+
it "can lookup a node based on node type" do
|
61
|
+
nodes = SafetyPin::Query.execute("SELECT * FROM [cq:Page]")
|
62
|
+
nodes.first.primary_type.should eql("cq:Page")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "can lookup a node based on its node super type" do
|
66
|
+
nodes = SafetyPin::Query.execute("SELECT * FROM [rep:Authorizable]")
|
67
|
+
primary_types = nodes.map(&:primary_type)
|
68
|
+
primary_types.should include("rep:User")
|
69
|
+
end
|
70
|
+
|
71
|
+
# context "given some nodes and child nodes" do
|
72
|
+
# before do
|
73
|
+
# @parent2 = SafetyPin::Node.create("/content/foo2")
|
74
|
+
# @child1 = SafetyPin::Node.create("/content/foo2/child")
|
75
|
+
# @child1.properties = {"bar" => "baz", "qux" => "qax", "child" => "yes"}
|
76
|
+
# @child1.save
|
77
|
+
# @child2 = SafetyPin::Node.create("/content/foo/child")
|
78
|
+
# @child2.properties = {"bar" => "baz", "qux" => "qax", "child" => "yes"}
|
79
|
+
# @child2.save
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# after do
|
83
|
+
# @parent2.destroy
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# it "can lookup a node based on nested WHERE conditions" do
|
87
|
+
# sql_statement = "SELECT * FROM [nt:base]
|
88
|
+
# WHERE [jcr:path] LIKE '/content/foo%'"
|
89
|
+
# nodes = SafetyPin::Query.execute(sql_statement)
|
90
|
+
# nodes.map(&:path).sort.should eql(["/content/foo/child", "/content/foo2/child"])
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
end
|
data/spec/node_spec.rb
ADDED
@@ -0,0 +1,494 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe SafetyPin::Node do
|
4
|
+
before(:all) do
|
5
|
+
SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
|
6
|
+
end
|
7
|
+
|
8
|
+
before do
|
9
|
+
SafetyPin::JCR.session.refresh(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:all) do
|
13
|
+
SafetyPin::JCR.logout
|
14
|
+
end
|
15
|
+
|
16
|
+
context ".find" do
|
17
|
+
context "given a node name" do
|
18
|
+
context "that exists" do
|
19
|
+
it "should return a node with a matching path" do
|
20
|
+
SafetyPin::Node.find("/content").path.should eql("/content")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "that doesn't exist" do
|
25
|
+
it "should return nil" do
|
26
|
+
SafetyPin::Node.find("/foo/bar/baz").should be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "complains if the path isn't an absolute path" do
|
32
|
+
lambda { node = SafetyPin::Node.find("content") }.should raise_exception(ArgumentError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context ".session" do
|
37
|
+
it "should return a session" do
|
38
|
+
SafetyPin::Node.session.should be_a(Java::JavaxJcr::Session)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "#session" do
|
43
|
+
it "should return a session" do
|
44
|
+
SafetyPin::Node.find("/content").session.should be_a(Java::JavaxJcr::Session)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should cache session in an instance variable" do
|
48
|
+
node = SafetyPin::Node.find("/content")
|
49
|
+
node.session
|
50
|
+
node.instance_eval { @session }.should be_a(Java::JavaxJcr::Session)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "#children" do
|
55
|
+
it "should return an array of child nodes" do
|
56
|
+
SafetyPin::Node.find("/content").children.first.should be_a(SafetyPin::Node)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "#child" do
|
61
|
+
context "given a node name" do
|
62
|
+
let(:node) { SafetyPin::Node.find("/") }
|
63
|
+
|
64
|
+
context "that exists" do
|
65
|
+
it "should return a child node with a matching name" do
|
66
|
+
node.child("content").name.should eql("content")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return a grandchild node given a relative path" do
|
70
|
+
SafetyPin::Node.create("/content/foo")
|
71
|
+
node.child("content/foo").name.should eql("foo")
|
72
|
+
SafetyPin::Node.find("/content/foo").destroy
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "that doesn't exist" do
|
77
|
+
it "should return nil" do
|
78
|
+
node.child("foobarbaz").should be_nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "#name" do
|
85
|
+
it "should return a string name" do
|
86
|
+
SafetyPin::Node.find("/content").name.should eql("content")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#save" do
|
91
|
+
context "on an existing node with changes" do
|
92
|
+
before do
|
93
|
+
@node = SafetyPin::Node.create("/content/foo")
|
94
|
+
@node["bar"] = "baz"
|
95
|
+
end
|
96
|
+
|
97
|
+
after { @node.destroy }
|
98
|
+
|
99
|
+
it "should save the changes to the JCR" do
|
100
|
+
@node.save
|
101
|
+
@node.reload
|
102
|
+
@node["bar"].should eql("baz")
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should return true if the save was successful" do
|
106
|
+
save_successful = @node.save
|
107
|
+
(save_successful and not @node.changed?).should be_true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "on a new node" do
|
112
|
+
it "should save the node" do
|
113
|
+
node = SafetyPin::Node.build("/content/foo")
|
114
|
+
node.save.should be_true
|
115
|
+
node.destroy
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should save changes in parent node" do
|
119
|
+
parent_node = SafetyPin::Node.create("/content/foo")
|
120
|
+
node = SafetyPin::Node.build("/content/foo/bar")
|
121
|
+
parent_node["baz"] = "qux"
|
122
|
+
parent_node.should be_changed
|
123
|
+
node.save
|
124
|
+
parent_node.should_not be_changed
|
125
|
+
node.destroy
|
126
|
+
parent_node.destroy
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "#read_attribute" do
|
132
|
+
context "on an existing node" do
|
133
|
+
before { @node = SafetyPin::Node.create("/content/foo") }
|
134
|
+
after { @node.destroy }
|
135
|
+
|
136
|
+
it "should return the string value of a string property" do
|
137
|
+
@node["foo"] = "bar"
|
138
|
+
@node.read_attribute("foo").should eql("bar")
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should return the boolean value of a boolean property" do
|
142
|
+
@node["foo"] = true
|
143
|
+
@node.read_attribute("foo").should eql(true)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should return the double value of a double (or Ruby float) property" do
|
147
|
+
@node["foo"] = 3.14
|
148
|
+
@node.read_attribute("foo").should eql(3.14)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should return the long value of a long (or Ruby Fixnum) property" do
|
152
|
+
@node["foo"] = 42
|
153
|
+
@node.read_attribute("foo").should eql(42)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should return the time value of a date property" do
|
157
|
+
time = Time.now
|
158
|
+
@node["foo"] = time
|
159
|
+
@node.read_attribute("foo").to_s.should eql(time.to_s)
|
160
|
+
end
|
161
|
+
|
162
|
+
context "given a multi-value property" do
|
163
|
+
it "should return an array of values" do
|
164
|
+
@node["foo"] = ["one", "two"]
|
165
|
+
@node.read_attribute("foo").should eql(["one", "two"])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should return the string value of a name property" do
|
171
|
+
SafetyPin::Node.find("/")["jcr:primaryType"].should eql("rep:root")
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should throw an exception when accessing a non-existent (nil) property" do
|
175
|
+
lambda { SafetyPin::Node.build("/content/foo").read_attribute("foo-bar-baz") }.should raise_error(SafetyPin::NilPropertyError)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "#write_attribute" do
|
180
|
+
before { @node = SafetyPin::Node.create("/content/foo") }
|
181
|
+
after { @node.destroy }
|
182
|
+
|
183
|
+
context "given a single value" do
|
184
|
+
it "should set a string property value" do
|
185
|
+
@node.write_attribute("foo", "bar")
|
186
|
+
@node.save
|
187
|
+
@node.reload
|
188
|
+
@node["foo"].should eql("bar")
|
189
|
+
end
|
190
|
+
|
191
|
+
context "given a Time object" do
|
192
|
+
it "should set a date property value" do
|
193
|
+
time = Time.now
|
194
|
+
@node.write_attribute("foo", time)
|
195
|
+
@node.save
|
196
|
+
@node.reload
|
197
|
+
@node["foo"].to_s.should eql(time.to_s)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context "given an array of values" do
|
203
|
+
context "of the same type" do
|
204
|
+
it "should set a multi-value string array" do
|
205
|
+
@node.write_attribute("foo", ["one", "two"])
|
206
|
+
@node.save
|
207
|
+
@node.reload
|
208
|
+
@node["foo"].should eql(["one", "two"])
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context "given a null value" do
|
214
|
+
it "should remove the property" do
|
215
|
+
@node["foo"] = "bar"
|
216
|
+
@node.write_attribute("foo", nil)
|
217
|
+
lambda { @node["foo"] }.should raise_error(SafetyPin::NilPropertyError)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context "changing jcr:primaryType property" do
|
222
|
+
it "should raise an error" do
|
223
|
+
lambda { @node.write_attribute("jcr:primaryType", "nt:folder") }.should raise_error(SafetyPin::PropertyError)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context "#reload" do
|
229
|
+
before { @node = SafetyPin::Node.create("/content/foo") }
|
230
|
+
after { @node.destroy }
|
231
|
+
|
232
|
+
it "should discard pending changes" do
|
233
|
+
@node["foo"] = "bar"
|
234
|
+
@node.reload
|
235
|
+
lambda { @node.read_attribute("foo") }.should raise_error(SafetyPin::NilPropertyError)
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should not discard changes for another node" do
|
239
|
+
@node["bar"] = "baz"
|
240
|
+
another_node = SafetyPin::Node.find("/content")
|
241
|
+
another_node["bar"] = "baz"
|
242
|
+
@node.reload
|
243
|
+
lambda { @node["bar"] }.should raise_error(SafetyPin::NilPropertyError)
|
244
|
+
another_node["bar"].should eql("baz")
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "#[]" do
|
249
|
+
it "should return the value of a given property name" do
|
250
|
+
node = SafetyPin::Node.create("/content/foo")
|
251
|
+
node.write_attribute("bar","baz")
|
252
|
+
node.save
|
253
|
+
node["bar"].should eql("baz")
|
254
|
+
node.destroy
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe "#[]=" do
|
259
|
+
it "should set the value of a given property name" do
|
260
|
+
node = SafetyPin::Node.create("/content/foo")
|
261
|
+
node.write_attribute("bar","baz")
|
262
|
+
node["bar"] = "qux"
|
263
|
+
node["bar"].should eql("qux")
|
264
|
+
node.destroy
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
context "#changed?" do
|
269
|
+
let(:node) { SafetyPin::Node.find("/content") }
|
270
|
+
|
271
|
+
it "should return false if the node does not have unsaved changes" do
|
272
|
+
node.should_not be_changed
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should return true if the node has unsaved changes" do
|
276
|
+
node["foo"] = "bar"
|
277
|
+
node.should be_changed
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
context "#new?" do
|
282
|
+
it "should return true if node has never been saved to JCR" do
|
283
|
+
SafetyPin::Node.build("/content/foo").should be_new
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should return false if node has been saved to JCR" do
|
287
|
+
SafetyPin::Node.find("/content").should_not be_new
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe "#properties" do
|
292
|
+
it "should return hash of all unprotected properties" do
|
293
|
+
SafetyPin::Node.find("/").properties.should eql({"sling:target"=>"/index.html", "sling:resourceType"=>"sling:redirect"})
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
describe "#properties=" do
|
298
|
+
before { @node = SafetyPin::Node.create("/content/foo") }
|
299
|
+
after { @node.destroy }
|
300
|
+
|
301
|
+
it "should set the properties of a node" do
|
302
|
+
@node.properties = {"foo" => "bar"}
|
303
|
+
@node.properties.should eql({"foo" => "bar"})
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should set unset properties not specified in hash" do
|
307
|
+
@node["foo"] = "bar"
|
308
|
+
@node.properties = {"baz" => "qux"}
|
309
|
+
@node.properties.should eql({"baz" => "qux"})
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
describe "#protected_properties" do
|
314
|
+
it "should return hash of all protected properties" do
|
315
|
+
SafetyPin::Node.find("/").protected_properties.should eql({"jcr:primaryType"=>"rep:root", "jcr:mixinTypes"=>["rep:AccessControllable", "rep:RepoAccessControllable"]})
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe "#mixin_types" do
|
320
|
+
before do
|
321
|
+
@node = SafetyPin::Node.create("/content/foo")
|
322
|
+
@node.j_node.add_mixin("mix:created")
|
323
|
+
@node.save
|
324
|
+
end
|
325
|
+
|
326
|
+
after { @node.destroy }
|
327
|
+
|
328
|
+
it "should return the mixin types of a node" do
|
329
|
+
@node.mixin_types.should eql(["mix:created"])
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe "#add_mixin" do
|
334
|
+
before { @node = SafetyPin::Node.create("/content/foo") }
|
335
|
+
after { @node.destroy }
|
336
|
+
|
337
|
+
it "should add a mixin type to node" do
|
338
|
+
@node.add_mixin("mix:created")
|
339
|
+
@node.save
|
340
|
+
@node.mixin_types.should eql(["mix:created"])
|
341
|
+
end
|
342
|
+
|
343
|
+
it "should require a save before the mixin addition is detected" do
|
344
|
+
@node.add_mixin("mix:created")
|
345
|
+
@node.mixin_types.should eql([])
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe "#remove_mixin" do
|
350
|
+
before do
|
351
|
+
@node = SafetyPin::Node.create("/content/foo")
|
352
|
+
@node.add_mixin("mix:created")
|
353
|
+
@node.save
|
354
|
+
end
|
355
|
+
|
356
|
+
after { @node.destroy }
|
357
|
+
|
358
|
+
it "should remove a mixin type from a node" do
|
359
|
+
@node.mixin_types.should eql(["mix:created"])
|
360
|
+
@node.remove_mixin("mix:created")
|
361
|
+
@node.save
|
362
|
+
@node.mixin_types.should eql([])
|
363
|
+
end
|
364
|
+
|
365
|
+
it "should require a save before the mixin removal is detected" do
|
366
|
+
@node.remove_mixin("mix:created")
|
367
|
+
@node.mixin_types.should eql(["mix:created"])
|
368
|
+
@node.reload
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
describe ".build" do
|
373
|
+
context "given an absolute path" do
|
374
|
+
it "should build and return a property-less, unsaved nt:unstructured child node" do
|
375
|
+
node = SafetyPin::Node.build("/content/foo")
|
376
|
+
node.should be_new
|
377
|
+
node.properties.should eql({})
|
378
|
+
end
|
379
|
+
|
380
|
+
context "and a node type string" do
|
381
|
+
it "should create an unsaved node of the given type" do
|
382
|
+
node = SafetyPin::Node.build("/content/foo", "nt:folder")
|
383
|
+
node.should be_new
|
384
|
+
node["jcr:primaryType"].should eql("nt:folder")
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
context "that already exists" do
|
389
|
+
it "should raise an error" do
|
390
|
+
lambda { SafetyPin::Node.build("/content") }.should raise_error(SafetyPin::NodeError)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
context "given an absolute path with a non-existent parent node" do
|
396
|
+
it "should raise an error" do
|
397
|
+
lambda { SafetyPin::Node.build("/content/foo/bar/baz") }.should raise_error(SafetyPin::NodeError)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
context "given a relative path" do
|
402
|
+
it "should raise an error" do
|
403
|
+
lambda { SafetyPin::Node.build("content/foo") }.should raise_error(ArgumentError)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
describe ".create" do
|
409
|
+
context "given a path" do
|
410
|
+
it "should build and save a node" do
|
411
|
+
node = SafetyPin::Node.create("/content/foo")
|
412
|
+
node.should be_a(SafetyPin::Node)
|
413
|
+
node.destroy
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
context "given a path and a node type" do
|
418
|
+
it "should build and save a node of the specified type" do
|
419
|
+
node = SafetyPin::Node.create("/content/foo", "nt:folder")
|
420
|
+
SafetyPin::Node.find("/content/foo").should_not be_nil
|
421
|
+
node.destroy
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
context "#value_factory" do
|
427
|
+
it "should return a value factory instance" do
|
428
|
+
SafetyPin::Node.find("/content").value_factory.should be_a(Java::JavaxJcr::ValueFactory)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
describe "#property_is_multi_valued" do
|
433
|
+
it "should return true if property is multi-valued" do
|
434
|
+
node = SafetyPin::Node.create("/content/foo")
|
435
|
+
node["bar"] = ["baz", "qux"]
|
436
|
+
node.save
|
437
|
+
property = node.j_node.get_property("bar")
|
438
|
+
node.property_is_multi_valued?(property).should be_true
|
439
|
+
node.destroy
|
440
|
+
end
|
441
|
+
|
442
|
+
it "should return false if property is not multi-valued" do
|
443
|
+
node = SafetyPin::Node.create("/content/foo")
|
444
|
+
node["bar"] = "baz"
|
445
|
+
node.save
|
446
|
+
property = node.j_node.get_property("bar")
|
447
|
+
node.property_is_multi_valued?(property).should be_false
|
448
|
+
node.destroy
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
describe "#destroy" do
|
453
|
+
it "should remove node from JCR" do
|
454
|
+
path = "/content/foo"
|
455
|
+
node = SafetyPin::Node.create(path)
|
456
|
+
node.destroy
|
457
|
+
SafetyPin::Node.find(path).should be_nil
|
458
|
+
end
|
459
|
+
|
460
|
+
it "should save changes in parent node" do
|
461
|
+
parent_node = SafetyPin::Node.create("/content/foo")
|
462
|
+
node = SafetyPin::Node.create("/content/foo/bar")
|
463
|
+
parent_node["baz"] = "qux"
|
464
|
+
parent_node.should be_changed
|
465
|
+
node.destroy
|
466
|
+
parent_node.should_not be_changed
|
467
|
+
parent_node.destroy
|
468
|
+
end
|
469
|
+
|
470
|
+
context "when it fails" do
|
471
|
+
before do
|
472
|
+
@node = SafetyPin::Node.create("/content/foo")
|
473
|
+
@node.add_mixin("mix:created")
|
474
|
+
@node.save
|
475
|
+
end
|
476
|
+
|
477
|
+
after { @node.reload; @node.destroy }
|
478
|
+
|
479
|
+
it "should raise an error" do
|
480
|
+
@node.remove_mixin("mix:created") # make node unremoveable
|
481
|
+
lambda { @node.destroy }.should raise_error(SafetyPin::NodeError)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
describe "#primary_type" do
|
487
|
+
before { @node = SafetyPin::Node.create("/content/foo") }
|
488
|
+
after { @node.destroy }
|
489
|
+
|
490
|
+
it "should return the primary type of the node" do
|
491
|
+
@node.primary_type.should eql("nt:unstructured")
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
data/spec/query_spec.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe SafetyPin::Query do
|
4
|
+
before(:all) do
|
5
|
+
SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
|
6
|
+
end
|
7
|
+
|
8
|
+
before do
|
9
|
+
SafetyPin::JCR.session.refresh(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:all) do
|
13
|
+
SafetyPin::JCR.logout
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:query) { SafetyPin::Query.new }
|
17
|
+
|
18
|
+
describe ".execute" do
|
19
|
+
before do
|
20
|
+
@node = SafetyPin::Node.create("/content/foo")
|
21
|
+
@node["bar"] = "baz"
|
22
|
+
@node.save
|
23
|
+
end
|
24
|
+
|
25
|
+
after { @node.destroy }
|
26
|
+
|
27
|
+
it "should lookup nodes given a valid JCR-SQL2 query string" do
|
28
|
+
nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE [nt:base].bar IS NOT NULL")
|
29
|
+
nodes.first["bar"].should_not be_nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#sql" do
|
34
|
+
it "should compile a default query string to return all nodes" do
|
35
|
+
query.sql.should eql("SELECT * FROM [nt:base]")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should compile a query string to search for nodes of a specific type" do
|
39
|
+
query.type("cq:Page")
|
40
|
+
query.sql.should eql("SELECT * FROM [cq:Page]")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should compile a query string to search for a node with a property and value" do
|
44
|
+
query.where("foo" => "bar")
|
45
|
+
query.sql.should eql("SELECT * FROM [nt:base] WHERE [foo] = 'bar'")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should compile a query string to search for a node with multiple properties and values" do
|
49
|
+
query.where("foo" => "bar", "baz" => "quux")
|
50
|
+
query.sql.should eql("SELECT * FROM [nt:base] WHERE [foo] = 'bar' AND [baz] = 'quux'")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should compile a query string to search for a node within a path" do
|
54
|
+
query.within("/content/")
|
55
|
+
query.sql.should eql("SELECT * FROM [nt:base] WHERE [jcr:path] LIKE '/content/%'")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should compile a query string to search a node within multiple nodes" do
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#within" do
|
64
|
+
context "given string paths" do
|
65
|
+
it "should append to @within instance variable" do
|
66
|
+
query.within("/foo")
|
67
|
+
query.within("/bar")
|
68
|
+
query.instance_eval { @within }.should eql(["/foo", "/bar"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "given an array of string paths" do
|
73
|
+
it "should append to @within instance variable" do
|
74
|
+
query.within("/foo")
|
75
|
+
query.within(["/bar", "/baz"])
|
76
|
+
query.instance_eval { @within }.should eql(["/foo", "/bar", "/baz"])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "given an invalid argument" do
|
81
|
+
it "should ignore it" do
|
82
|
+
query.within("/foo")
|
83
|
+
query.within(nil)
|
84
|
+
query.instance_eval { @within }.should eql(["/foo"])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#where" do
|
90
|
+
it "should append to where_conditions" do
|
91
|
+
query.where("foo" => "bar")
|
92
|
+
query.where("baz" => "quux")
|
93
|
+
query.where_conditions.should eql([SafetyPin::Query::WhereCondition.new("foo", "bar"), SafetyPin::Query::WhereCondition.new("baz", "quux")])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#type" do
|
98
|
+
it "should set the node type" do
|
99
|
+
query = SafetyPin::Query.new
|
100
|
+
query.type("cq:Page")
|
101
|
+
query.instance_eval { @type }.should eql("cq:Page")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#where_conditions" do
|
106
|
+
it "should return an empty array by default" do
|
107
|
+
query.where_conditions.should eql([])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# it "should query for nodes beneath a specific path" do
|
113
|
+
# pending
|
114
|
+
# nodes = SafetyPin::Query.path("/content/sfu")
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# it "should query for nodes with a specific property" do
|
118
|
+
# pending
|
119
|
+
# nodes = SafetyPin::Query.where("cq:Template" => "/apps/sfu/templates/basicpage")
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# it "should chain together query methods to build a query" do
|
123
|
+
# pending
|
124
|
+
# nodes = SafetyPin::Query.path("/content").type("nt:unstructured").where("jcr:title" => "Open House")
|
125
|
+
# end
|
126
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SafetyPin::Query::WhereCondition do
|
4
|
+
it "should be instantiated with a name, value, and comparator" do
|
5
|
+
where_condition = SafetyPin::Query::WhereCondition.new("foo", "bar", "LIKE")
|
6
|
+
where_condition.should_not be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should generate a SQL WHERE string fragment" do
|
10
|
+
where_condition = SafetyPin::Query::WhereCondition.new("foo", "bar", "LIKE")
|
11
|
+
where_condition.sql_fragment.should eql("[foo] LIKE 'bar'")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be equal based on its main attributes" do
|
15
|
+
condition1 = SafetyPin::Query::WhereCondition.new("foo", "bar", "LIKE")
|
16
|
+
condition2 = SafetyPin::Query::WhereCondition.new("foo", "bar", "LIKE")
|
17
|
+
condition1.should == condition2
|
18
|
+
condition1.should eql(condition2)
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: safety_pin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jordan Raine
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-05-10 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: An easy-to-use JCR connector for JRuby
|
17
|
+
email:
|
18
|
+
- jnraine@gmail.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- .rspec
|
27
|
+
- .rvmrc
|
28
|
+
- Gemfile
|
29
|
+
- Gemfile.lock
|
30
|
+
- LICENSE
|
31
|
+
- README.md
|
32
|
+
- Rakefile
|
33
|
+
- lib/safety_pin.rb
|
34
|
+
- lib/safety_pin/jcr.rb
|
35
|
+
- lib/safety_pin/node.rb
|
36
|
+
- lib/safety_pin/query.rb
|
37
|
+
- lib/safety_pin/query/where_condition.rb
|
38
|
+
- lib/safety_pin/version.rb
|
39
|
+
- safety_pin.gemspec
|
40
|
+
- spec/jcr_spec.rb
|
41
|
+
- spec/jcr_sql2_spec.rb
|
42
|
+
- spec/node_spec.rb
|
43
|
+
- spec/query_spec.rb
|
44
|
+
- spec/spec_helper.rb
|
45
|
+
- spec/where_condition_spec.rb
|
46
|
+
homepage: ""
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.15
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: JCR connector for JRuby
|
73
|
+
test_files:
|
74
|
+
- spec/jcr_spec.rb
|
75
|
+
- spec/jcr_sql2_spec.rb
|
76
|
+
- spec/node_spec.rb
|
77
|
+
- spec/query_spec.rb
|
78
|
+
- spec/spec_helper.rb
|
79
|
+
- spec/where_condition_spec.rb
|