active_xml 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: