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.
@@ -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
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1 @@
1
+ 2.4.0
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.2
5
+ before_install: gem install bundler -v 1.16.0
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in lazy_xml_model.gemspec
6
+ gemspec
@@ -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
@@ -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.
@@ -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
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,3 @@
1
+ module LazyXmlModel
2
+ VERSION = "0.1.0"
3
+ 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: []