data_active 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -1
- data/data_active.gemspec +8 -6
- data/features/step_definitions/step_helper.rb +5 -4
- data/features/step_definitions/sync_books_with_xml.rb +11 -7
- data/features/support/book_generator.rb +29 -0
- data/features/support/factories/book.rb +6 -0
- data/features/support/factories/book_price.rb +8 -0
- data/features/support/factories/chapter.rb +7 -0
- data/features/support/factories/page.rb +7 -0
- data/features/support/fixtures/fresh/pages.yml +10 -10
- data/features/sync_database_with_xml.feature +2 -2
- data/features/sync_one_to_many.feature +0 -6
- data/features/sync_one_to_one.feature +1 -1
- data/lib/data_active.rb +8 -282
- data/lib/data_active/attribute.rb +12 -0
- data/lib/data_active/entity.rb +151 -0
- data/lib/data_active/parser.rb +159 -0
- data/lib/data_active/sax_document.rb +29 -0
- data/lib/data_active/version.rb +1 -1
- data/spec/entity_spec.rb +11 -0
- data/spec/fixtures/xml/ms_xml/books_fresh.xml +173 -0
- data/spec/fixtures/xml/rails_xml/books_fresh.xml +173 -0
- data/spec/parser_spec.rb +285 -0
- data/spec/sax_document_spec.rb +3 -0
- data/spec/spec_helper.rb +39 -0
- data/test_apps/book_store_rails_31x/config/application.rb +0 -32
- data/test_apps/book_store_rails_32x/.rspec +1 -0
- data/test_apps/book_store_rails_32x/.rvmrc +1 -1
- data/test_apps/book_store_rails_32x/Gemfile +12 -4
- data/test_apps/book_store_rails_32x/config/application.rb +0 -40
- metadata +63 -5
- data/features/step_definitions/web_steps.rb +0 -211
data/README.rdoc
CHANGED
@@ -231,7 +231,7 @@ You can combine any of the options (:create, :update or :destroy) and Data Activ
|
|
231
231
|
|
232
232
|
* <b>:sync</b> is really the combination of :create, :update and :destroy. Using this option will cause Data Active to ignore :create, :update and :destroy options and will proceed to make your database records match those in the XML document.
|
233
233
|
|
234
|
-
* <b>:
|
234
|
+
* <b>:strict</b> will raise an exception when elements that do not exist as ActiveRecord classes or attibutes on those classes. When this option isn't used Data Active will ignore those unknow classes and attributes.
|
235
235
|
|
236
236
|
|
237
237
|
=== One to One Associations
|
@@ -266,6 +266,7 @@ Data Active supports has_one association with all options and following arr the
|
|
266
266
|
* 0.0.1 First Release
|
267
267
|
* 0.0.2 Resolved issue where the parent record in a one to many relationship had not been saved causing validation errors when saving child records
|
268
268
|
* 0.0.3 Added option to raise exception when an invalid record id found
|
269
|
+
* 0.0.7 Ractoring of internals to optimise speed and memory usage for large documents
|
269
270
|
|
270
271
|
=== Future Features
|
271
272
|
|
data/data_active.gemspec
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path(
|
3
|
-
require
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'data_active/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
6
|
+
s.name = 'data_active'
|
7
7
|
s.version = DataActive::VERSION
|
8
|
-
s.authors = [
|
8
|
+
s.authors = ['Michael Harrison']
|
9
9
|
s.email = %w(michael@focalpause.com)
|
10
|
-
s.homepage =
|
10
|
+
s.homepage = 'https://github.com/michael-harrison/data_active'
|
11
11
|
s.summary = "data_active #{s.version}"
|
12
12
|
s.description = %q{Data Active is an extension of ActiveRecord that provides features to synchronise an ActiveRecord Model with a supplied XML document}
|
13
13
|
|
14
|
-
s.rubyforge_project =
|
14
|
+
s.rubyforge_project = 'data_active'
|
15
15
|
|
16
16
|
s.add_dependency 'nokogiri'
|
17
17
|
s.add_dependency 'rails'
|
18
18
|
s.add_development_dependency 'rake'
|
19
|
+
s.add_development_dependency 'rspec'
|
20
|
+
s.add_development_dependency 'rspec-rails'
|
19
21
|
|
20
22
|
s.files = `git ls-files`.split("\n")
|
21
23
|
s.test_files = `git ls-files -- {test_apps,test,spec,features}/*`.split("\n")
|
@@ -1,14 +1,15 @@
|
|
1
1
|
module StepHelper
|
2
2
|
def StepHelper.load_fixtures(path)
|
3
|
-
|
4
|
-
fixtures = Dir[File.join(fixtures_folder, '*.yml')].map {|f| File.basename(f, '.yml') }
|
3
|
+
fixtures = Dir[File.join(path, '*.yml')].map {|f| File.basename(f, '.yml') }
|
5
4
|
|
6
5
|
if defined? Fixtures == nil
|
6
|
+
puts 'create via Fixtures'
|
7
7
|
Fixtures.reset_cache
|
8
|
-
Fixtures.create_fixtures(
|
8
|
+
Fixtures.create_fixtures(path, fixtures)
|
9
9
|
else
|
10
|
+
puts 'create via ActiveRecord::Fixtures'
|
10
11
|
ActiveRecord::Fixtures.reset_cache
|
11
|
-
ActiveRecord::Fixtures.create_fixtures(
|
12
|
+
ActiveRecord::Fixtures.create_fixtures(path, fixtures)
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -21,18 +21,18 @@ When /^I have the "([^"]*)" file$/ do |xml_document_file|
|
|
21
21
|
end
|
22
22
|
|
23
23
|
When /^I synchronise with "([^"]*)"$/ do |xml_document_file|
|
24
|
-
Book.many_from_xml(File.open(Rails.root.join(xml_document_file))
|
24
|
+
Book.many_from_xml(File.open(Rails.root.join(xml_document_file)), [:sync]) != nil
|
25
25
|
end
|
26
26
|
|
27
27
|
When /^I synchronise with "([^"]*)" I should get an error$/ do |xml_document_file|
|
28
|
+
failed = true
|
28
29
|
begin
|
29
30
|
Book.many_from_xml(File.open(Rails.root.join(xml_document_file)).read, [:sync]) != nil
|
30
|
-
|
31
|
-
|
32
|
-
if e.message.exclude? "Too many records for one to one association"
|
33
|
-
fail "Wrong exception was raised"
|
34
|
-
end
|
31
|
+
rescue
|
32
|
+
failed = false
|
35
33
|
end
|
34
|
+
|
35
|
+
fail "Error was didn't happen" if failed
|
36
36
|
end
|
37
37
|
|
38
38
|
When /^I update with "([^"]*)"$/ do |xml_document_file|
|
@@ -284,7 +284,7 @@ When /^the database will contain identical pages for the chapters as those in "(
|
|
284
284
|
fail "Page with id #{page_id} is missing"
|
285
285
|
else
|
286
286
|
if page_content != page.content
|
287
|
-
|
287
|
+
fail "Page content in database doesn't match page content in xml for page with id #{page_id}, XML: #{page_content}, Database: #{page.content}"
|
288
288
|
end
|
289
289
|
|
290
290
|
if page_number != page.number.to_s
|
@@ -337,3 +337,7 @@ end
|
|
337
337
|
Then /^I should have a failure$/ do
|
338
338
|
fail "no error message" if @error_message.nil?
|
339
339
|
end
|
340
|
+
When /^I synchronise with "([^"]*)" I should only have one book price for all books$/ do |xml_document_file|
|
341
|
+
Book.many_from_xml(File.open(Rails.root.join(xml_document_file)).read, [:sync])
|
342
|
+
BookPrice.count.should eq 1
|
343
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'factory_girl'
|
2
|
+
|
3
|
+
class BookGenerator
|
4
|
+
def fresh
|
5
|
+
FactoryGirl.create_list(:book, 2).each do |book|
|
6
|
+
FactoryGirl.create(:book_price, book_id: book.id) if book.id.eql? 1
|
7
|
+
FactoryGirl.create_list(:chapter, 3, book_id: book.id).each do |chapter|
|
8
|
+
FactoryGirl.create_list(:page, )
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def changed
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def no_matching_records
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def without_chapters
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def without_one_to_one
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -34,19 +34,19 @@ book_1_chapter_2_page_1:
|
|
34
34
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
35
35
|
number: 1
|
36
36
|
|
37
|
-
|
37
|
+
book_1_chapter_2_page_2:
|
38
38
|
id: 6
|
39
39
|
chapter_id: 2
|
40
40
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
41
41
|
number: 2
|
42
42
|
|
43
|
-
|
43
|
+
book_1_chapter_2_page_3:
|
44
44
|
id: 7
|
45
45
|
chapter_id: 2
|
46
46
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
47
47
|
number: 3
|
48
48
|
|
49
|
-
|
49
|
+
book_1_chapter_2_page_4:
|
50
50
|
id: 8
|
51
51
|
chapter_id: 2
|
52
52
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
@@ -61,13 +61,13 @@ book_1_chapter_3_page_1:
|
|
61
61
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
62
62
|
number: 1
|
63
63
|
|
64
|
-
|
64
|
+
book_1_chapter_3_page_2:
|
65
65
|
id: 10
|
66
66
|
chapter_id: 3
|
67
67
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
68
68
|
number: 2
|
69
69
|
|
70
|
-
|
70
|
+
book_1_chapter_3_page_3:
|
71
71
|
id: 11
|
72
72
|
chapter_id: 3
|
73
73
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
@@ -109,19 +109,19 @@ book_2_chapter_2_page_1:
|
|
109
109
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
110
110
|
number: 1
|
111
111
|
|
112
|
-
|
112
|
+
book_2_chapter_2_page_2:
|
113
113
|
id: 17
|
114
114
|
chapter_id: 5
|
115
115
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
116
116
|
number: 2
|
117
117
|
|
118
|
-
|
118
|
+
book_2_chapter_2_page_3:
|
119
119
|
id: 18
|
120
120
|
chapter_id: 5
|
121
121
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
122
122
|
number: 3
|
123
123
|
|
124
|
-
|
124
|
+
book_2_chapter_2_page_4:
|
125
125
|
id: 19
|
126
126
|
chapter_id: 5
|
127
127
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
@@ -136,13 +136,13 @@ book_2_chapter_3_page_1:
|
|
136
136
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
137
137
|
number: 1
|
138
138
|
|
139
|
-
|
139
|
+
book_2_chapter_3_page_2:
|
140
140
|
id: 21
|
141
141
|
chapter_id: 6
|
142
142
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
143
143
|
number: 2
|
144
144
|
|
145
|
-
|
145
|
+
book_2_chapter_3_page_3:
|
146
146
|
id: 22
|
147
147
|
chapter_id: 6
|
148
148
|
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus purus nulla, condimentum vitae hendrerit nec, blandit et felis. Suspendisse vulputate mollis suscipit. Vivamus non libero quis urna gravida euismod quis in nisi. Morbi turpis orci, posuere nec ultrices ut, egestas ac purus. Morbi id pretium erat. In ullamcorper, ligula id porta pellentesque, sem turpis ultricies libero, non elementum ipsum neque at dui. Donec auctor nulla id mi dapibus id faucibus felis mollis. Curabitur imperdiet tristique nisi, consectetur molestie purus accumsan id. Curabitur lacinia diam et nisl iaculis eleifend. Quisque turpis elit, volutpat eget dapibus sed, egestas nec leo. Mauris dignissim tellus non lorem fringilla pharetra.
|
@@ -28,5 +28,5 @@ Feature: Synchronise database with XML
|
|
28
28
|
|
29
29
|
Examples:
|
30
30
|
| xml_file |
|
31
|
-
| features/support/fixtures/xml/
|
32
|
-
| features/support/fixtures/xml/ms_access/
|
31
|
+
| features/support/fixtures/xml/books_fresh.xml |
|
32
|
+
| features/support/fixtures/xml/ms_access/books_fresh.xml |
|
@@ -15,12 +15,6 @@ Feature: Synchronise one to many relationships
|
|
15
15
|
When I synchronise with "features/support/fixtures/xml/books_with_changed_chapters.xml"
|
16
16
|
Then the chapters will be identical to those in "features/support/fixtures/xml/books_with_changed_chapters.xml"
|
17
17
|
|
18
|
-
Scenario: Replace records in the database
|
19
|
-
Given I have a fresh set of books
|
20
|
-
And I have the "features/support/fixtures/xml/books_with_mismatched_chapters.xml" file
|
21
|
-
When I synchronise with "features/support/fixtures/xml/books_with_mismatched_chapters.xml"
|
22
|
-
Then the chapters will be identical to those in "features/support/fixtures/xml/books_with_mismatched_chapters.xml"
|
23
|
-
|
24
18
|
Scenario: Parent changes records in the database
|
25
19
|
Given I have a fresh set of books
|
26
20
|
And I have the "features/support/fixtures/xml/books_with_moved_chapters.xml" file
|
@@ -30,4 +30,4 @@ Feature: Synchronise one to one relationships
|
|
30
30
|
Scenario: Duplicate records in the xml
|
31
31
|
Given I have a fresh set of books
|
32
32
|
And I have the "features/support/fixtures/xml/books_with_many_one_to_one_records.xml" file
|
33
|
-
When I synchronise with "features/support/fixtures/xml/books_with_many_one_to_one_records.xml" I should
|
33
|
+
When I synchronise with "features/support/fixtures/xml/books_with_many_one_to_one_records.xml" I should only have one book price for all books
|
data/lib/data_active.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require 'data_active/version'
|
2
|
+
require 'data_active/entity'
|
3
|
+
require 'data_active/sax_document'
|
4
|
+
require 'data_active/parser'
|
5
|
+
require 'data_active/attribute'
|
2
6
|
|
3
7
|
module DataActive
|
4
8
|
def self.included(base)
|
@@ -15,290 +19,12 @@ module DataActive
|
|
15
19
|
|
16
20
|
module ClassMethods
|
17
21
|
def many_from_xml(source_xml, options = [])
|
18
|
-
|
19
|
-
|
22
|
+
parser = Nokogiri::XML::SAX::Parser.new(DataActive::SaxDocument.new(self.name.underscore, options))
|
23
|
+
parser.parse(source_xml)
|
20
24
|
end
|
21
25
|
|
22
26
|
def one_from_xml(source_xml, options = [])
|
23
|
-
|
24
|
-
|
25
|
-
current_node = root_node_in source_xml
|
26
|
-
|
27
|
-
if current_node.name.underscore.eql?(self.name.underscore)
|
28
|
-
# Load or create a new record
|
29
|
-
pk_node = current_node.xpath self.primary_key.to_s
|
30
|
-
|
31
|
-
active_record = find_or_create_based_on(pk_node)
|
32
|
-
|
33
|
-
unless active_record.nil?
|
34
|
-
# Process the attributes
|
35
|
-
if options.include? :update or options.include? :sync or options.include? :create
|
36
|
-
assign_attributes_from current_node, :to => active_record
|
37
|
-
if options.include? :fail_on_invalid and !active_record.valid?
|
38
|
-
messages = active_record.errors.messages.map { |attribute, messages| "#{attribute} #{messages.map { |message| message }.join(', ')}" }.join(', ')
|
39
|
-
raise "Found an invalid #{active_record.class.name} with the following errors: #{messages}. Source: #{current_node.to_s}"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Check through associations and apply sync appropriately
|
44
|
-
self.reflect_on_all_associations.each do |association|
|
45
|
-
foreign_key = foreign_key_from(association)
|
46
|
-
klass = association.klass
|
47
|
-
|
48
|
-
case
|
49
|
-
when association.macro == :has_many, association.macro == :has_and_belongs_to_many
|
50
|
-
instances = instances_for association, :from => current_node, :for => active_record
|
51
|
-
|
52
|
-
child_ids = []
|
53
|
-
instances.each do |instance|
|
54
|
-
new_record = klass.one_from_xml(instance, options)
|
55
|
-
if new_record != nil
|
56
|
-
child_ids << new_record[klass.primary_key]
|
57
|
-
active_record.__send__(klass.name.underscore.pluralize.to_sym) << new_record
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
unless active_record.new_record?
|
62
|
-
if options.include?(:sync) or options.include?(:destroy)
|
63
|
-
if child_ids.length > 0
|
64
|
-
klass.destroy_all [klass.primary_key.to_s + " not in (?) and #{foreign_key} = ?", child_ids.collect, active_record.attributes[self.primary_key.to_s]]
|
65
|
-
else
|
66
|
-
klass.destroy_all
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
when association.macro == :has_one
|
72
|
-
klass = association.klass
|
73
|
-
if active_record.new_record?
|
74
|
-
single_objects = current_node.xpath(".//#{association.name}")
|
75
|
-
else
|
76
|
-
record = klass.where(foreign_key => active_record.attributes[self.primary_key.to_s]).all
|
77
|
-
single_objects = current_node.xpath("//#{self.name.underscore}[#{self.primary_key}=#{active_record.attributes[self.primary_key.to_s]}]/#{association.name}")
|
78
|
-
end
|
79
|
-
|
80
|
-
if single_objects.count == 1
|
81
|
-
# Check to see if the already record exists
|
82
|
-
if record.present?
|
83
|
-
if record.count == 1
|
84
|
-
db_pk_value = record[0][klass.primary_key]
|
85
|
-
xml_pk_value = Integer(single_objects[0].element_children.xpath("//#{self.name.underscore}/#{klass.primary_key}").text)
|
86
|
-
|
87
|
-
if db_pk_value != xml_pk_value
|
88
|
-
# Different record in xml
|
89
|
-
if options.include?(:sync) or options.include?(:destroy)
|
90
|
-
# Delete the one in the database
|
91
|
-
klass.destroy(record[0][klass.primary_key])
|
92
|
-
end
|
93
|
-
end
|
94
|
-
elsif record.count > 1
|
95
|
-
raise "Too many records for one to one association in the database. Found #{record.count} records of '#{association.name}' for association with '#{self.name}'"
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
if options.include?(:create) or options.include?(:update) or options.include?(:sync)
|
100
|
-
new_record = klass.one_from_xml(single_objects[0], options)
|
101
|
-
if new_record != nil
|
102
|
-
new_record[foreign_key.to_sym] = active_record[self.primary_key.to_s]
|
103
|
-
if active_record.new_record?
|
104
|
-
active_record.send("#{klass.name.underscore.to_sym}=", new_record)
|
105
|
-
else
|
106
|
-
new_record.save!
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
elsif single_objects.count > 1
|
111
|
-
# There are more than one associations
|
112
|
-
raise "Too many records for one to one association in the provided XML. Found #{single_objects.count} records of '#{association.name}' for association with '#{self.name}'"
|
113
|
-
else
|
114
|
-
# There are no records in the XML
|
115
|
-
if record.present?
|
116
|
-
if record.count > 0 and options.include?(:sync) or options.include?(:destroy)
|
117
|
-
# Found some in the database: destroy then
|
118
|
-
klass.destroy_all("#{foreign_key} = #{active_record.attributes[self.primary_key.to_s]}")
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
when association.macro == :belongs_to
|
124
|
-
|
125
|
-
else
|
126
|
-
raise "unsupported association #{association.macro} for #{association.name } on #{self.name}"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
# Save the record
|
131
|
-
if options.include? :sync
|
132
|
-
# Doing complete synchronisation with XML
|
133
|
-
active_record.save
|
134
|
-
elsif options.include?(:create) and active_record.new_record?
|
135
|
-
active_record.save
|
136
|
-
elsif options.include?(:update) and not active_record.new_record?
|
137
|
-
active_record.save
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
active_record
|
142
|
-
else
|
143
|
-
raise "The supplied XML (#{current_node.name}) cannot be mapped to this class (#{self.name})"
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
private
|
148
|
-
def many_from(current_node)
|
149
|
-
case
|
150
|
-
when (is_ms_access_xml?(current_node) or is_rails_like_xml?(current_node))
|
151
|
-
process_children_for current_node
|
152
|
-
|
153
|
-
when self.name.underscore.eql?(current_node.name.underscore)
|
154
|
-
raise "The supplied XML (#{current_node.name}) is a single instance of '#{self.name}'. Please use one_from_xml"
|
155
|
-
|
156
|
-
else
|
157
|
-
raise "The supplied XML (#{current_node.name}) cannot be mapped to this class (#{self.name})"
|
158
|
-
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def process_children_for(current_node)
|
163
|
-
records = []
|
164
|
-
recorded_ids = []
|
165
|
-
|
166
|
-
current_node.xpath(".//#{self.name.underscore}").each do |node|
|
167
|
-
record = self.one_from_xml(node, @data_active_options)
|
168
|
-
if record
|
169
|
-
recorded_ids << record[primary_key.to_sym]
|
170
|
-
records << record
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
remove_records_not_in recorded_ids
|
175
|
-
records
|
176
|
-
end
|
177
|
-
|
178
|
-
def is_ms_access_xml?(node)
|
179
|
-
node.name.eql?('dataroot') and node.namespace_definitions.map { |ns| ns.href }.include?('urn:schemas-microsoft-com:officedata')
|
180
|
-
end
|
181
|
-
|
182
|
-
def is_rails_like_xml?(current_node)
|
183
|
-
self.name.pluralize.underscore.eql?(current_node.name.underscore)
|
184
|
-
end
|
185
|
-
|
186
|
-
def xml_node_matches_class(xml_node)
|
187
|
-
if xml_node.attributes['type'].blank?
|
188
|
-
xml_node.name.underscore == self.name.underscore
|
189
|
-
else
|
190
|
-
xml_node.attributes['type'].value.underscore == self.name.underscore
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def find_or_create_based_on(pk_node)
|
195
|
-
ar = nil
|
196
|
-
if pk_node
|
197
|
-
begin
|
198
|
-
ar = find pk_node.text
|
199
|
-
rescue
|
200
|
-
# No record exists, create a new one
|
201
|
-
if @data_active_options.include?(:sync) or @data_active_options.include?(:create)
|
202
|
-
ar = self.new
|
203
|
-
end
|
204
|
-
end
|
205
|
-
else
|
206
|
-
# No primary key value, must be a new record
|
207
|
-
if @data_active_options.include?(:sync) or @data_active_options.include?(:create)
|
208
|
-
ar = self.new
|
209
|
-
end
|
210
|
-
end
|
211
|
-
ar
|
212
|
-
end
|
213
|
-
|
214
|
-
def assign_attributes_from(current_node, options)
|
215
|
-
record = options[:to]
|
216
|
-
|
217
|
-
record.attributes.each do |name, value|
|
218
|
-
attribute_nodes = current_node.xpath name.to_s
|
219
|
-
if attribute_nodes.count == 1
|
220
|
-
if attribute_nodes[0].attributes['nil'].try(:value)
|
221
|
-
record[name] = nil
|
222
|
-
else
|
223
|
-
record[name] = attribute_nodes[0].text
|
224
|
-
end
|
225
|
-
elsif attribute_nodes.count > 1
|
226
|
-
raise "Found duplicate elements in xml for active record attribute '#{name}'"
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def instances_for(association, options)
|
232
|
-
current_node = options[:from]
|
233
|
-
active_record = options[:for]
|
234
|
-
|
235
|
-
# Attempt to find instances which are in the following format
|
236
|
-
# <books>
|
237
|
-
# <book>
|
238
|
-
# ...
|
239
|
-
# </book>
|
240
|
-
# <book>
|
241
|
-
# ...
|
242
|
-
# </book>
|
243
|
-
# </books>
|
244
|
-
if active_record.new_record?
|
245
|
-
results = current_node.xpath("#{association.name}")
|
246
|
-
else
|
247
|
-
results = current_node.xpath("//#{self.name.underscore}[#{self.primary_key}=#{active_record.attributes[self.primary_key.to_s]}]/#{association.name}")
|
248
|
-
end
|
249
|
-
|
250
|
-
|
251
|
-
if results.count.eql? 0
|
252
|
-
# Attempt to find instances which are in the following format
|
253
|
-
# <book>
|
254
|
-
# ...
|
255
|
-
# </book>
|
256
|
-
if active_record.new_record?
|
257
|
-
results = current_node.xpath("#{association.name.to_s.singularize}")
|
258
|
-
else
|
259
|
-
results = current_node.xpath("//#{self.name.underscore}[#{self.primary_key}=#{active_record.attributes[self.primary_key.to_s]}]/#{association.name.to_s.singularize}")
|
260
|
-
end
|
261
|
-
else
|
262
|
-
results = results.first.element_children
|
263
|
-
end
|
264
|
-
|
265
|
-
results
|
266
|
-
end
|
267
|
-
|
268
|
-
def foreign_key_from(association)
|
269
|
-
if ActiveRecord::Reflection::AssociationReflection.method_defined? :foreign_key
|
270
|
-
# Support for Rails 3.1 and later
|
271
|
-
foreign_key = association.foreign_key
|
272
|
-
elsif ActiveRecord::Reflection::AssociationReflection.method_defined? :primary_key_name
|
273
|
-
# Support for Rails earlier than 3.1
|
274
|
-
foreign_key = association.primary_key_name
|
275
|
-
else
|
276
|
-
raise 'Unsupported version of ActiveRecord. Unable to identify the foreign key.'
|
277
|
-
end
|
278
|
-
foreign_key
|
279
|
-
end
|
280
|
-
|
281
|
-
def root_node_in(source_xml)
|
282
|
-
if source_xml.is_a?(String)
|
283
|
-
doc = Nokogiri::XML(source_xml)
|
284
|
-
doc.children.first
|
285
|
-
else
|
286
|
-
source_xml
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
def remove_records_not_in(recorded_ids)
|
291
|
-
if @data_active_options.include?(:sync)
|
292
|
-
if recorded_ids.length > 0
|
293
|
-
self.destroy_all [self.primary_key.to_s + " not in (?)", recorded_ids.collect]
|
294
|
-
end
|
295
|
-
elsif @data_active_options.include?(:destroy)
|
296
|
-
if recorded_ids.length > 0
|
297
|
-
self.destroy_all [self.primary_key.to_s + " not in (?)", recorded_ids.collect]
|
298
|
-
else
|
299
|
-
self.destroy_all
|
300
|
-
end
|
301
|
-
end
|
27
|
+
many_from_xml(source_xml, options)
|
302
28
|
end
|
303
29
|
end
|
304
30
|
end
|