data_active 0.0.6 → 0.0.7
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.
- 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
         
     |