activedocument 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,225 @@
1
+ # Copyright 2010 Mark Logic, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require 'ActiveDocument/mark_logic_http'
17
+ require 'rubygems'
18
+ require 'nokogiri'
19
+ require 'yaml'
20
+ require 'ActiveDocument/mark_logic_query_builder'
21
+ require 'ActiveDocument/search_results'
22
+ require 'ActiveDocument/finder'
23
+ require "ActiveDocument/inheritable"
24
+
25
+ # The ActiveXML module is used as a namespace for all classes relating to the ActiveXML functionality.
26
+ # ActiveXML::Base is the class that should be extended in order to make use of this functionality in your own
27
+ # domain objects.
28
+ #
29
+ module ActiveDocument
30
+
31
+ # Developers should extend this class to create their own domain classes
32
+ # -------------------
33
+ # = Usage
34
+ # -------------------
35
+ # == Dynamic Finders
36
+ # ActiveDocument::Base provides extensive methods for finding matching documents based on a variety of criteria.
37
+ # === Find By Element
38
+ # Accessed via find_by_ELEMENT method where Element = the name of your element. Executes a search for all documents
39
+ # with an element ELEMENT that contains the value passed in to the method call.
40
+ # The signature of this dynamic finder is:
41
+ # <tt>find_by_ELEMENT(value, root [optional], element_namespace [optional], root_namespace [optional])</tt>
42
+ #
43
+ # Parameters details are as follows:
44
+ # <b>Value:</b> the text to be found within the given element. This is a mandatory parameter
45
+ # <b>Namespace:</b> The namespace in which the element being searched occurs. This is an optional element. If provided,
46
+ # it will be used in the search and will override any default values. If no namespace if provided then the code will
47
+ # attempt to dynamically determine the namespace. First, if the element name is contained in the namespaces hash
48
+ # then that namespace is used. If the element name is not found then the _default_namespace_
49
+ # is used. If there is no default namespace, then no namespace is used.
50
+ # -------------------
51
+ # == Dynamic Accessors
52
+ # In addition to the ability to access the underlying XML document (as a Nokogiri XML Document) you have the ability
53
+ # to access the XML as attributes of your domain object via dynamic attribute accessors (eg. domain_object.element_name) The rules for using accessors
54
+ # are as follows:
55
+ # 1. If the element_name is a simple type (i.e. text node with no children <tt><example>text</exmample></tt>)
56
+ # 1. If there is only one occurence of the element, return its text value
57
+ # 2. If there are multiple occurences of the element, return a list of each text value
58
+ # 2. If the element_name is a complex type (e.g. <tt><example><text>hi</text></example></tt>)
59
+ # 1. If there is only one ocurence of the element then return it as a Nokoguri Element
60
+ # 2. If there are multiple occurences of the element, return a list of Nokoguri Elements
61
+ #
62
+ # More complex dynamic accessors are also supported. They still adhere to the rules above, but instead of just looking
63
+ # for an element anywhere in the document, you can be more specific. For example, domain_object.chapter.paragraph
64
+ # will find all paragraph elements that are children of chapter elements.
65
+ # -------------------
66
+ class Base < Finder
67
+ include ClassLevelInheritableAttributes
68
+ inheritable_attributes_list :namespaces, :default_namespace, :root
69
+ @namespaces = Hash.new
70
+ @default_namespace = String.new
71
+ attr_reader :document, :uri
72
+
73
+
74
+ # create a new instance with an optional xml string to use for constructing the model
75
+ def initialize(xml_string = nil, uri = nil)
76
+ unless xml_string.nil?
77
+ @document = Nokogiri::XML(xml_string) do |config|
78
+ config.noblanks
79
+ end
80
+ end
81
+ @root = self.class.to_s
82
+ @uri = uri
83
+ end
84
+
85
+ # Returns the root element for this object
86
+ def root
87
+ @root
88
+ end
89
+
90
+ # Sets the root element for this object
91
+ def root=(value)
92
+ @root = value
93
+ end
94
+
95
+ # enables the dynamic finders
96
+ def method_missing(method_id, *arguments, &block)
97
+ @@log.debug("ActiveDocument::Base at line #{__LINE__}: method called is #{method_id} with arguments #{arguments}")
98
+ method = method_id.to_s
99
+ if method =~ /^(\w*)$/ # methods with no '.' in them and not ending in '='
100
+ if arguments.length > 0
101
+ super
102
+ end
103
+ access_element $1
104
+ end
105
+ end
106
+
107
+ def access_element(element)
108
+ xpath = ""
109
+ xpath = "//" unless self.instance_of? PartialResult
110
+ namespace = self.class.namespace_for_element(element)
111
+ element = "ns:#{element}" unless namespace.nil? || namespace.empty?
112
+ xpath << element
113
+ if namespace.nil?
114
+ nodes = @document.xpath(xpath)
115
+ else
116
+ nodes = @document.xpath(xpath, {'ns' => namespace})
117
+ end
118
+ evaluate_nodeset(nodes)
119
+
120
+ end
121
+
122
+
123
+ def evaluate_nodeset(result_nodeset)
124
+ if result_nodeset.length == 1 # found one match
125
+ if result_nodeset[0].children.length == 1 and result_nodeset[0].children[0].type == Nokogiri::XML::Node::TEXT_NODE
126
+ result_nodeset[0].text
127
+ elsif result_nodeset[0].children.length >1 # we are now dealing with complex nodes
128
+ PartialResult.new(result_nodeset)
129
+ end
130
+ elsif result_nodeset.length >1 # multiple matches
131
+ if result_nodeset.all? {|node| node.children.length == 1} and result_nodeset.all? {|node| node.children[0].type == Nokogiri::XML::Node::TEXT_NODE}
132
+ # we have multiple simple text nodes
133
+ result_nodeset.collect {|node| node.text}
134
+ else
135
+ # we have multiple complex elements
136
+ PartialResult.new(result_nodeset)
137
+ end
138
+ end
139
+ end
140
+
141
+ class PartialResult < self
142
+ def initialize(nodeset)
143
+ @document = nodeset
144
+ @root = nodeset[0].name
145
+ end
146
+
147
+ def to_s
148
+ @document.to_s
149
+ end
150
+
151
+
152
+ end
153
+
154
+ class << self
155
+ attr_reader :namespaces, :default_namespace, :root
156
+
157
+ def namespaces(namespace_hash)
158
+ @namespaces = namespace_hash
159
+ end
160
+
161
+ def add_namespace(element, uri)
162
+ @namespaces[element.to_s] == uri
163
+ end
164
+
165
+ def remove_namespace(element)
166
+ @namespaces.delete element
167
+ end
168
+
169
+ def default_namespace(namespace)
170
+ @default_namespace = namespace # todo should this just be an entry in namespaces?
171
+ end
172
+
173
+ # enables the dynamic finders
174
+ def method_missing(method_id, *arguments, &block)
175
+ @@log.debug("ActiveDocument::Base at line #{__LINE__}: method called is #{method_id} with arguments #{arguments}")
176
+ method = method_id.to_s
177
+ # identify element search methods
178
+ if method =~ /find_by_(.*)$/ and arguments.length > 0
179
+ value = arguments[0]
180
+ element = $1.to_sym
181
+ if arguments[1]
182
+ root = arguments[1]
183
+ else
184
+ root = @root
185
+ end
186
+ if arguments[2]
187
+ element_namespace = arguments[2]
188
+ else
189
+ element_namespace = namespace_for_element(element)
190
+ end
191
+ if arguments[3]
192
+ root_namespace = arguments[3]
193
+ else
194
+ root_namespace = namespace_for_element(root)
195
+ end
196
+ execute_finder(element, value, root, element_namespace, root_namespace)
197
+ end
198
+
199
+ end
200
+
201
+ # Returns an ActiveXML object representing the requested information
202
+ def load(uri)
203
+ self.new(@@ml_http.send_xquery(@@xquery_builder.load(uri)), uri)
204
+ end
205
+
206
+ # Finds all documents of this type that contain the word anywhere in their structure
207
+ def find_by_word(word, root=@root, namespace=@default_namespace)
208
+ SearchResults.new(@@ml_http.send_xquery(@@xquery_builder.find_by_word(word, root, namespace)))
209
+ end
210
+
211
+ def namespace_for_element(element)
212
+ namespace = nil
213
+ if @namespaces[element]
214
+ namespace = @namespaces[element]
215
+ else
216
+ namespace = @default_namespace unless @default_namespace.nil?
217
+ end
218
+ namespace
219
+ end
220
+ end # end inner class
221
+
222
+ end # end class
223
+
224
+
225
+ end # end module
@@ -0,0 +1,100 @@
1
+ # Copyright 2010 Mark Logic, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'ActiveDocument/mark_logic_http'
16
+ require 'ActiveDocument/mark_logic_query_builder'
17
+ require 'rubygems'
18
+ require 'nokogiri'
19
+ require 'ActiveDocument/search_results'
20
+ require 'logger'
21
+
22
+ module ActiveDocument
23
+
24
+ class Finder
25
+
26
+
27
+ @@xquery_builder = ActiveDocument::MarkLogicQueryBuilder.new
28
+
29
+ def self.config(yaml_file)
30
+ config = YAML.load_file(yaml_file)
31
+ @@ml_http = ActiveDocument::MarkLogicHTTP.new(config['uri'], config['user_name'], config['password'])
32
+ configure_logger(config)
33
+ end
34
+
35
+ # enables the dynamic finders
36
+ def self.method_missing(method_id, *arguments, &block)
37
+ @@log.debug("ActiveDocument::Finder at line #{__LINE__}: method called is #{method_id} with arguments #{arguments}")
38
+ method = method_id.to_s
39
+ # identify finder methods
40
+ if method =~ /find_by_(.*)$/ and arguments.length > 0
41
+ namespace = arguments[1] if arguments.length == 2
42
+ execute_finder($1.to_sym, arguments[0], nil, namespace)
43
+ else
44
+ puts "missed"
45
+ end
46
+ end
47
+
48
+ def self.execute_finder(element, value, root = nil, element_namespace = nil, root_namespace = nil)
49
+ xquery = @@xquery_builder.find_by_element(element, value, root, element_namespace, root_namespace)
50
+ @@log.info("Finder.execute_finder at line #{__LINE__}: #{xquery}")
51
+ SearchResults.new(@@ml_http.send_xquery(xquery))
52
+ end
53
+
54
+ def self.search(search_string, start = 1, page_length = 10, options = nil)
55
+ if start.nil? : start = 1 end
56
+ if page_length.nil? : page_length = 10 end
57
+ search_text = @@xquery_builder.search(search_string, start, page_length, options)
58
+ SearchResults.new(@@ml_http.send_xquery(search_text)) # todo support options node
59
+ end
60
+ private
61
+
62
+ def self.configure_logger(config)
63
+
64
+ begin
65
+ log_location = if config['logger']['file']
66
+ config['logger']['file']
67
+ else
68
+ STDERR
69
+ end
70
+ log_level = case config['logger']['level']
71
+ when "debug" then
72
+ Logger::DEBUG
73
+ when "info" then
74
+ Logger::INFO
75
+ when "warn" then
76
+ Logger::WARN
77
+ when "error" then
78
+ Logger::ERROR
79
+ when "fatal" then
80
+ Logger::FATAL
81
+ else
82
+ Logger::WARN
83
+ end
84
+
85
+ rotation = if config['logger']['rotation']
86
+ config['logger']['rotation']
87
+ else
88
+ "daily"
89
+ end
90
+ file = open(log_location, File::WRONLY | File::APPEND | File::CREAT)
91
+ @@log = Logger.new(file, rotation)
92
+ @@log.level = log_level
93
+ rescue StandardError => oops
94
+ @@log = Logger.new(STDERR)
95
+ @@log.level = Logger::WARN
96
+ end
97
+ end
98
+ end
99
+
100
+ end
@@ -0,0 +1,25 @@
1
+ module ClassLevelInheritableAttributes
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ end
5
+
6
+ module ClassMethods
7
+ def inheritable_attributes_list(*args)
8
+ @inheritable_attributes_list ||= [:inheritable_attributes_list]
9
+ @inheritable_attributes_list += args
10
+ args.each do |arg|
11
+ class_eval %(
12
+ class << self; attr_accessor :#{arg} end
13
+ )
14
+ end
15
+ @inheritable_attributes_list
16
+ end
17
+
18
+ def inherited(subclass)
19
+ @inheritable_attributes_list.each do |inheritable_attribute|
20
+ instance_var = "@#{inheritable_attribute}"
21
+ subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,97 @@
1
+ # Copyright 2010 Mark Logic, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'net/http'
16
+ require 'uri'
17
+ require 'digest/md5'
18
+
19
+ module Net
20
+ module HTTPHeader
21
+ @@nonce_count = -1
22
+ CNONCE = Digest::MD5.new.update("%x" % (Time.now.to_i + rand(65535))).hexdigest
23
+
24
+ def digest_auth(user, password, response)
25
+ # based on http://segment7.net/projects/ruby/snippets/digest_auth.rb
26
+ @@nonce_count += 1
27
+
28
+ response['www-authenticate'] =~ /^(\w+) (.*)/
29
+
30
+ params = {}
31
+ $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
32
+
33
+ a_1 = "#{user}:#{params['realm']}:#{password}"
34
+ a_2 = "#{@method}:#{@path}"
35
+ request_digest = ''
36
+ request_digest << Digest::MD5.new.update(a_1).hexdigest
37
+ request_digest << ':' << params['nonce']
38
+ request_digest << ':' << ('%08x' % @@nonce_count)
39
+ request_digest << ':' << CNONCE
40
+ request_digest << ':' << params['qop']
41
+ request_digest << ':' << Digest::MD5.new.update(a_2).hexdigest
42
+
43
+ header = []
44
+ header << "Digest username=\"#{user}\""
45
+ header << "realm=\"#{params['realm']}\""
46
+
47
+ header << "qop=#{params['qop']}"
48
+
49
+ header << "algorithm=MD5"
50
+ header << "uri=\"#{@path}\""
51
+ header << "nonce=\"#{params['nonce']}\""
52
+ header << "nc=#{'%08x' % @@nonce_count}"
53
+ header << "cnonce=\"#{CNONCE}\""
54
+ header << "response=\"#{Digest::MD5.new.update(request_digest).hexdigest}\""
55
+
56
+ @header['Authorization'] = header
57
+ end
58
+ end
59
+ end
60
+
61
+ module ActiveDocument
62
+
63
+ class MarkLogicHTTP
64
+
65
+ def initialize(uri, user_name, password)
66
+ @url = URI.parse(uri)
67
+ @user_name = user_name
68
+ @password = password
69
+ end
70
+
71
+ def send_xquery(xquery)
72
+ if xquery.nil? or xquery.empty? then
73
+ return nil
74
+ end
75
+ req = authenticate()
76
+ req.set_form_data({'request'=>"#{xquery}"})
77
+ res = Net::HTTP.new(@url.host, @url.port).start {|http| http.request(req) }
78
+ case res
79
+ when Net::HTTPSuccess, Net::HTTPRedirection
80
+ # puts res.body
81
+ res.body
82
+ else
83
+ res.error!
84
+ end
85
+ end
86
+
87
+ private
88
+ def authenticate
89
+ req = Net::HTTP::Post.new(@url.path)
90
+ Net::HTTP.start(@url.host, @url.port) do |http|
91
+ res = http.head(@url.request_uri)
92
+ req.digest_auth(@user_name, @password, res)
93
+ end
94
+ return req
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,76 @@
1
+ # Copyright 2010 Mark Logic, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module ActiveDocument
16
+
17
+ class MarkLogicQueryBuilder
18
+
19
+ def load(uri)
20
+ "fn:doc('#{uri}')"
21
+ end
22
+
23
+ # This method does a full text search
24
+ def find_by_word(word, root, namespace)
25
+ xquery = <<GENERATED
26
+ import module namespace search = "http://marklogic.com/appservices/search"at "/MarkLogic/appservices/search/search.xqy";
27
+ search:search("#{word}",
28
+ <options xmlns="http://marklogic.com/appservices/search">
29
+ GENERATED
30
+ unless root.nil?
31
+ xquery << "<searchable-expression"
32
+
33
+ xquery << " xmlns:a=\"#{namespace}\"" unless namespace.nil? or namespace.empty?
34
+ xquery << '>/'
35
+ xquery << "a:" unless namespace.nil? or namespace.empty?
36
+ xquery << "#{root}</searchable-expression>"
37
+ end
38
+ xquery << "</options>)"
39
+ end
40
+
41
+ def find_by_element(element, value, root = nil, element_namespace = nil, root_namespace = nil)
42
+ xquery = <<GENERATED
43
+ import module namespace search = "http://marklogic.com/appservices/search"at "/MarkLogic/appservices/search/search.xqy";
44
+ search:search("word:#{value}",
45
+ <options xmlns="http://marklogic.com/appservices/search">
46
+ GENERATED
47
+ unless root.nil?
48
+ xquery << "<searchable-expression"
49
+ xquery << " xmlns:a=\"#{root_namespace}\"" unless root_namespace.nil?
50
+ xquery << '>/'
51
+ xquery << "a:" unless root_namespace.nil?
52
+ xquery << "#{root}</searchable-expression>)"
53
+ end
54
+ xquery << <<CONSTRAINT
55
+ <constraint name="word">
56
+ <word>
57
+ <element ns="#{element_namespace unless element_namespace.nil?}" name="#{element}"/>
58
+ </word>
59
+ </constraint></options>)
60
+ CONSTRAINT
61
+ end
62
+
63
+ def search(search_text, start, page_length, options)
64
+ if options.nil?
65
+ option = '()'
66
+ else
67
+ option = options.to_s
68
+ end
69
+ <<-GENERATED
70
+ import module namespace search = "http://marklogic.com/appservices/search"at "/MarkLogic/appservices/search/search.xqy";
71
+ search:search("#{search_text}",#{option},#{start}, #{page_length})
72
+ GENERATED
73
+ end
74
+
75
+ end # end class
76
+ end # end module
@@ -0,0 +1,54 @@
1
+ # Copyright 2010 Mark Logic, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rubygems'
16
+ require 'nokogiri'
17
+
18
+ module ActiveDocument
19
+ class SearchMatch
20
+ include Enumerable
21
+
22
+ def initialize(node)
23
+ @node = node
24
+ end
25
+
26
+ def path
27
+ @node.xpath("./@path").to_s
28
+ end
29
+
30
+ def to_s
31
+ value = @node.xpath("./node()").to_s
32
+ begin
33
+ value[/<search:highlight>/] = ""
34
+ value[/<\/search:highlight>/] = ""
35
+ rescue IndexError
36
+ end
37
+ return value
38
+ end
39
+
40
+ def highlighted_match(highlight_tag = nil)
41
+ value = @node.xpath("./node()").to_s
42
+ unless highlight_tag.nil?
43
+ begin
44
+ value[/<search:highlight>/] = "<#{highlight_tag}>"
45
+ value[/<\/search:highlight>/] = "</#{highlight_tag}>"
46
+ rescue IndexError
47
+ value
48
+ end
49
+
50
+ end
51
+ return value
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,59 @@
1
+ # Copyright 2010 Mark Logic, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ # SearchOptions allow you to control exactly how the ActiveDocument::Finder#search method behaves and what additional
17
+ # information may be returned in the ActiveDocument::SearchResults object
18
+ # == Attributes
19
+ # * return_facets - if true then facet information is returned in the resultant ActiveDocument::SearchResults object. Default is true
20
+ # * value_constraints - this is a #Hash of value constraint names to their options. e.g. search_options_object.value_constraints["Region"] = {"namespace" => "http://wits.nctc.gov", "element" => "Region"}
21
+ module ActiveDocument
22
+ class SearchOptions
23
+ attr_accessor :return_facets, :value_constraints
24
+
25
+ def initialize
26
+ @return_facets = true;
27
+ @value_constraints = Hash.new
28
+ end
29
+
30
+
31
+ # outputs the object in correctly formatted XML suitable for use in a search
32
+ def to_s
33
+ value_constraints = String.new
34
+ @value_constraints.each do |key, value|
35
+ value_constraints << <<-XML
36
+ <constraint name="#{key}">
37
+ <value>
38
+ <element ns="#{value["namespace"]}" name="#{value["element"]}"/>
39
+ </value>
40
+ </constraint>
41
+ XML
42
+ end
43
+
44
+ value = <<-XML
45
+ <options xmlns="http://marklogic.com/appservices/search">
46
+ <return-facets>#{@return_facets}</return-facets>
47
+ XML
48
+
49
+ # add in constraints
50
+ unless value_constraints.empty?
51
+ value << value_constraints
52
+ end
53
+
54
+ # close the options node
55
+ value << "</options>"
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright 2010 Mark Logic, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rubygems'
16
+ require 'nokogiri'
17
+ require 'ActiveDocument/search_match'
18
+
19
+ module ActiveDocument
20
+ class SearchResult
21
+ include Enumerable
22
+
23
+ def initialize(node)
24
+ @node = node
25
+ end
26
+
27
+ def index
28
+ Integer(@node.xpath("./@index").to_s)
29
+ end
30
+
31
+ def uri
32
+ @node.xpath("./@uri").to_s
33
+ end
34
+
35
+ def path
36
+ @node.xpath("./@path").to_s
37
+ end
38
+
39
+ def score
40
+ Float(@node.xpath("./@score").to_s)
41
+ end
42
+
43
+ def confidence
44
+ Float(@node.xpath("./@confidence").to_s)
45
+ end
46
+
47
+ def fitness
48
+ Integer(@node.xpath("./@fitness").to_s)
49
+ end
50
+
51
+ def each(&block)
52
+ @node.xpath("./search:snippet/search:match").each {|node| yield SearchMatch.new(node)}
53
+ end
54
+
55
+ def root_type
56
+ full_path = @node.xpath("./search:snippet/search:match")[1].xpath("./@path").to_s
57
+ root = full_path.match(/:[[:alpha:]]+\//) # find the first :something/ which should indicate the root
58
+ root.to_s.delete(":/") # remove the : and / to get the root element name
59
+ end
60
+
61
+ def realize(klass)
62
+ klass.load(uri)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright 2010 Mark Logic, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rubygems'
16
+ require 'nokogiri'
17
+ require 'ActiveDocument/search_result'
18
+
19
+ module ActiveDocument
20
+ class SearchResults
21
+ include Enumerable
22
+
23
+ def initialize(results)
24
+ @results_document = Nokogiri::XML(results)
25
+ end
26
+
27
+ def total
28
+ Integer(@results_document.xpath("/search:response/@total").to_s)
29
+ end
30
+
31
+ def start
32
+ Integer(@results_document.xpath("/search:response/@start").to_s)
33
+ end
34
+
35
+ def page_length
36
+ Integer(@results_document.xpath("/search:response/@page-length").to_s)
37
+ end
38
+
39
+ def search_text
40
+ @results_document.xpath("/search:response/search:qtext/text()").to_s
41
+ end
42
+
43
+ def query_resolution_time
44
+ @results_document.xpath("/search:response/search:metrics/search:query-resolution-time/text()").to_s
45
+ end
46
+
47
+ def snippet_resolution_time
48
+ @results_document.xpath("/search:response/search:metrics/search:snippet-resolution-time/text()").to_s
49
+ end
50
+
51
+ def facet_resolution_time
52
+ @results_document.xpath("/search:response/search:metrics/search:facet-resolution-time/text()").to_s
53
+ end
54
+
55
+ def total_time
56
+ @results_document.xpath("/search:response/search:metrics/search:total-time/text()").to_s
57
+ end
58
+
59
+ def each(&block)
60
+ @results_document.xpath("/search:response/search:result").each {|node| yield SearchResult.new(node)}
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,10 @@
1
+ require 'ActiveDocument/active_document'
2
+ require 'ActiveDocument/finder'
3
+ require 'ActiveDocument/inheritable'
4
+ require 'ActiveDocument/mark_logic_http'
5
+ require 'ActiveDocument/mark_logic_query_builder'
6
+ require 'ActiveDocument/search_match'
7
+ require 'ActiveDocument/search_options'
8
+ require 'ActiveDocument/search_result'
9
+ require 'ActiveDocument/search_results'
10
+
@@ -0,0 +1 @@
1
+ require 'active_document'
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activedocument
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ version: "0.1"
9
+ platform: ruby
10
+ authors:
11
+ - Clark D. Richey, Jr.
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-04-02 00:00:00 -04:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: nokogiri
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 1
28
+ - 4
29
+ - 1
30
+ version: 1.4.1
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: Object Mapper for XML Database. Initially setup for connection to MarkLogic
34
+ email: clark@clarkrichey.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files: []
40
+
41
+ files:
42
+ - lib/active_document.rb
43
+ - lib/ActiveDocument/active_document.rb
44
+ - lib/ActiveDocument/finder.rb
45
+ - lib/ActiveDocument/inheritable.rb
46
+ - lib/ActiveDocument/mark_logic_http.rb
47
+ - lib/ActiveDocument/mark_logic_query_builder.rb
48
+ - lib/ActiveDocument/search_match.rb
49
+ - lib/ActiveDocument/search_options.rb
50
+ - lib/ActiveDocument/search_result.rb
51
+ - lib/ActiveDocument/search_results.rb
52
+ - lib/activedocument.rb
53
+ has_rdoc: true
54
+ homepage: http://github.com/crichey/ActiveDocument
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options: []
59
+
60
+ require_paths:
61
+ - .
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.6
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Object Mapper for XML Database
83
+ test_files: []
84
+