activedocument 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.
@@ -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
+