lazy_xml_model 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +219 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lazy_xml_model.gemspec +32 -0
- data/lib/lazy_xml_model.rb +66 -0
- data/lib/lazy_xml_model/attribute_node.rb +22 -0
- data/lib/lazy_xml_model/collection_proxy.rb +137 -0
- data/lib/lazy_xml_model/element_node.rb +33 -0
- data/lib/lazy_xml_model/element_proxy.rb +35 -0
- data/lib/lazy_xml_model/has_many_association.rb +32 -0
- data/lib/lazy_xml_model/has_one_association.rb +48 -0
- data/lib/lazy_xml_model/object_proxy.rb +64 -0
- data/lib/lazy_xml_model/version.rb +3 -0
- metadata +178 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4c3b4b9ff0fe8d3ba0aba451e4bbb6fe74449fd6
|
4
|
+
data.tar.gz: 1a3b4fe280483a8d344330a925c8298dd633cdc4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dc703645c5bcaee13a21d1f928e276bbdffbcb44ca47bcec0b602dc427cc3186412b8d53bb9ab3d34b853d4ab7ad74921e40b3b75c16b55ad6eb916c6fa85070
|
7
|
+
data.tar.gz: ee946a3f8875ac1c0d3ce0fa9c5aebc8c9b45b9de1ad7719b8228649474b6404d3147bf86baa33272acd0803e69e427fc74bb6f861ad5007499a9d65ca9e81eb
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.0
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
lazy_xml_model (0.1.0)
|
5
|
+
activemodel
|
6
|
+
activesupport
|
7
|
+
nokogiri
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (5.1.4)
|
13
|
+
activesupport (= 5.1.4)
|
14
|
+
activesupport (5.1.4)
|
15
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
16
|
+
i18n (~> 0.7)
|
17
|
+
minitest (~> 5.1)
|
18
|
+
tzinfo (~> 1.1)
|
19
|
+
codecov (0.1.10)
|
20
|
+
json
|
21
|
+
simplecov
|
22
|
+
url
|
23
|
+
coderay (1.1.2)
|
24
|
+
concurrent-ruby (1.0.5)
|
25
|
+
diff-lcs (1.3)
|
26
|
+
docile (1.1.5)
|
27
|
+
i18n (0.9.1)
|
28
|
+
concurrent-ruby (~> 1.0)
|
29
|
+
json (2.1.0)
|
30
|
+
method_source (0.9.0)
|
31
|
+
mini_portile2 (2.3.0)
|
32
|
+
minitest (5.10.3)
|
33
|
+
nokogiri (1.8.1)
|
34
|
+
mini_portile2 (~> 2.3.0)
|
35
|
+
pry (0.11.3)
|
36
|
+
coderay (~> 1.1.0)
|
37
|
+
method_source (~> 0.9.0)
|
38
|
+
rake (10.5.0)
|
39
|
+
rspec (3.7.0)
|
40
|
+
rspec-core (~> 3.7.0)
|
41
|
+
rspec-expectations (~> 3.7.0)
|
42
|
+
rspec-mocks (~> 3.7.0)
|
43
|
+
rspec-core (3.7.0)
|
44
|
+
rspec-support (~> 3.7.0)
|
45
|
+
rspec-expectations (3.7.0)
|
46
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
47
|
+
rspec-support (~> 3.7.0)
|
48
|
+
rspec-mocks (3.7.0)
|
49
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
50
|
+
rspec-support (~> 3.7.0)
|
51
|
+
rspec-support (3.7.0)
|
52
|
+
simplecov (0.15.1)
|
53
|
+
docile (~> 1.1.0)
|
54
|
+
json (>= 1.8, < 3)
|
55
|
+
simplecov-html (~> 0.10.0)
|
56
|
+
simplecov-html (0.10.2)
|
57
|
+
thread_safe (0.3.6)
|
58
|
+
tzinfo (1.2.4)
|
59
|
+
thread_safe (~> 0.1)
|
60
|
+
url (0.3.2)
|
61
|
+
|
62
|
+
PLATFORMS
|
63
|
+
ruby
|
64
|
+
|
65
|
+
DEPENDENCIES
|
66
|
+
bundler (~> 1.16)
|
67
|
+
codecov
|
68
|
+
lazy_xml_model!
|
69
|
+
pry
|
70
|
+
rake (~> 10.0)
|
71
|
+
rspec (~> 3.0)
|
72
|
+
|
73
|
+
BUNDLED WITH
|
74
|
+
1.16.0
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Evan Rolfe
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
# LazyXmlModel
|
2
|
+
Lets you modify xml files using ruby models with an interface similar to ActiveRecord models. It also lazily evaluates the xml file so you do not have to specify a model which covers the entire xml file. This is useful if you only want to modify certain parts of an xml file but do not care about the other contents.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
Add this to your Gemfile:
|
6
|
+
```ruby
|
7
|
+
gem 'lazy_xml_model', git: 'https://github.com/evanrolfe/lazy_xml_model'
|
8
|
+
```
|
9
|
+
## Usage
|
10
|
+
Example XML file `company.xml`:
|
11
|
+
```xml
|
12
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
13
|
+
<company name="SUSE">
|
14
|
+
<description type="about">
|
15
|
+
<headquarters>Nuremberg</headquarters>
|
16
|
+
<website>http://www.suse.com</website>
|
17
|
+
</description>
|
18
|
+
<trading>yes</trading>
|
19
|
+
<employee name="Tanya Erickson">
|
20
|
+
<jobtitle>Chief Marketing Synergist</jobtitle>
|
21
|
+
</employee>
|
22
|
+
<employee name="Rolando Garcia">
|
23
|
+
<jobtitle>Human Integration Coordinator</jobtitle>
|
24
|
+
</employee>
|
25
|
+
<employee name="Xavier Bringham">
|
26
|
+
<jobtitle>Regional Markets Executive</jobtitle>
|
27
|
+
</employee>
|
28
|
+
</company>
|
29
|
+
```
|
30
|
+
Ruby models:
|
31
|
+
```ruby
|
32
|
+
class Company
|
33
|
+
include LazyXmlModel
|
34
|
+
|
35
|
+
attribute_node :name
|
36
|
+
element_node :trading
|
37
|
+
|
38
|
+
has_one :description, class_name: 'Description'
|
39
|
+
has_many :employees, class_name: 'Employee'
|
40
|
+
end
|
41
|
+
|
42
|
+
class Description
|
43
|
+
include LazyXmlModel
|
44
|
+
|
45
|
+
element_node :headquarters
|
46
|
+
element_node :website
|
47
|
+
end
|
48
|
+
|
49
|
+
class Employee
|
50
|
+
include LazyXmlModel
|
51
|
+
|
52
|
+
attribute_node :name
|
53
|
+
element_node :jobtitle
|
54
|
+
end
|
55
|
+
```
|
56
|
+
**Parsing the xml:**
|
57
|
+
```ruby
|
58
|
+
xml_str = File.read('company.xml')
|
59
|
+
company = Company.parse(xml_str)
|
60
|
+
```
|
61
|
+
|
62
|
+
**Accessing elements & attributes:**
|
63
|
+
```ruby
|
64
|
+
company.name
|
65
|
+
# => 'SUSE'
|
66
|
+
company.trading
|
67
|
+
# => 'yes'
|
68
|
+
company.name = 'openSuse'
|
69
|
+
company.trading = 'no'
|
70
|
+
```
|
71
|
+
**has_one associations:**
|
72
|
+
```ruby
|
73
|
+
company.description.headquarters
|
74
|
+
# => 'Nuremberg'
|
75
|
+
company.description.destroy # Removes the <description> tag from the xml
|
76
|
+
company.description
|
77
|
+
# => nil
|
78
|
+
company.build_description # Adds a new Description object and <description/> tag
|
79
|
+
company.description.headquarters = 'Prague' # Sets the value on the new description
|
80
|
+
```
|
81
|
+
**has_many associations:**
|
82
|
+
```ruby
|
83
|
+
company.employees[0].name
|
84
|
+
# => 'Tanya Erickson'
|
85
|
+
company.employees[0].jobtitle
|
86
|
+
# => 'Chief Marketing Synergist'
|
87
|
+
company.employees[0].delete # Deletes the employee
|
88
|
+
company.employees.build # Adds a new blank employee to the collection
|
89
|
+
company.employees.delete_all
|
90
|
+
```
|
91
|
+
**Add a new item to has_many association:**
|
92
|
+
```ruby
|
93
|
+
new_employee = Employee.new
|
94
|
+
new_employee.name = 'Evan Rolfe'
|
95
|
+
new_employee.jobtitle = 'Junior Xml Parser'
|
96
|
+
company.employees << new_employee
|
97
|
+
```
|
98
|
+
**Outputting the xml:**
|
99
|
+
```ruby
|
100
|
+
company.to_xml
|
101
|
+
```
|
102
|
+
|
103
|
+
**Validating the XML input**
|
104
|
+
```ruby
|
105
|
+
company = Company.parse('<company name="an invalid company!">')
|
106
|
+
company.xml_document.errors
|
107
|
+
# => => [#<Nokogiri::XML::SyntaxError: 1:37: FATAL: Premature end of data in tag company line 1>]
|
108
|
+
```
|
109
|
+
|
110
|
+
## Integrating with ActiveModel
|
111
|
+
|
112
|
+
LazyXmlModel plays nicely with ActiveModel so you can have nice things like mass assignment and validations on your xml models.
|
113
|
+
```ruby
|
114
|
+
class Company
|
115
|
+
include LazyXmlModel
|
116
|
+
include ActiveModel::Model
|
117
|
+
include ActiveModel::Validations
|
118
|
+
|
119
|
+
attribute_node :name
|
120
|
+
element_node :trading
|
121
|
+
|
122
|
+
has_one :description, class_name: 'Description'
|
123
|
+
has_many :employees, class_name: 'Employee'
|
124
|
+
|
125
|
+
validates :name, presence: true
|
126
|
+
validates :trading, inclusion: { in: %w(yes no) }
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
**Validating the object:**
|
131
|
+
```ruby
|
132
|
+
company = Company.new(name: 'My Company', trading: 'i dont know')
|
133
|
+
company.valid?
|
134
|
+
# => false
|
135
|
+
company.errors.messages
|
136
|
+
# => {:trading=>["is not included in the list"]}
|
137
|
+
```
|
138
|
+
|
139
|
+
**Validating the xml content:**
|
140
|
+
Example XML file `company.xml`:
|
141
|
+
```xml
|
142
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
143
|
+
<company>
|
144
|
+
<trading>yes</trading>
|
145
|
+
</company>
|
146
|
+
```
|
147
|
+
```ruby
|
148
|
+
xml_str = File.read('company.xml')
|
149
|
+
company = Company.parse(xml_str)
|
150
|
+
company.valid?
|
151
|
+
# => false
|
152
|
+
company.errors.messages
|
153
|
+
# => {:name=>["can't be blank"]}
|
154
|
+
```
|
155
|
+
## Integrating with rails nested forms
|
156
|
+
If you include ActiveModel on your models then LazyXmlMapping gives you an `_attributes=` method on your has_one and has_many associations which means the models can be used with `fields_for` in the same way that an ActiveRecord model which calls `accepts_nested_attributes_for` works.
|
157
|
+
|
158
|
+
```html
|
159
|
+
<%= form_for(company) do |f| %>
|
160
|
+
<div class="form-group">
|
161
|
+
<%= f.label :name %>
|
162
|
+
<%= f.text_field :name %>
|
163
|
+
</div>
|
164
|
+
|
165
|
+
<div class="form-group">
|
166
|
+
<%= f.label :trading %>
|
167
|
+
<%= f.text_field :trading %>
|
168
|
+
</div>
|
169
|
+
|
170
|
+
<%= f.fields_for :description do |description_fields| %>
|
171
|
+
<div class="form-group">
|
172
|
+
<%= description_fields.label :headquarters %>
|
173
|
+
<%= description_fields.text_field :headquarters %>
|
174
|
+
</div>
|
175
|
+
|
176
|
+
<div class="form-group">
|
177
|
+
<%= description_fields.label :website %>
|
178
|
+
<%= description_fields.text_field :website %>
|
179
|
+
</div>
|
180
|
+
<% end %>
|
181
|
+
|
182
|
+
<%= f.fields_for :employees, f.object.employees.to_a do |employees_fields| %>
|
183
|
+
<hr/>
|
184
|
+
<div class="row">
|
185
|
+
<b>Employee:</b>
|
186
|
+
</div>
|
187
|
+
|
188
|
+
<div class="form-group">
|
189
|
+
<%= employees_fields.label :name %>
|
190
|
+
<%= employees_fields.text_field :name %>
|
191
|
+
</div>
|
192
|
+
|
193
|
+
<div class="form-group">
|
194
|
+
<%= employees_fields.label :jobtitle %>
|
195
|
+
<%= employees_fields.text_field :jobtitle %>
|
196
|
+
</div>
|
197
|
+
|
198
|
+
<div class="form-group">
|
199
|
+
<label class="form-check-label">
|
200
|
+
<%= employees_fields.check_box :_destroy %>
|
201
|
+
Delete?
|
202
|
+
</label>
|
203
|
+
</div>
|
204
|
+
<% end %>
|
205
|
+
|
206
|
+
<%= f.submit 'Save' %>
|
207
|
+
<% end %>
|
208
|
+
|
209
|
+
```
|
210
|
+
|
211
|
+
## TODO
|
212
|
+
|
213
|
+
* Validate associated objects as well as the root object
|
214
|
+
* Handle a collection of elements like:
|
215
|
+
```xml
|
216
|
+
<element>value1</element>
|
217
|
+
<element>value2</element>
|
218
|
+
<element>value3</element>
|
219
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "lazy_xml_model"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "lazy_xml_model/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "lazy_xml_model"
|
8
|
+
spec.version = LazyXmlModel::VERSION
|
9
|
+
spec.authors = ["Evan Rolfe"]
|
10
|
+
spec.email = ["esrolfe@suse.de"]
|
11
|
+
|
12
|
+
spec.summary = %q{Allows you to create ActiveRecord-like models for editing XML files.}
|
13
|
+
spec.homepage = "https://github.com/evanrolfe/lazy_xml_model"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.metadata = { "source_code_uri" => "https://github.com/evanrolfe/lazy_xml_model" }
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency 'activesupport'
|
25
|
+
spec.add_dependency 'activemodel'
|
26
|
+
spec.add_dependency 'nokogiri'
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
30
|
+
spec.add_development_dependency "pry"
|
31
|
+
spec.add_development_dependency "codecov"
|
32
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'nokogiri'
|
7
|
+
|
8
|
+
require 'lazy_xml_model/version'
|
9
|
+
require 'lazy_xml_model/attribute_node'
|
10
|
+
require 'lazy_xml_model/element_node'
|
11
|
+
require 'lazy_xml_model/has_one_association'
|
12
|
+
require 'lazy_xml_model/has_many_association'
|
13
|
+
|
14
|
+
module LazyXmlModel
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
|
17
|
+
included do
|
18
|
+
include AttributeNode
|
19
|
+
include HasManyAssociation
|
20
|
+
include HasOneAssociation
|
21
|
+
include ElementNode
|
22
|
+
|
23
|
+
attr_writer :xml_document, :xml_parent_element, :xml_element
|
24
|
+
class_attribute :tag
|
25
|
+
|
26
|
+
#
|
27
|
+
# Class Methods
|
28
|
+
#
|
29
|
+
def self.parse(xml_string)
|
30
|
+
object = self.new
|
31
|
+
object.xml_document = Nokogiri::XML::Document.parse(xml_string, &:noblanks)
|
32
|
+
object
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Instance Methods
|
38
|
+
#
|
39
|
+
def xml_document
|
40
|
+
@xml_document ||= Nokogiri::XML::Document.parse("<#{root_tag}/>", &:noblanks)
|
41
|
+
end
|
42
|
+
|
43
|
+
def xml_parent_element
|
44
|
+
@xml_parent_element
|
45
|
+
end
|
46
|
+
|
47
|
+
def xml_element
|
48
|
+
@xml_element ||= xml_document.root
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_xml
|
52
|
+
# TODO: Make this work for non-root objects
|
53
|
+
xml_document.to_xml(indent: 2, encoding: 'UTF-8')
|
54
|
+
end
|
55
|
+
|
56
|
+
def delete
|
57
|
+
xml_element.remove
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def root_tag
|
63
|
+
self.tag || self.class.name.demodulize.downcase
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module LazyXmlModel
|
2
|
+
module AttributeNode
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
def self.attribute_node(name)
|
7
|
+
# Getter Method
|
8
|
+
define_method(name) do
|
9
|
+
attribute = xml_element.attributes[name.to_s]
|
10
|
+
return if attribute.nil?
|
11
|
+
|
12
|
+
xml_element.attributes[name.to_s].value
|
13
|
+
end
|
14
|
+
|
15
|
+
# Setter Method
|
16
|
+
define_method("#{name}=") do |value|
|
17
|
+
xml_element.set_attribute(name.to_s, value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module LazyXmlModel
|
2
|
+
class CollectionProxy
|
3
|
+
include Enumerable
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_reader :association_name, :xml_parent_element, :options
|
7
|
+
|
8
|
+
def_delegators :collection, :each, :[], :size, :length, :empty?
|
9
|
+
|
10
|
+
def initialize(association_name, xml_parent_element, options = {})
|
11
|
+
@association_name = association_name
|
12
|
+
@xml_parent_element = xml_parent_element
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def <<(item)
|
17
|
+
item.xml_document = nil
|
18
|
+
item.xml_parent_element = xml_parent_element
|
19
|
+
|
20
|
+
if collection.any?
|
21
|
+
collection.last.xml_element.add_next_sibling(item.xml_element)
|
22
|
+
else
|
23
|
+
xml_parent_element.add_child(item.xml_element)
|
24
|
+
end
|
25
|
+
|
26
|
+
@collection << item
|
27
|
+
end
|
28
|
+
alias_method :push, :<<
|
29
|
+
|
30
|
+
def []=(index, new_item)
|
31
|
+
original_item = self[index]
|
32
|
+
|
33
|
+
new_item.xml_document = nil
|
34
|
+
new_item.xml_parent_element = xml_parent_element
|
35
|
+
|
36
|
+
# 1. Replace the xml with new_item.to_xml
|
37
|
+
original_item.xml_element.replace(new_item.xml_element)
|
38
|
+
|
39
|
+
# 2. Replace the original object with the new object
|
40
|
+
@collection[index] = new_item
|
41
|
+
end
|
42
|
+
|
43
|
+
# TODO:
|
44
|
+
def concat(item)
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete(item)
|
49
|
+
# Delete the object thats wrapping this xml element
|
50
|
+
@collection.delete(item)
|
51
|
+
|
52
|
+
# Delete from the xml document
|
53
|
+
item.xml_element.remove
|
54
|
+
end
|
55
|
+
|
56
|
+
def delete_all
|
57
|
+
@collection = []
|
58
|
+
xml_elements.each(&:remove)
|
59
|
+
end
|
60
|
+
alias_method :clear, :delete_all
|
61
|
+
|
62
|
+
def build(params = {})
|
63
|
+
new_object =
|
64
|
+
begin
|
65
|
+
klass.new(params)
|
66
|
+
# Incase the class does not include ActiveModel::AttributeAssignment
|
67
|
+
rescue ArgumentError
|
68
|
+
klass.new
|
69
|
+
end
|
70
|
+
|
71
|
+
self << new_object
|
72
|
+
end
|
73
|
+
|
74
|
+
def attributes=(attributes)
|
75
|
+
indexes_to_delete = []
|
76
|
+
|
77
|
+
# 1. Update the existing items and create new ones
|
78
|
+
attributes.each do |i, object_params|
|
79
|
+
i = i.to_i
|
80
|
+
object_params = object_params.with_indifferent_access
|
81
|
+
|
82
|
+
if self[i].present?
|
83
|
+
self[i].assign_attributes(object_params.except(:_destroy)) # Update the object
|
84
|
+
else
|
85
|
+
build(object_params.except(:_destroy)) # Build the object
|
86
|
+
end
|
87
|
+
|
88
|
+
# Keep track of which items will be deleted
|
89
|
+
indexes_to_delete << i if [true, 1, '1'].include?(object_params[:_destroy])
|
90
|
+
end
|
91
|
+
|
92
|
+
# 2. Delete any items marked for deletion, must come after the first step and
|
93
|
+
# must be in reverse order
|
94
|
+
indexes_to_delete.sort.reverse.each do |i|
|
95
|
+
delete(self[i])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def xml_elements
|
102
|
+
xml_parent_element.elements.select { |element| element.name == element_name }
|
103
|
+
end
|
104
|
+
|
105
|
+
def collection
|
106
|
+
@collection ||= []
|
107
|
+
|
108
|
+
xml_elements.map do |element|
|
109
|
+
item_from_collection = @collection.find { |item| item.xml_element == element }
|
110
|
+
|
111
|
+
if item_from_collection.present?
|
112
|
+
item_from_collection
|
113
|
+
else
|
114
|
+
item = build_item_for_element(element)
|
115
|
+
@collection << item
|
116
|
+
item
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_item_for_element(element)
|
122
|
+
new_item = klass.new
|
123
|
+
new_item.xml_parent_element = xml_parent_element
|
124
|
+
new_item.xml_element = element
|
125
|
+
new_item
|
126
|
+
end
|
127
|
+
|
128
|
+
def element_name
|
129
|
+
klass.tag || association_name
|
130
|
+
end
|
131
|
+
|
132
|
+
def klass
|
133
|
+
return ArgumentError, 'You must specify :class_name!' if options[:class_name].nil?
|
134
|
+
options[:class_name].constantize
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'element_proxy'
|
2
|
+
|
3
|
+
module LazyXmlModel
|
4
|
+
module ElementNode
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
def self.element_node(element_tag)
|
9
|
+
# Proxy Accessor Method
|
10
|
+
define_method("#{element_tag}_proxy".to_sym) do
|
11
|
+
element_proxy = instance_variable_get("@#{element_tag}_proxy")
|
12
|
+
return element_proxy if element_proxy.present?
|
13
|
+
|
14
|
+
element_proxy = LazyXmlModel::ElementProxy.new(element_tag, xml_document, xml_element)
|
15
|
+
instance_variable_set("@#{element_tag}_proxy", element_proxy)
|
16
|
+
element_proxy
|
17
|
+
end
|
18
|
+
|
19
|
+
# Getter Method
|
20
|
+
define_method(element_tag) do
|
21
|
+
element_proxy = send("#{element_tag}_proxy".to_sym)
|
22
|
+
element_proxy.value
|
23
|
+
end
|
24
|
+
|
25
|
+
# Setter Method
|
26
|
+
define_method("#{element_tag}=") do |value|
|
27
|
+
element_proxy = send("#{element_tag}_proxy".to_sym)
|
28
|
+
element_proxy.value = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module LazyXmlModel
|
2
|
+
class ElementProxy
|
3
|
+
attr_reader :element_tag, :xml_document, :xml_parent_element
|
4
|
+
|
5
|
+
def initialize(element_tag, xml_document, xml_parent_element)
|
6
|
+
@element_tag = element_tag
|
7
|
+
@xml_document = xml_document
|
8
|
+
@xml_parent_element = xml_parent_element
|
9
|
+
end
|
10
|
+
|
11
|
+
# Getter Method
|
12
|
+
def value
|
13
|
+
if xml_element.present?
|
14
|
+
xml_element.children.first.content
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Setter Method
|
19
|
+
def value=(value)
|
20
|
+
if xml_element.present?
|
21
|
+
xml_element.children.first.content = value
|
22
|
+
else
|
23
|
+
xml_element = Nokogiri::XML::Element.new(element_tag.to_s, xml_document)
|
24
|
+
xml_element.content = value
|
25
|
+
xml_parent_element.add_child(xml_element)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def xml_element
|
32
|
+
xml_parent_element.elements.find { |element| element.name == element_tag.to_s }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'collection_proxy'
|
2
|
+
|
3
|
+
module LazyXmlModel
|
4
|
+
module HasManyAssociation
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
def self.has_many(association_name, options = {})
|
9
|
+
# Accessor Method
|
10
|
+
define_method(association_name) do
|
11
|
+
collection_proxy = instance_variable_get("@#{association_name}")
|
12
|
+
return collection_proxy if collection_proxy.present?
|
13
|
+
|
14
|
+
collection_proxy = LazyXmlModel::CollectionProxy.new(
|
15
|
+
association_name.to_s.singularize.gsub('_',''),
|
16
|
+
xml_element,
|
17
|
+
options
|
18
|
+
)
|
19
|
+
instance_variable_set("@#{association_name}", collection_proxy)
|
20
|
+
collection_proxy
|
21
|
+
end
|
22
|
+
|
23
|
+
# _attributes= Builder Method
|
24
|
+
# NOTE: This method requires that your object follows the API defined in ActiveModel::AttributeAssignment
|
25
|
+
define_method("#{association_name}_attributes=") do |attributes|
|
26
|
+
collection_proxy = send(association_name)
|
27
|
+
collection_proxy.attributes = attributes
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'object_proxy'
|
2
|
+
|
3
|
+
module LazyXmlModel
|
4
|
+
module HasOneAssociation
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
def self.has_one(association_name, options)
|
9
|
+
association_name = association_name.to_s
|
10
|
+
|
11
|
+
# Proxy Accessor Method
|
12
|
+
define_method("#{association_name}_proxy".to_sym) do
|
13
|
+
object_proxy = instance_variable_get("@#{association_name}_proxy")
|
14
|
+
return object_proxy if object_proxy.present?
|
15
|
+
|
16
|
+
object_proxy = LazyXmlModel::ObjectProxy.new(association_name, xml_document, xml_element, options)
|
17
|
+
instance_variable_set("@#{association_name}_proxy", object_proxy)
|
18
|
+
object_proxy
|
19
|
+
end
|
20
|
+
|
21
|
+
# Getter Method
|
22
|
+
define_method(association_name) do
|
23
|
+
object_proxy = send("#{association_name}_proxy".to_sym)
|
24
|
+
object_proxy.object
|
25
|
+
end
|
26
|
+
|
27
|
+
# Setter Method
|
28
|
+
define_method("#{association_name}=".to_sym) do |object|
|
29
|
+
object_proxy = send("#{association_name}_proxy".to_sym)
|
30
|
+
object_proxy.object = object
|
31
|
+
end
|
32
|
+
|
33
|
+
# Builder Method
|
34
|
+
define_method("build_#{association_name}") do |params = {}|
|
35
|
+
object_proxy = send("#{association_name}_proxy".to_sym)
|
36
|
+
object_proxy.build_object(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
# _attributes= Builder Method
|
40
|
+
# NOTE: This method requires that your object follows the API defined in ActiveModel::AttributeAssignment
|
41
|
+
define_method("#{association_name}_attributes=") do |attributes|
|
42
|
+
object_proxy = send("#{association_name}_proxy".to_sym)
|
43
|
+
object_proxy.attributes = attributes
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module LazyXmlModel
|
2
|
+
class ObjectProxy
|
3
|
+
attr_reader :association_name, :xml_document, :xml_parent_element, :options
|
4
|
+
|
5
|
+
def initialize(association_name, xml_document, xml_parent_element, options = {})
|
6
|
+
@association_name = association_name
|
7
|
+
@xml_document = xml_document
|
8
|
+
@xml_parent_element = xml_parent_element
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
# Getter Method
|
13
|
+
def object
|
14
|
+
element = xml_parent_element.elements.find { |element| element.name == association_name }
|
15
|
+
return unless element.present?
|
16
|
+
|
17
|
+
@object ||= begin
|
18
|
+
new_object = klass.new
|
19
|
+
new_object.xml_parent_element = xml_parent_element
|
20
|
+
new_object.xml_element = element
|
21
|
+
new_object
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Setter Method
|
26
|
+
def object=(new_object)
|
27
|
+
@object.delete if @object.present?
|
28
|
+
|
29
|
+
@object = new_object
|
30
|
+
|
31
|
+
xml_parent_element.add_child(
|
32
|
+
new_object.xml_element
|
33
|
+
)
|
34
|
+
new_object.xml_parent_element = xml_parent_element
|
35
|
+
end
|
36
|
+
|
37
|
+
# Builder Method
|
38
|
+
def build_object(params = {})
|
39
|
+
new_object =
|
40
|
+
begin
|
41
|
+
klass.new(params)
|
42
|
+
# Incase the class does not include ActiveModel::AttributeAssignment
|
43
|
+
rescue ArgumentError
|
44
|
+
klass.new
|
45
|
+
end
|
46
|
+
|
47
|
+
self.object = new_object
|
48
|
+
end
|
49
|
+
|
50
|
+
def attributes=(attributes)
|
51
|
+
if object.present?
|
52
|
+
object.assign_attributes(attributes) # Update the object
|
53
|
+
else
|
54
|
+
build_object(attributes) # Build the object
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def klass
|
61
|
+
options[:class_name].constantize
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lazy_xml_model
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Evan Rolfe
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.16'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.16'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: codecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
- esrolfe@suse.de
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- ".rspec"
|
134
|
+
- ".ruby-version"
|
135
|
+
- ".travis.yml"
|
136
|
+
- Gemfile
|
137
|
+
- Gemfile.lock
|
138
|
+
- LICENSE.txt
|
139
|
+
- README.md
|
140
|
+
- Rakefile
|
141
|
+
- bin/console
|
142
|
+
- bin/setup
|
143
|
+
- lazy_xml_model.gemspec
|
144
|
+
- lib/lazy_xml_model.rb
|
145
|
+
- lib/lazy_xml_model/attribute_node.rb
|
146
|
+
- lib/lazy_xml_model/collection_proxy.rb
|
147
|
+
- lib/lazy_xml_model/element_node.rb
|
148
|
+
- lib/lazy_xml_model/element_proxy.rb
|
149
|
+
- lib/lazy_xml_model/has_many_association.rb
|
150
|
+
- lib/lazy_xml_model/has_one_association.rb
|
151
|
+
- lib/lazy_xml_model/object_proxy.rb
|
152
|
+
- lib/lazy_xml_model/version.rb
|
153
|
+
homepage: https://github.com/evanrolfe/lazy_xml_model
|
154
|
+
licenses:
|
155
|
+
- MIT
|
156
|
+
metadata:
|
157
|
+
source_code_uri: https://github.com/evanrolfe/lazy_xml_model
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubyforge_project:
|
174
|
+
rubygems_version: 2.6.8
|
175
|
+
signing_key:
|
176
|
+
specification_version: 4
|
177
|
+
summary: Allows you to create ActiveRecord-like models for editing XML files.
|
178
|
+
test_files: []
|