blythedunham-ar_dumper 1.2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +77 -0
  3. data/Rakefile +76 -0
  4. data/db/migrate/generic_schema.rb +31 -0
  5. data/init.rb +3 -0
  6. data/lib/ar_dumper.rb +5 -0
  7. data/lib/ar_dumper_active_record.rb +31 -0
  8. data/lib/ar_dumper_base.rb +505 -0
  9. data/lib/ar_dumper_controller.rb +52 -0
  10. data/lib/xml_serializer_dumper_support.rb +49 -0
  11. data/test/ar_dumper_controller_test.rb +10 -0
  12. data/test/ar_dumper_test.rb +101 -0
  13. data/test/data/expected_results/all_books.csv +10 -0
  14. data/test/data/expected_results/all_books.xml +93 -0
  15. data/test/data/expected_results/all_books.yml +90 -0
  16. data/test/data/expected_results/except_title_and_author.csv +10 -0
  17. data/test/data/expected_results/except_title_and_author.xml +75 -0
  18. data/test/data/expected_results/except_title_and_author.yml +72 -0
  19. data/test/data/expected_results/only_title_and_author.csv +10 -0
  20. data/test/data/expected_results/only_title_and_author.xml +39 -0
  21. data/test/data/expected_results/only_title_and_author.yml +36 -0
  22. data/test/data/expected_results/proc.csv +3 -0
  23. data/test/data/expected_results/proc.xml +11 -0
  24. data/test/data/expected_results/proc.yml +8 -0
  25. data/test/data/expected_results/second_book.csv +2 -0
  26. data/test/data/expected_results/second_book.xml +13 -0
  27. data/test/data/expected_results/second_book.yml +10 -0
  28. data/test/data/expected_results/topic_name.csv +10 -0
  29. data/test/data/expected_results/topic_name.xml +48 -0
  30. data/test/data/expected_results/topic_name.yml +45 -0
  31. data/test/fixtures/books.yml +65 -0
  32. data/test/fixtures/topics.yml +5 -0
  33. data/test/models/book.rb +8 -0
  34. data/test/models/topic.rb +13 -0
  35. data/test/run.rb +38 -0
  36. data/test/test_helper.rb +60 -0
  37. metadata +96 -0
@@ -0,0 +1,52 @@
1
+ class ActionController::Base
2
+
3
+ #Change to :file to use regular files
4
+ cattr_accessor :dumper_target
5
+ @@dumper_target = :tmp_file
6
+
7
+
8
+ # send a dumped file
9
+ # Options are:
10
+ # <tt>klass</tt> - the active record to use. Animal or Contact or Author or Tumama
11
+ # <tt>send_options</tt> send_options include send_file options such as <tt>:type</tt> and <tt>:disposition</tt>
12
+ # <tt>dump_options</tt> options to send to the dumper. These options are in <tt>ArDumper.dump_to_csv</tt>
13
+ # * <tt>:find</tt> - a map of the finder options passed to find. For example, <tt>{:conditions => ['features.hair = ?', 'blonde'], :include => :features}</tt>
14
+ # * <tt>:header</tt> - when a hash is specified, maps the field name to the header name. For example <tt>{:a => 'COL A', :b => 'COL B'}</tt> would print 'COL A', 'COL B'
15
+ # when an array is specified uses this instead of the fields
16
+ # when true or by default prints the fields
17
+ # when false does not include a header
18
+ # * <tt>:csv</tt> - any options to pass to csv parser. Example <tt> :csv => { :col_sep => "\t" }</tt>
19
+ # * <tt>:xml</tt> - any options to pass to xml parser. Example <tt> :xml => { :indent => 4 } </tt>
20
+ # * <tt>:page_size</tt> - the page size to use. Defaults to dumper_page_size or 50
21
+ def send_file_dump(format, klass, send_options={}, dump_options={})
22
+
23
+ #use the base name of the dump file name if specified
24
+ send_options[:filename] ||= File.basename(dump_options[:filename]) if dump_options[:filename]
25
+
26
+ #never delete the file
27
+ dump_options[:delete_file] = false
28
+
29
+ #use temporary files unless otherwise specified
30
+ dump_options[:target_type]||= @@dumper_target
31
+
32
+ #send_options[:type] = 'application/xml; charset=utf-8;'
33
+ if send_options[:type].nil? && send_options[:disposition] == 'inline'
34
+ send_options[:type] =
35
+ case format.to_sym
36
+ when :xml then 'application/xml; charset=utf-8;'
37
+ when :csv then 'application/csv; charset=utf-8;'
38
+ when :yml then 'text/html; charset=utf-8;'
39
+ end
40
+
41
+ end
42
+
43
+ target = klass.dumper(format, dump_options)
44
+
45
+ if dump_options[:target_type] == :string
46
+ send_data(target, send_options)
47
+ else
48
+ send_file(target, send_options)
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,49 @@
1
+ # Add support to ActiveRecord::XmlSerializer to have value based tags
2
+ class ActiveRecord::XmlSerializer
3
+ #Add ability to set margin with options to serializer
4
+ def builder_with_margin#:nodoc:
5
+ @builder ||= begin
6
+ options[:indent] ||= 2
7
+ options[:margin] ||= 0
8
+ builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent], :margin => options[:margin])
9
+
10
+ unless options[:skip_instruct]
11
+ builder.instruct!
12
+ options[:skip_instruct] = true
13
+ end
14
+
15
+ builder
16
+ end
17
+ end
18
+
19
+ alias_method_chain :builder, :margin
20
+
21
+ # Adds a tag based on the name and value and the serializer options
22
+ # Decorations for the tag are derived from the value type
23
+ #
24
+ # add_tag_for_value('my_tag', "some data")
25
+ # # XML: <my-tag>some data</my-tag>
26
+ def add_tag_for_value(name, value)
27
+ add_tag(ValueAttribute.new(name, @record, value))
28
+ end
29
+
30
+
31
+ # An attribute where the type and value are based on the
32
+ # values when created
33
+ class ValueAttribute < Attribute
34
+ def initialize(name, record, value)
35
+ @value = value
36
+ super(name, record)
37
+ end
38
+
39
+ def compute_value#:nodoc:
40
+ @value
41
+ end
42
+
43
+ def compute_type#:nodoc:
44
+ Hash::XML_TYPE_NAMES[@value.class.name] || :string
45
+ end
46
+ end
47
+ end
48
+
49
+ #ActiveRecord::XmlSerializer.send :include, XmlSerializerDumperSupport unless ActiveRecord.respond_to?(:add_procs_with_record)
@@ -0,0 +1,10 @@
1
+ require File.join( File.dirname( __FILE__ ), 'test_helper' )
2
+
3
+ class ArDumperControllerTest < TestCaseSuperClass
4
+ fixtures :books
5
+
6
+ def test_dumper_should_dump_csv
7
+ assert(true)
8
+ end
9
+
10
+ end
@@ -0,0 +1,101 @@
1
+ require File.join( File.dirname( __FILE__ ), 'test_helper' )
2
+
3
+ class ArDumperTest< TestCaseSuperClass
4
+
5
+ fixtures :books, :topics
6
+
7
+ #This allows us to easily create tests for all three output types
8
+ def self.add_dump_test(options={})
9
+ proc = options[:dump_options].delete(:procs) if options[:dump_options] && options[:dump_options][:procs]
10
+ dump_options_str = (options[:dump_options]||{}).inspect
11
+
12
+ if proc
13
+ proc_str = proc.collect{|k,v| "#{k.inspect} => #{v}"}.join(',')
14
+ dump_options_str.gsub!(/\}$/, ", :procs => { #{proc_str} } }")
15
+ dump_options_str.gsub!(/^\{,/, '{')
16
+ end
17
+
18
+ %w(xml yml csv).each do |export_type|
19
+
20
+ new_method = %{
21
+ def test_dumper_should_#{options[:name]||options[:expected_result]}_#{export_type}
22
+ file_name = Book.dumper :#{export_type}, #{dump_options_str}
23
+ result_file = assert_result_files(file_name, :expected_results => '#{options[:expected_result]}.#{export_type}',
24
+ :file_name_match => /ardumper\\.book\\.\\d+\\.\\d+\\.#{export_type}/).first
25
+ end
26
+ }
27
+ #puts new_method
28
+ class_eval new_method, __FILE__, __LINE__
29
+ end
30
+ end
31
+
32
+ def setup
33
+ super
34
+ FileUtils.rm_r ArDumper.dumper_file_path, :force => true
35
+ FileUtils.mkdir_p ArDumper.dumper_file_path
36
+ end
37
+
38
+ def teardown
39
+ super
40
+ FileUtils.rm_r ArDumper.dumper_file_path
41
+ end
42
+
43
+ add_dump_test :name => 'dump', :expected_result => 'all_books'
44
+ add_dump_test :name => 'dump_second_book', :expected_result => 'second_book',
45
+ :dump_options => {:find => {:conditions => 'id = 2'}}
46
+
47
+
48
+ add_dump_test :name => 'dump_only_option', :expected_result => 'only_title_and_author',
49
+ :dump_options => {:only => [:title, :author_name]}
50
+
51
+
52
+ add_dump_test :name => 'dump_except_option', :expected_result => 'except_title_and_author',
53
+ :dump_options => {:except => [:title, :author_name]}
54
+
55
+
56
+ add_dump_test :name => 'dump_only_and_method', :expected_result => 'topic_name',
57
+ :dump_options => {:only => [:title, :author_name], :methods => [:topic_name]}
58
+
59
+ add_dump_test(:name => 'dump_custom_proc', :expected_result => 'proc',
60
+ :dump_options => {
61
+ :find => {:conditions => 'id in (3,4)'},
62
+ :only => [:author_name],
63
+ :procs => {:topic_content => "Proc.new{|options| options[:record].topic ? options[:record].topic.content : 'NO CONTENT' }"}})
64
+
65
+
66
+ protected
67
+
68
+ def parse_csv( csv )
69
+ parsed_csv = FasterCSV.parse( csv )
70
+ headers = parsed_csv.first
71
+ data = parsed_csv[1..-1]
72
+ OpenStruct.new :headers=>headers, :data=>data, :size=>parsed_csv.size
73
+ end
74
+
75
+ def result_dir
76
+ @result_dir ||= File.expand_path(File.join(File.dirname( __FILE__ ), 'data', 'results'))
77
+ end
78
+
79
+ def assert_result_files(file_name, options={})
80
+
81
+ assert_equal(result_dir, File.expand_path(File.dirname(file_name)))
82
+ assert file_name =~ options[:file_name_match], "File name #{file_name} did not match #{options[:file_name_match].inspect}" if options[:file_name_match]
83
+
84
+ result_files = Dir.glob(File.join(ArDumper.dumper_file_path, '*'))
85
+
86
+ assert_equal(options[:file_count]||1, result_files.length)
87
+
88
+ if options[:expected_results]
89
+ assert_equal(File.read(expected_results_file(options[:expected_results])),
90
+ File.read(result_files.first))
91
+ end
92
+
93
+ result_files
94
+ end
95
+
96
+ def expected_results_file(file_name)
97
+ @expected_results_dir ||= File.expand_path(File.join(File.dirname( __FILE__ ), 'data', 'expected_results'))
98
+ File.join(@expected_results_dir, file_name)
99
+ end
100
+
101
+ end
@@ -0,0 +1,10 @@
1
+ id,title,publisher,author_name,created_at,updated_at,topic_id,for_sale
2
+ 1,The Voyage of Jerle Shannara: Antrax,Del Rey,Terry Brooks,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
3
+ 2,The Voyage of Jerle Shannara: Ilse Witch,Del Rey,Terry Brooks,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,false
4
+ 3,The Voyage of Jerle Shannara: Jarka Ruus,Del Rey,Terry Brooks,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
5
+ 4,The Voyage of Jerle Shannara: Morgwar,Del Rey,Terry Brooks,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,4,true
6
+ 5,Circle At Center,ACE Fantasy,Douglas Niles,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
7
+ 6,WISDOM for the way,Hallmark Books,Charles R. Swindoll,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
8
+ 7,High Tech Startup,Free Press,John L. Neshem,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
9
+ 8,The Two Swords,Forgotten Realms,R.A. Salvatore,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
10
+ 9,Come Thirsty,W Publishing Group,Max Lucado,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
@@ -0,0 +1,93 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <books>
3
+ <book>
4
+ <author-name>Terry Brooks</author-name>
5
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
6
+ <for-sale type="boolean">true</for-sale>
7
+ <id type="integer">1</id>
8
+ <publisher>Del Rey</publisher>
9
+ <title>The Voyage of Jerle Shannara: Antrax</title>
10
+ <topic-id type="integer" nil="true"></topic-id>
11
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
12
+ </book>
13
+ <book>
14
+ <author-name>Terry Brooks</author-name>
15
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
16
+ <for-sale type="boolean">false</for-sale>
17
+ <id type="integer">2</id>
18
+ <publisher>Del Rey</publisher>
19
+ <title>The Voyage of Jerle Shannara: Ilse Witch</title>
20
+ <topic-id type="integer" nil="true"></topic-id>
21
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
22
+ </book>
23
+ <book>
24
+ <author-name>Terry Brooks</author-name>
25
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
26
+ <for-sale type="boolean">true</for-sale>
27
+ <id type="integer">3</id>
28
+ <publisher>Del Rey</publisher>
29
+ <title>The Voyage of Jerle Shannara: Jarka Ruus</title>
30
+ <topic-id type="integer" nil="true"></topic-id>
31
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
32
+ </book>
33
+ <book>
34
+ <author-name>Terry Brooks</author-name>
35
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
36
+ <for-sale type="boolean">true</for-sale>
37
+ <id type="integer">4</id>
38
+ <publisher>Del Rey</publisher>
39
+ <title>The Voyage of Jerle Shannara: Morgwar</title>
40
+ <topic-id type="integer">4</topic-id>
41
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
42
+ </book>
43
+ <book>
44
+ <author-name>Douglas Niles</author-name>
45
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
46
+ <for-sale type="boolean">true</for-sale>
47
+ <id type="integer">5</id>
48
+ <publisher>ACE Fantasy</publisher>
49
+ <title>Circle At Center</title>
50
+ <topic-id type="integer" nil="true"></topic-id>
51
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
52
+ </book>
53
+ <book>
54
+ <author-name>Charles R. Swindoll</author-name>
55
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
56
+ <for-sale type="boolean">true</for-sale>
57
+ <id type="integer">6</id>
58
+ <publisher>Hallmark Books</publisher>
59
+ <title>WISDOM for the way</title>
60
+ <topic-id type="integer" nil="true"></topic-id>
61
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
62
+ </book>
63
+ <book>
64
+ <author-name>John L. Neshem</author-name>
65
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
66
+ <for-sale type="boolean">true</for-sale>
67
+ <id type="integer">7</id>
68
+ <publisher>Free Press</publisher>
69
+ <title>High Tech Startup</title>
70
+ <topic-id type="integer" nil="true"></topic-id>
71
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
72
+ </book>
73
+ <book>
74
+ <author-name>R.A. Salvatore</author-name>
75
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
76
+ <for-sale type="boolean">true</for-sale>
77
+ <id type="integer">8</id>
78
+ <publisher>Forgotten Realms</publisher>
79
+ <title>The Two Swords</title>
80
+ <topic-id type="integer" nil="true"></topic-id>
81
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
82
+ </book>
83
+ <book>
84
+ <author-name>Max Lucado</author-name>
85
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
86
+ <for-sale type="boolean">true</for-sale>
87
+ <id type="integer">9</id>
88
+ <publisher>W Publishing Group</publisher>
89
+ <title>Come Thirsty</title>
90
+ <topic-id type="integer" nil="true"></topic-id>
91
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
92
+ </book>
93
+ </books>
@@ -0,0 +1,90 @@
1
+ ---
2
+ book_1:
3
+ topic_id: ""
4
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
5
+ author_name: Terry Brooks
6
+ title: "The Voyage of Jerle Shannara: Antrax"
7
+ id: "1"
8
+ publisher: Del Rey
9
+ for_sale: "true"
10
+ created_at: Sun Apr 12 21:17:34 -0600 2009
11
+
12
+ book_2:
13
+ topic_id: ""
14
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
15
+ author_name: Terry Brooks
16
+ title: "The Voyage of Jerle Shannara: Ilse Witch"
17
+ id: "2"
18
+ publisher: Del Rey
19
+ for_sale: "false"
20
+ created_at: Sun Apr 12 21:17:34 -0600 2009
21
+
22
+ book_3:
23
+ topic_id: ""
24
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
25
+ author_name: Terry Brooks
26
+ title: "The Voyage of Jerle Shannara: Jarka Ruus"
27
+ id: "3"
28
+ publisher: Del Rey
29
+ for_sale: "true"
30
+ created_at: Sun Apr 12 21:17:34 -0600 2009
31
+
32
+ book_4:
33
+ topic_id: "4"
34
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
35
+ author_name: Terry Brooks
36
+ title: "The Voyage of Jerle Shannara: Morgwar"
37
+ id: "4"
38
+ publisher: Del Rey
39
+ for_sale: "true"
40
+ created_at: Sun Apr 12 21:17:34 -0600 2009
41
+
42
+ book_5:
43
+ topic_id: ""
44
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
45
+ author_name: Douglas Niles
46
+ title: Circle At Center
47
+ id: "5"
48
+ publisher: ACE Fantasy
49
+ for_sale: "true"
50
+ created_at: Sun Apr 12 21:17:34 -0600 2009
51
+
52
+ book_6:
53
+ topic_id: ""
54
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
55
+ author_name: Charles R. Swindoll
56
+ title: WISDOM for the way
57
+ id: "6"
58
+ publisher: Hallmark Books
59
+ for_sale: "true"
60
+ created_at: Sun Apr 12 21:17:34 -0600 2009
61
+
62
+ book_7:
63
+ topic_id: ""
64
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
65
+ author_name: John L. Neshem
66
+ title: High Tech Startup
67
+ id: "7"
68
+ publisher: Free Press
69
+ for_sale: "true"
70
+ created_at: Sun Apr 12 21:17:34 -0600 2009
71
+
72
+ book_8:
73
+ topic_id: ""
74
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
75
+ author_name: R.A. Salvatore
76
+ title: The Two Swords
77
+ id: "8"
78
+ publisher: Forgotten Realms
79
+ for_sale: "true"
80
+ created_at: Sun Apr 12 21:17:34 -0600 2009
81
+
82
+ book_9:
83
+ topic_id: ""
84
+ updated_at: Sun Apr 12 21:17:34 -0600 2009
85
+ author_name: Max Lucado
86
+ title: Come Thirsty
87
+ id: "9"
88
+ publisher: W Publishing Group
89
+ for_sale: "true"
90
+ created_at: Sun Apr 12 21:17:34 -0600 2009
@@ -0,0 +1,10 @@
1
+ id,publisher,created_at,updated_at,topic_id,for_sale
2
+ 1,Del Rey,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
3
+ 2,Del Rey,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,false
4
+ 3,Del Rey,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
5
+ 4,Del Rey,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,4,true
6
+ 5,ACE Fantasy,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
7
+ 6,Hallmark Books,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
8
+ 7,Free Press,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
9
+ 8,Forgotten Realms,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
10
+ 9,W Publishing Group,Sun Apr 12 21:17:34 -0600 2009,Sun Apr 12 21:17:34 -0600 2009,,true
@@ -0,0 +1,75 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <books>
3
+ <book>
4
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
5
+ <for-sale type="boolean">true</for-sale>
6
+ <id type="integer">1</id>
7
+ <publisher>Del Rey</publisher>
8
+ <topic-id type="integer" nil="true"></topic-id>
9
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
10
+ </book>
11
+ <book>
12
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
13
+ <for-sale type="boolean">false</for-sale>
14
+ <id type="integer">2</id>
15
+ <publisher>Del Rey</publisher>
16
+ <topic-id type="integer" nil="true"></topic-id>
17
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
18
+ </book>
19
+ <book>
20
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
21
+ <for-sale type="boolean">true</for-sale>
22
+ <id type="integer">3</id>
23
+ <publisher>Del Rey</publisher>
24
+ <topic-id type="integer" nil="true"></topic-id>
25
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
26
+ </book>
27
+ <book>
28
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
29
+ <for-sale type="boolean">true</for-sale>
30
+ <id type="integer">4</id>
31
+ <publisher>Del Rey</publisher>
32
+ <topic-id type="integer">4</topic-id>
33
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
34
+ </book>
35
+ <book>
36
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
37
+ <for-sale type="boolean">true</for-sale>
38
+ <id type="integer">5</id>
39
+ <publisher>ACE Fantasy</publisher>
40
+ <topic-id type="integer" nil="true"></topic-id>
41
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
42
+ </book>
43
+ <book>
44
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
45
+ <for-sale type="boolean">true</for-sale>
46
+ <id type="integer">6</id>
47
+ <publisher>Hallmark Books</publisher>
48
+ <topic-id type="integer" nil="true"></topic-id>
49
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
50
+ </book>
51
+ <book>
52
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
53
+ <for-sale type="boolean">true</for-sale>
54
+ <id type="integer">7</id>
55
+ <publisher>Free Press</publisher>
56
+ <topic-id type="integer" nil="true"></topic-id>
57
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
58
+ </book>
59
+ <book>
60
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
61
+ <for-sale type="boolean">true</for-sale>
62
+ <id type="integer">8</id>
63
+ <publisher>Forgotten Realms</publisher>
64
+ <topic-id type="integer" nil="true"></topic-id>
65
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
66
+ </book>
67
+ <book>
68
+ <created-at type="datetime">2009-04-12T21:17:34-06:00</created-at>
69
+ <for-sale type="boolean">true</for-sale>
70
+ <id type="integer">9</id>
71
+ <publisher>W Publishing Group</publisher>
72
+ <topic-id type="integer" nil="true"></topic-id>
73
+ <updated-at type="datetime">2009-04-12T21:17:34-06:00</updated-at>
74
+ </book>
75
+ </books>