active_xml 0.0.1 → 0.0.2

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 ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1,54 @@
1
+ module ActiveXML
2
+ class Collection
3
+ include ActiveXML
4
+ include Enumerable
5
+
6
+ def pluck(key)
7
+ map do |elem|
8
+ fetch_contents(elem.elements, key).first
9
+ end
10
+ end
11
+
12
+ def where(where_values)
13
+ raise ArgumentError.new("Passing more then 1 where value is currently not possible") if where_values.size > 1
14
+ Query.new(@path, where_values)
15
+ end
16
+
17
+ def split(route_path)
18
+ hash = Hash.new
19
+
20
+ each do |elem|
21
+ route = Route.new(route_path, elem)
22
+ hashed_path = route.to_hash
23
+ path = Pathname.new(hashed_path.values[0])
24
+ create_file(path, elem.to_xml)
25
+ hash.merge!(hashed_path) { |key,old,new| old.class == Array ? old << new[0] : [new,old] }
26
+ end
27
+
28
+ hash
29
+ end
30
+
31
+ def split_to_array(route_path)
32
+ map do |elem|
33
+ route = Route.new(route_path, elem)
34
+ path = Pathname.new(route.generate)
35
+ create_file(path, elem.to_xml)
36
+ path
37
+ end
38
+ end
39
+
40
+ def delete_nodes(keys)
41
+ keys.each { |key| xml.search(key).first.remove }
42
+ end
43
+
44
+ private
45
+
46
+ def root
47
+ "objects"
48
+ end
49
+
50
+ def each(&block)
51
+ xml.child.elements.each(&block)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,27 @@
1
+ module ActiveXML
2
+ class Collection
3
+ class Query < Collection
4
+ def initialize(path, where_values)
5
+ super(path)
6
+ @where_key = where_values.keys.first
7
+ @where_value = where_values.values.first
8
+ end
9
+
10
+ private
11
+
12
+ def each
13
+ xml.child.elements.each do |elem|
14
+ yield(elem) if in_where?(elem)
15
+ end
16
+ end
17
+
18
+ def in_where?(elem)
19
+ if @where_value.is_a?(Array)
20
+ @where_value.any?{|object| fetch_contents(elem.elements, @where_key).first == object.to_s}
21
+ else
22
+ fetch_contents(elem.elements, @where_key).first == @where_value.to_s
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ module ActiveXML
2
+ class Collection
3
+ class Route
4
+ attr_reader :keys
5
+
6
+ def initialize(route, node)
7
+ @route = "#{route.clone}"
8
+ @keys = route_to_keys
9
+ @node = node
10
+ @cache = Hash.new
11
+ end
12
+
13
+ def to_hash
14
+ { read(@keys[0]) => [generate] }
15
+ end
16
+
17
+ def generate
18
+ @keys.each do |key|
19
+ @route.gsub!(":#{key}", read(key))
20
+ end
21
+
22
+ "#{@route}.xml"
23
+ end
24
+
25
+ private
26
+
27
+ def read(key)
28
+ @cache[key] || (@cache[key] = @node.search(key.to_s).first.try(:content))
29
+ end
30
+
31
+ def route_to_keys
32
+ @route.scan(/:[^\/]+/).map{|key| key.gsub(":", "").to_sym}
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,75 @@
1
+ module ActiveXML
2
+ class Object
3
+ include ActiveXML
4
+
5
+ def delete
6
+ @path.delete
7
+ end
8
+
9
+ def delete_node(key)
10
+ find(key).remove
11
+ end
12
+
13
+ def xml=(xml)
14
+ raise ArgumentError.new("Passed data must be Nokogiri::XML::Document") unless xml.is_a?(Nokogiri::XML::Document)
15
+ @xml = xml
16
+ end
17
+
18
+ def read_attribute(*keys)
19
+ result = find(keys.join('/'))
20
+ is_text_node?(result) ? result.content : result
21
+ end
22
+
23
+ def write_attribute(*keys, value)
24
+ append_missing_subtree(keys)
25
+ append_value(keys, value)
26
+ end
27
+
28
+ private
29
+
30
+ def root
31
+ "object"
32
+ end
33
+
34
+ def is_text_node?(node)
35
+ node && node.children.none?(&:element?)
36
+ end
37
+
38
+ def append_missing_subtree(keys)
39
+ keys.each_with_index do |key, index|
40
+ next if find(keys[0..index].join('/'))
41
+
42
+ if index == 0 #there is no element at all, searching by css will fail, start from root
43
+ xml.root.add_child(build_xml_structure(keys))
44
+ else
45
+ find(keys[0..index-1].join('/')).add_child(build_xml_structure(keys[index..-1]))
46
+ end
47
+ end
48
+ end
49
+
50
+ def append_value(keys, value)
51
+ css_path = keys.join('/')
52
+ if value.respond_to?(:to_xml)
53
+ find(css_path).add_child(Nokogiri::XML(value.to_xml).root)
54
+ else
55
+ find(css_path).content = value
56
+ end
57
+ end
58
+
59
+ def build_xml_structure(keys)
60
+ Nokogiri::XML::Builder.new do |xml|
61
+ keys_to_xml_elements(keys.clone, xml)
62
+ end.doc.root
63
+ end
64
+
65
+ def keys_to_xml_elements(keys, xml)
66
+ while next_element = keys.shift
67
+ xml.send(next_element.to_sym) {|x| keys_to_xml_elements(keys, x)}
68
+ end
69
+ end
70
+
71
+ def find(key)
72
+ xml.at_css(key.to_s)
73
+ end
74
+ end
75
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveXML
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveXML::Collection::Query do
4
+ let(:path) { Pathname.new("spec/fixtures/active_xml/example.xml") }
5
+
6
+ describe "#pluck(key)" do
7
+
8
+ it "works with params as single value" do
9
+ ActiveXML::Collection::Query.new(path, id: 1).pluck(:name).should == ["Foo"]
10
+ end
11
+
12
+ it "works with params as an array" do
13
+ ActiveXML::Collection::Query.new(path, id: [1, 2]).pluck(:name).should == ["Foo", "Bar"]
14
+ end
15
+ end
16
+
17
+ describe "#count" do
18
+ it "works with params as single value" do
19
+ ActiveXML::Collection::Query.new(path, id: 1).count.should == 1
20
+ end
21
+
22
+ it "works with params as an array" do
23
+ ActiveXML::Collection::Query.new(path, id: [1, 2]).count.should == 2
24
+ end
25
+ end
26
+
27
+ describe "#split" do
28
+ let(:temp_dir) { Pathname.new(Dir.mktmpdir("test_active_xml_query")) }
29
+ let(:query) { ActiveXML::Collection::Query.new(path, id: 2) }
30
+
31
+ it "split only matched results" do
32
+ query.split("#{temp_dir}/objects/:id")
33
+ temp_dir.join('objects/1.xml').should_not exist
34
+ temp_dir.join('objects/2.xml').should exist
35
+ end
36
+
37
+ it "saves correct data" do
38
+ query.split("#{temp_dir}/objects/:id")
39
+ temp_dir.join('objects/2.xml').read.should include('<id>2</id>', '<name>Bar</name>')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveXML::Collection::Route do
4
+ describe "new" do
5
+ it "finds one key" do
6
+ ActiveXML::Collection::Route.new("this/is/route/with/:key", nil).keys.should == [:key]
7
+ end
8
+
9
+ it "finds multiple keys" do
10
+ ActiveXML::Collection::Route.new("this/is/route/with/:key/and/:another", nil).keys.should == [:key, :another]
11
+ end
12
+ end
13
+
14
+ describe "to_hash" do
15
+ it "generates one element hash" do
16
+ xml_node = Nokogiri::XML("<object><key>value</key><another>something</another><object>").child
17
+ route = ActiveXML::Collection::Route.new("this/is/route/with/:key/and/:another", xml_node)
18
+ route.to_hash.should == { "value" => ["this/is/route/with/value/and/something.xml"] }
19
+ end
20
+ end
21
+
22
+ describe "generate" do
23
+ it "generates proper path" do
24
+ xml_node = Nokogiri::XML("<object><key>value</key><another>something</another><object>").child
25
+ route = ActiveXML::Collection::Route.new("this/is/route/with/:key/and/:another", xml_node)
26
+ route.generate.should == "this/is/route/with/value/and/something.xml"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveXML::Collection do
4
+ subject { ActiveXML::Collection.new(path) }
5
+ let(:path) { Pathname.new("spec/fixtures/active_xml/example.xml") }
6
+
7
+ it_behaves_like "active_xml"
8
+
9
+ describe "#pluck(key)" do
10
+ it "returns the array of objects stored in key given in param" do
11
+ subject.pluck(:name).should == ["Foo", "Bar"]
12
+ end
13
+ end
14
+
15
+ describe "#count" do
16
+ it "returns a number of objects in collection" do
17
+ subject.count.should == 2
18
+ end
19
+ end
20
+
21
+ describe "#where" do
22
+ it "returns Query object" do
23
+ subject.where(id: 1).should be_a(ActiveXML::Collection::Query)
24
+ end
25
+
26
+ it "raises an error if more that one key:value pair passed in where" do
27
+ expect{ subject.where(id: 1, name: "Foo") }.to raise_error(ArgumentError, "Passing more then 1 where value is currently not possible")
28
+ end
29
+ end
30
+
31
+ describe "#delete_nodes" do
32
+ it "deletes given nodes" do
33
+ subject.delete_nodes(["id","name"])
34
+ subject.xml.to_s.should_not include('<id>1</id>','<name>Foo</name>')
35
+ subject.xml.to_s.should include('<id>2</id>','<name>Bar</name>')
36
+ end
37
+ end
38
+
39
+ describe "#split" do
40
+ let(:temp_dir) { Pathname.new(Dir.mktmpdir("test_active_xml_collection")) }
41
+
42
+ it "should create 2 files" do
43
+ subject.split("#{temp_dir}/objects/:id")
44
+ temp_dir.join('objects/1.xml').should exist
45
+ temp_dir.join('objects/2.xml').should exist
46
+ end
47
+
48
+ it "new files should have valid content" do
49
+ subject.split("#{temp_dir}/objects/:id")
50
+ temp_dir.join('objects/1.xml').read.should include('<id>1</id>', '<name>Foo</name>')
51
+ end
52
+
53
+ it "returns hash of paths" do
54
+ subject.split("#{temp_dir}/objects/:id").should == {
55
+ "1" => ["#{temp_dir}/objects/1.xml"],
56
+ "2" => ["#{temp_dir}/objects/2.xml"]
57
+ }
58
+ end
59
+
60
+ it "returns hash of paths, each id has array of paths" do
61
+ collection = ActiveXML::Collection.new("spec/fixtures/active_xml/nested_example.xml")
62
+ collection.split("#{temp_dir}/objects/:id/:code").should == {
63
+ "1" => [
64
+ "#{temp_dir}/objects/1/ABC.xml",
65
+ "#{temp_dir}/objects/1/ABCD.xml"
66
+ ],
67
+ "2" => [
68
+ "#{temp_dir}/objects/2/CBA.xml",
69
+ ]
70
+ }
71
+ end
72
+ end
73
+
74
+ describe "#split_to_array" do
75
+ let(:temp_dir) { Pathname.new(Dir.mktmpdir("test_active_xml_collection")) }
76
+
77
+ it "should create 2 files" do
78
+ subject.split_to_array("#{temp_dir}/objects/:id")
79
+ temp_dir.join('objects/1.xml').should exist
80
+ temp_dir.join('objects/2.xml').should exist
81
+ end
82
+
83
+ it "new files should have valid content" do
84
+ subject.split_to_array("#{temp_dir}/objects/:id")
85
+ temp_dir.join('objects/1.xml').read.should include('<id>1</id>', '<name>Foo</name>')
86
+ end
87
+
88
+ it "returns array of Pathnames" do
89
+ subject.split_to_array("#{temp_dir}/objects/:id").first.should be_kind_of Pathname
90
+ end
91
+
92
+ it "returns proper Pathname" do
93
+ subject.split_to_array("#{temp_dir}/objects/:id").first.to_s.should == "#{temp_dir}/objects/1.xml"
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveXML::Object do
4
+ subject { ActiveXML::Object.new(path) }
5
+ let(:path) { Pathname.new("spec/fixtures/active_xml/object.xml") }
6
+ let(:temp_file) { Pathname.new(Tempfile.new('temp')) }
7
+
8
+ it_behaves_like "active_xml"
9
+
10
+ describe "self.new" do
11
+ it "creates a file at given path if it doesnt exist" do
12
+ temp = temp_file
13
+ temp.delete
14
+ ActiveXML::Object.new(temp)
15
+ temp.should exist
16
+ end
17
+ end
18
+
19
+ describe "#xml=" do
20
+ it "raises an error if passed data is not Nokogiri::XML::Document" do
21
+ expect{ subject.xml = "not an nokogiri document" }.to raise_error(ArgumentError, "Passed data must be Nokogiri::XML::Document")
22
+ end
23
+
24
+ it "replaces nokogiri document with passed one" do
25
+ new_xml = Nokogiri::XML("<key>value</key>")
26
+ subject.xml = new_xml
27
+ subject.xml.should eq(new_xml)
28
+ end
29
+ end
30
+
31
+ describe "#save" do
32
+ it "writes stored data to file" do
33
+ temp_file.open('w') {|f| f.write '<root>value</root>'}
34
+ object = ActiveXML::Object.new(temp_file)
35
+ object.xml = Nokogiri::XML("<key>changed value</key>")
36
+ object.save
37
+ temp_file.read.should include('<key>changed value</key>')
38
+ end
39
+ end
40
+
41
+ describe "#read_attribute" do
42
+ it "returns first found element if string passed" do
43
+ subject.read_attribute("city").should == "Wroclaw"
44
+ end
45
+
46
+ it "returns first found element if symbol passed" do
47
+ subject.read_attribute(:city).should == "Wroclaw"
48
+ end
49
+
50
+ it "returns nil when key not found" do
51
+ subject.read_attribute("zip-code").should be_nil
52
+ end
53
+
54
+ it "returns nokogiri object if element has nested keys" do
55
+ subject.read_attribute(:object).should be_kind_of(Nokogiri::XML::Element)
56
+ end
57
+
58
+ it "build css path from passed arguments" do
59
+ subject.read_attribute(:object, :city).should == "Wroclaw"
60
+ end
61
+ end
62
+
63
+ describe "#write_attribute" do
64
+ context "when given node exists" do
65
+ it "overwrites found node to given value with string passed as key" do
66
+ subject.write_attribute("city", "Krakow")
67
+ subject.xml.to_s.should include("<city>Krakow</city>")
68
+ end
69
+
70
+ it "overwrites found node to given value with symbol passed as key" do
71
+ subject.write_attribute(:city, "Krakow")
72
+ subject.xml.to_s.should include("<city>Krakow</city>")
73
+ end
74
+
75
+ it "leaves existing value intact if Nokogiri::XML is passed as value" do
76
+ value = Nokogiri::XML('<external><internal>val</internal></external>')
77
+ subject.write_attribute(:city, value)
78
+ subject.read_attribute(:city, :external, :internal).should == 'val'
79
+ subject.read_attribute(:city).to_s.should include('Wroclaw')
80
+ end
81
+ end
82
+
83
+ context "when given node does not exist" do
84
+ it "creates node with given key and value with string passed as key" do
85
+ subject.write_attribute("street", "Basztowa")
86
+ subject.xml.to_s.should include ("<street>Basztowa</street>")
87
+ end
88
+
89
+ it "creates node with given key and value with string passed as key" do
90
+ subject.write_attribute(:street, "Basztowa")
91
+ subject.xml.to_s.should include ("<street>Basztowa</street>")
92
+ end
93
+
94
+ it "creates missing part of tree" do
95
+ subject.write_attribute(:city, :zipcode, '123')
96
+ subject.xml.to_s.should include("<zipcode>123</zipcode>")
97
+ end
98
+
99
+ it "leaves existing nodes intact" do
100
+ subject.write_attribute(:city, :zipcode, '123')
101
+ subject.xml.to_s.should include('Wroclaw')
102
+ end
103
+
104
+ it "works with Nokogiri::XML objects as values" do
105
+ value = Nokogiri::XML('<external><internal>val</internal></external>')
106
+ subject.write_attribute(:city, :zipcode, value)
107
+ subject.read_attribute(:city, :zipcode, :external, :internal).should == 'val'
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "#set_root!" do
113
+ it "changes root name by given argument" do
114
+ subject.stub(root: "new_root")
115
+ subject.set_root!
116
+ subject.xml.root.name.should == "new_root"
117
+ end
118
+ end
119
+
120
+ describe "#delete_node" do
121
+ it "deletes the nodes from xml" do
122
+ subject.delete_node('city')
123
+ subject.xml.search('city').should be_empty
124
+ end
125
+ end
126
+
127
+ describe "#delete" do
128
+ it "removes the file" do
129
+ temp_file.open('w') {|f| f.write '<root>value</root>'}
130
+ xml = ActiveXML::Object.new(temp_file)
131
+ xml.delete
132
+ temp_file.should_not exist
133
+ end
134
+ end
135
+ end
136
+
@@ -0,0 +1,10 @@
1
+ <example>
2
+ <object>
3
+ <id>1</id>
4
+ <name>Foo</name>
5
+ </object>
6
+ <object>
7
+ <id>2</id>
8
+ <name>Bar</name>
9
+ </object>
10
+ </example>
@@ -0,0 +1,17 @@
1
+ <example>
2
+ <object>
3
+ <id>1</id>
4
+ <name>Foo</name>
5
+ <code>ABC</code>
6
+ </object>
7
+ <object>
8
+ <id>1</id>
9
+ <name>Foo1</name>
10
+ <code>ABCD</code>
11
+ </object>
12
+ <object>
13
+ <id>2</id>
14
+ <name>Bar</name>
15
+ <code>CBA</code>
16
+ </object>
17
+ </example>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0"?>
2
+ <object>
3
+ <city>Wroclaw</city>
4
+ <country>Poland</country>
5
+ </object>
@@ -0,0 +1,20 @@
1
+ require 'active_xml'
2
+ require 'support/shared_examples/active_xml'
3
+ require 'tempfile'
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # Require this file using `require "spec_helper"` to ensure that it is only
7
+ # loaded once.
8
+ #
9
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ config.filter_run :focus
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.order = 'random'
20
+ end
@@ -0,0 +1,19 @@
1
+ shared_examples "active_xml" do
2
+ describe ActiveXML do
3
+ describe "#new" do
4
+ let(:path) { Pathname.new("spec/fixtures/active_xml/example.xml") }
5
+
6
+ it "should not raise errors if path to xml file provided" do
7
+ expect{ subject }.to_not raise_error
8
+ end
9
+ end
10
+
11
+ describe "#path" do
12
+ let(:path) { Pathname.new("spec/fixtures/active_xml/example.xml") }
13
+
14
+ it "returns the path given in initialiser" do
15
+ subject.path.should == path
16
+ end
17
+ end
18
+ end
19
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_xml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -115,13 +115,27 @@ extensions: []
115
115
  extra_rdoc_files: []
116
116
  files:
117
117
  - .gitignore
118
+ - .rspec
118
119
  - Gemfile
119
120
  - LICENSE.txt
120
121
  - README.md
121
122
  - Rakefile
122
123
  - active_xml.gemspec
123
124
  - lib/active_xml.rb
125
+ - lib/active_xml/collection.rb
126
+ - lib/active_xml/collection/query.rb
127
+ - lib/active_xml/collection/route.rb
128
+ - lib/active_xml/object.rb
124
129
  - lib/active_xml/version.rb
130
+ - spec/active_xml/collection/query_spec.rb
131
+ - spec/active_xml/collection/route_spec.rb
132
+ - spec/active_xml/collection_spec.rb
133
+ - spec/active_xml/object_spec.rb
134
+ - spec/fixtures/active_xml/example.xml
135
+ - spec/fixtures/active_xml/nested_example.xml
136
+ - spec/fixtures/active_xml/object.xml
137
+ - spec/spec_helper.rb
138
+ - spec/support/shared_examples/active_xml.rb
125
139
  homepage: ''
126
140
  licenses:
127
141
  - MIT
@@ -147,5 +161,14 @@ rubygems_version: 1.8.23
147
161
  signing_key:
148
162
  specification_version: 3
149
163
  summary: Nokogiri XML Wrapper
150
- test_files: []
164
+ test_files:
165
+ - spec/active_xml/collection/query_spec.rb
166
+ - spec/active_xml/collection/route_spec.rb
167
+ - spec/active_xml/collection_spec.rb
168
+ - spec/active_xml/object_spec.rb
169
+ - spec/fixtures/active_xml/example.xml
170
+ - spec/fixtures/active_xml/nested_example.xml
171
+ - spec/fixtures/active_xml/object.xml
172
+ - spec/spec_helper.rb
173
+ - spec/support/shared_examples/active_xml.rb
151
174
  has_rdoc: