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.
@@ -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>:fail_on_invalid</b> will raise an exception when an invalid record is found (i.e. one that doesn't pass the model validity rules). When this option isn't used Data Active will fail silently not saving the invalid record.
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
 
@@ -1,21 +1,23 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "data_active/version"
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'data_active/version'
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = "data_active"
6
+ s.name = 'data_active'
7
7
  s.version = DataActive::VERSION
8
- s.authors = ["Michael Harrison"]
8
+ s.authors = ['Michael Harrison']
9
9
  s.email = %w(michael@focalpause.com)
10
- s.homepage = "https://github.com/michael-harrison/data_active"
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 = "data_active"
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
- fixtures_folder = path
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(fixtures_folder, 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(fixtures_folder, 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)).read, [:sync]) != nil
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
- fail "Error was didn't happen"
31
- rescue Exception => e
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
- file "Page content in database doesn't match page content in xml for page with id #{page_id}, XML: #{page_content}, Database: #{page.content}"
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
@@ -0,0 +1,6 @@
1
+ FactoryGirl.define do
2
+ factory :book do
3
+ sequence(:id) { |n| n }
4
+ sequence(:name) { |n| "Book #{n}" }
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ FactoryGirl.define do
2
+ factory :book_price do
3
+ sequence(:id) { |n| n }
4
+ sequence(:sell) { |n| n * 50.0 }
5
+ sequence(:educational) { |n| n * 35.0 }
6
+ sequence(:cost) { |n| n * 20.0 }
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ FactoryGirl.define do
2
+ factory :chapter do
3
+ sequence(:id) { |n| n }
4
+ sequence(:title) { |n| "Chapter #{n}" }
5
+ sequence(:introduction) { |n| 1.upto(n).each { 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }.join(' ') }
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ FactoryGirl.define do
2
+ factory :page do
3
+ sequence(:id) { |n| n }
4
+ sequence(:number) { |n| "Chapter #{n}" }
5
+ sequence(:content) { |n| 1.upto(n).each { 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' }.join(' ') }
6
+ end
7
+ 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
- book_1_chapter_2_page_1:
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
- book_1_chapter_2_page_1:
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
- book_1_chapter_2_page_1:
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
- book_1_chapter_3_page_1:
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
- book_1_chapter_3_page_1:
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
- book_2_chapter_2_page_1:
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
- book_2_chapter_2_page_1:
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
- book_2_chapter_2_page_1:
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
- book_2_chapter_3_page_1:
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
- book_2_chapter_3_page_1:
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/books_changed.xml |
32
- | features/support/fixtures/xml/ms_access/books_changed.xml |
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 get an error
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
@@ -1,4 +1,8 @@
1
- require "data_active/version"
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
- @data_active_options = options
19
- many_from root_node_in source_xml
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
- @data_active_options = options
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