square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +359 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -0
@@ -0,0 +1,59 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ # = Active Record Schema
5
+ #
6
+ # Allows programmers to programmatically define a schema in a portable
7
+ # DSL. This means you can define tables, indexes, etc. without using SQL
8
+ # directly, so your applications can more easily support multiple
9
+ # databases.
10
+ #
11
+ # Usage:
12
+ #
13
+ # ActiveRecord::Schema.define do
14
+ # create_table :authors do |t|
15
+ # t.string :name, :null => false
16
+ # end
17
+ #
18
+ # add_index :authors, :name, :unique
19
+ #
20
+ # create_table :posts do |t|
21
+ # t.integer :author_id, :null => false
22
+ # t.string :subject
23
+ # t.text :body
24
+ # t.boolean :private, :default => false
25
+ # end
26
+ #
27
+ # add_index :posts, :author_id
28
+ # end
29
+ #
30
+ # ActiveRecord::Schema is only supported by database adapters that also
31
+ # support migrations, the two features being very similar.
32
+ class Schema < Migration
33
+ private_class_method :new
34
+
35
+ def self.migrations_path
36
+ ActiveRecord::Migrator.migrations_path
37
+ end
38
+
39
+ # Eval the given block. All methods available to the current connection
40
+ # adapter are available within the block, so you can easily use the
41
+ # database definition DSL to build up your schema (+create_table+,
42
+ # +add_index+, etc.).
43
+ #
44
+ # The +info+ hash is optional, and if given is used to define metadata
45
+ # about the current schema (currently, only the schema's version):
46
+ #
47
+ # ActiveRecord::Schema.define(:version => 20380119000001) do
48
+ # ...
49
+ # end
50
+ def self.define(info={}, &block)
51
+ instance_eval(&block)
52
+
53
+ unless info[:version].blank?
54
+ initialize_schema_migrations_table
55
+ assume_migrated_upto_version(info[:version], migrations_path)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,195 @@
1
+ require 'stringio'
2
+ require 'active_support/core_ext/big_decimal'
3
+
4
+ module ActiveRecord
5
+ # = Active Record Schema Dumper
6
+ #
7
+ # This class is used to dump the database schema for some connection to some
8
+ # output format (i.e., ActiveRecord::Schema).
9
+ class SchemaDumper #:nodoc:
10
+ private_class_method :new
11
+
12
+ ##
13
+ # :singleton-method:
14
+ # A list of tables which should not be dumped to the schema.
15
+ # Acceptable values are strings as well as regexp.
16
+ # This setting is only used if ActiveRecord::Base.schema_format == :ruby
17
+ cattr_accessor :ignore_tables
18
+ @@ignore_tables = []
19
+
20
+ def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
21
+ new(connection).dump(stream)
22
+ stream
23
+ end
24
+
25
+ def dump(stream)
26
+ header(stream)
27
+ tables(stream)
28
+ trailer(stream)
29
+ stream
30
+ end
31
+
32
+ private
33
+
34
+ def initialize(connection)
35
+ @connection = connection
36
+ @types = @connection.native_database_types
37
+ @version = Migrator::current_version rescue nil
38
+ end
39
+
40
+ def header(stream)
41
+ define_params = @version ? ":version => #{@version}" : ""
42
+
43
+ stream.puts <<HEADER
44
+ # This file is auto-generated from the current state of the database. Instead
45
+ # of editing this file, please use the migrations feature of Active Record to
46
+ # incrementally modify your database, and then regenerate this schema definition.
47
+ #
48
+ # Note that this schema.rb definition is the authoritative source for your
49
+ # database schema. If you need to create the application database on another
50
+ # system, you should be using db:schema:load, not running all the migrations
51
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
52
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
53
+ #
54
+ # It's strongly recommended to check this file into your version control system.
55
+
56
+ ActiveRecord::Schema.define(#{define_params}) do
57
+
58
+ HEADER
59
+ end
60
+
61
+ def trailer(stream)
62
+ stream.puts "end"
63
+ end
64
+
65
+ def tables(stream)
66
+ @connection.tables.sort.each do |tbl|
67
+ next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
68
+ case ignored
69
+ when String; tbl == ignored
70
+ when Regexp; tbl =~ ignored
71
+ else
72
+ raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
73
+ end
74
+ end
75
+ table(tbl, stream)
76
+ end
77
+ end
78
+
79
+ def table(table, stream)
80
+ columns = @connection.columns(table)
81
+ begin
82
+ tbl = StringIO.new
83
+
84
+ # first dump primary key column
85
+ if @connection.respond_to?(:pk_and_sequence_for)
86
+ pk, pk_seq = @connection.pk_and_sequence_for(table)
87
+ elsif @connection.respond_to?(:primary_key)
88
+ pk = @connection.primary_key(table)
89
+ end
90
+
91
+ tbl.print " create_table #{table.inspect}"
92
+ if columns.detect { |c| c.name == pk }
93
+ if pk != 'id'
94
+ tbl.print %Q(, :primary_key => "#{pk}")
95
+ end
96
+ else
97
+ tbl.print ", :id => false"
98
+ end
99
+ tbl.print ", :force => true"
100
+ tbl.puts " do |t|"
101
+
102
+ # then dump all non-primary key columns
103
+ column_specs = columns.map do |column|
104
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
105
+ next if column.name == pk
106
+ spec = {}
107
+ spec[:name] = column.name.inspect
108
+
109
+ # AR has an optimisation which handles zero-scale decimals as integers. This
110
+ # code ensures that the dumper still dumps the column as a decimal.
111
+ spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
112
+ 'decimal'
113
+ else
114
+ column.type.to_s
115
+ end
116
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
117
+ spec[:precision] = column.precision.inspect if !column.precision.nil?
118
+ spec[:scale] = column.scale.inspect if !column.scale.nil?
119
+ spec[:null] = 'false' if !column.null
120
+ spec[:default] = default_string(column.default) if column.has_default?
121
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
122
+ spec
123
+ end.compact
124
+
125
+ # find all migration keys used in this table
126
+ keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map{ |k| k.keys }.flatten
127
+
128
+ # figure out the lengths for each column based on above keys
129
+ lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
130
+
131
+ # the string we're going to sprintf our values against, with standardized column widths
132
+ format_string = lengths.map{ |len| "%-#{len}s" }
133
+
134
+ # find the max length for the 'type' column, which is special
135
+ type_length = column_specs.map{ |column| column[:type].length }.max
136
+
137
+ # add column type definition to our format string
138
+ format_string.unshift " t.%-#{type_length}s "
139
+
140
+ format_string *= ''
141
+
142
+ column_specs.each do |colspec|
143
+ values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
144
+ values.unshift colspec[:type]
145
+ tbl.print((format_string % values).gsub(/,\s*$/, ''))
146
+ tbl.puts
147
+ end
148
+
149
+ tbl.puts " end"
150
+ tbl.puts
151
+
152
+ indexes(table, tbl)
153
+
154
+ tbl.rewind
155
+ stream.print tbl.read
156
+ rescue => e
157
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
158
+ stream.puts "# #{e.message}"
159
+ stream.puts
160
+ end
161
+
162
+ stream
163
+ end
164
+
165
+ def default_string(value)
166
+ case value
167
+ when BigDecimal
168
+ value.to_s
169
+ when Date, DateTime, Time
170
+ "'" + value.to_s(:db) + "'"
171
+ else
172
+ value.inspect
173
+ end
174
+ end
175
+
176
+ def indexes(table, stream)
177
+ if (indexes = @connection.indexes(table)).any?
178
+ add_index_statements = indexes.map do |index|
179
+ statement_parts = [ ('add_index ' + index.table.inspect) ]
180
+ statement_parts << index.columns.inspect
181
+ statement_parts << (':name => ' + index.name.inspect)
182
+ statement_parts << ':unique => true' if index.unique
183
+
184
+ index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
185
+ statement_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
186
+
187
+ ' ' + statement_parts.join(', ')
188
+ end
189
+
190
+ stream.puts add_index_statements.sort.join("\n")
191
+ stream.puts
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,60 @@
1
+ module ActiveRecord #:nodoc:
2
+ # = Active Record Serialization
3
+ module Serialization
4
+ extend ActiveSupport::Concern
5
+ include ActiveModel::Serializers::JSON
6
+
7
+ def serializable_hash(options = nil)
8
+ options = options.try(:clone) || {}
9
+
10
+ options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
11
+ options[:except] |= Array.wrap(self.class.inheritance_column)
12
+
13
+ hash = super(options)
14
+
15
+ serializable_add_includes(options) do |association, records, opts|
16
+ hash[association] = records.is_a?(Enumerable) ?
17
+ records.map { |r| r.serializable_hash(opts) } :
18
+ records.serializable_hash(opts)
19
+ end
20
+
21
+ hash
22
+ end
23
+
24
+ private
25
+ # Add associations specified via the <tt>:includes</tt> option.
26
+ #
27
+ # Expects a block that takes as arguments:
28
+ # +association+ - name of the association
29
+ # +records+ - the association record(s) to be serialized
30
+ # +opts+ - options for the association records
31
+ def serializable_add_includes(options = {})
32
+ return unless include_associations = options.delete(:include)
33
+
34
+ base_only_or_except = { :except => options[:except],
35
+ :only => options[:only] }
36
+
37
+ include_has_options = include_associations.is_a?(Hash)
38
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
39
+
40
+ for association in associations
41
+ records = case self.class.reflect_on_association(association).macro
42
+ when :has_many, :has_and_belongs_to_many
43
+ send(association).to_a
44
+ when :has_one, :belongs_to
45
+ send(association)
46
+ end
47
+
48
+ unless records.nil?
49
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
50
+ opts = options.merge(association_options)
51
+ yield(association, records, opts)
52
+ end
53
+ end
54
+
55
+ options[:include] = include_associations
56
+ end
57
+ end
58
+ end
59
+
60
+ require 'active_record/serializers/xml_serializer'
@@ -0,0 +1,244 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/core_ext/hash/conversions'
3
+
4
+ module ActiveRecord #:nodoc:
5
+ module Serialization
6
+ include ActiveModel::Serializers::Xml
7
+
8
+ # Builds an XML document to represent the model. Some configuration is
9
+ # available through +options+. However more complicated cases should
10
+ # override ActiveRecord::Base#to_xml.
11
+ #
12
+ # By default the generated XML document will include the processing
13
+ # instruction and all the object's attributes. For example:
14
+ #
15
+ # <?xml version="1.0" encoding="UTF-8"?>
16
+ # <topic>
17
+ # <title>The First Topic</title>
18
+ # <author-name>David</author-name>
19
+ # <id type="integer">1</id>
20
+ # <approved type="boolean">false</approved>
21
+ # <replies-count type="integer">0</replies-count>
22
+ # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
23
+ # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
24
+ # <content>Have a nice day</content>
25
+ # <author-email-address>david@loudthinking.com</author-email-address>
26
+ # <parent-id></parent-id>
27
+ # <last-read type="date">2004-04-15</last-read>
28
+ # </topic>
29
+ #
30
+ # This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
31
+ # <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
32
+ # The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
33
+ # +attributes+ method. The default is to dasherize all column names, but you
34
+ # can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
35
+ # to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
36
+ # To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
37
+ #
38
+ # For instance:
39
+ #
40
+ # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
41
+ #
42
+ # <topic>
43
+ # <title>The First Topic</title>
44
+ # <author-name>David</author-name>
45
+ # <approved type="boolean">false</approved>
46
+ # <content>Have a nice day</content>
47
+ # <author-email-address>david@loudthinking.com</author-email-address>
48
+ # <parent-id></parent-id>
49
+ # <last-read type="date">2004-04-15</last-read>
50
+ # </topic>
51
+ #
52
+ # To include first level associations use <tt>:include</tt>:
53
+ #
54
+ # firm.to_xml :include => [ :account, :clients ]
55
+ #
56
+ # <?xml version="1.0" encoding="UTF-8"?>
57
+ # <firm>
58
+ # <id type="integer">1</id>
59
+ # <rating type="integer">1</rating>
60
+ # <name>37signals</name>
61
+ # <clients type="array">
62
+ # <client>
63
+ # <rating type="integer">1</rating>
64
+ # <name>Summit</name>
65
+ # </client>
66
+ # <client>
67
+ # <rating type="integer">1</rating>
68
+ # <name>Microsoft</name>
69
+ # </client>
70
+ # </clients>
71
+ # <account>
72
+ # <id type="integer">1</id>
73
+ # <credit-limit type="integer">50</credit-limit>
74
+ # </account>
75
+ # </firm>
76
+ #
77
+ # Additionally, the record being serialized will be passed to a Proc's second
78
+ # parameter. This allows for ad hoc additions to the resultant document that
79
+ # incorporate the context of the record being serialized. And by leveraging the
80
+ # closure created by a Proc, to_xml can be used to add elements that normally fall
81
+ # outside of the scope of the model -- for example, generating and appending URLs
82
+ # associated with models.
83
+ #
84
+ # proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
85
+ # firm.to_xml :procs => [ proc ]
86
+ #
87
+ # <firm>
88
+ # # ... normal attributes as shown above ...
89
+ # <name-reverse>slangis73</name-reverse>
90
+ # </firm>
91
+ #
92
+ # To include deeper levels of associations pass a hash like this:
93
+ #
94
+ # firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
95
+ # <?xml version="1.0" encoding="UTF-8"?>
96
+ # <firm>
97
+ # <id type="integer">1</id>
98
+ # <rating type="integer">1</rating>
99
+ # <name>37signals</name>
100
+ # <clients type="array">
101
+ # <client>
102
+ # <rating type="integer">1</rating>
103
+ # <name>Summit</name>
104
+ # <address>
105
+ # ...
106
+ # </address>
107
+ # </client>
108
+ # <client>
109
+ # <rating type="integer">1</rating>
110
+ # <name>Microsoft</name>
111
+ # <address>
112
+ # ...
113
+ # </address>
114
+ # </client>
115
+ # </clients>
116
+ # <account>
117
+ # <id type="integer">1</id>
118
+ # <credit-limit type="integer">50</credit-limit>
119
+ # </account>
120
+ # </firm>
121
+ #
122
+ # To include any methods on the model being called use <tt>:methods</tt>:
123
+ #
124
+ # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
125
+ #
126
+ # <firm>
127
+ # # ... normal attributes as shown above ...
128
+ # <calculated-earnings>100000000000000000</calculated-earnings>
129
+ # <real-earnings>5</real-earnings>
130
+ # </firm>
131
+ #
132
+ # To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
133
+ # modified version of the options hash that was given to +to_xml+:
134
+ #
135
+ # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
136
+ # firm.to_xml :procs => [ proc ]
137
+ #
138
+ # <firm>
139
+ # # ... normal attributes as shown above ...
140
+ # <abc>def</abc>
141
+ # </firm>
142
+ #
143
+ # Alternatively, you can yield the builder object as part of the +to_xml+ call:
144
+ #
145
+ # firm.to_xml do |xml|
146
+ # xml.creator do
147
+ # xml.first_name "David"
148
+ # xml.last_name "Heinemeier Hansson"
149
+ # end
150
+ # end
151
+ #
152
+ # <firm>
153
+ # # ... normal attributes as shown above ...
154
+ # <creator>
155
+ # <first_name>David</first_name>
156
+ # <last_name>Heinemeier Hansson</last_name>
157
+ # </creator>
158
+ # </firm>
159
+ #
160
+ # As noted above, you may override +to_xml+ in your ActiveRecord::Base
161
+ # subclasses to have complete control about what's generated. The general
162
+ # form of doing this is:
163
+ #
164
+ # class IHaveMyOwnXML < ActiveRecord::Base
165
+ # def to_xml(options = {})
166
+ # options[:indent] ||= 2
167
+ # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
168
+ # xml.instruct! unless options[:skip_instruct]
169
+ # xml.level_one do
170
+ # xml.tag!(:second_level, 'content')
171
+ # end
172
+ # end
173
+ # end
174
+ def to_xml(options = {}, &block)
175
+ XmlSerializer.new(self, options).serialize(&block)
176
+ end
177
+ end
178
+
179
+ class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
180
+ def initialize(*args)
181
+ super
182
+ options[:except] |= Array.wrap(@serializable.class.inheritance_column)
183
+ end
184
+
185
+ def add_extra_behavior
186
+ add_includes
187
+ end
188
+
189
+ def add_includes
190
+ procs = options.delete(:procs)
191
+ @serializable.send(:serializable_add_includes, options) do |association, records, opts|
192
+ add_associations(association, records, opts)
193
+ end
194
+ options[:procs] = procs
195
+ end
196
+
197
+ # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
198
+ def add_associations(association, records, opts)
199
+ association_name = association.to_s.singularize
200
+ merged_options = options.merge(opts).merge!(:root => association_name, :skip_instruct => true)
201
+
202
+ if records.is_a?(Enumerable)
203
+ tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
204
+ type = options[:skip_types] ? { } : {:type => "array"}
205
+
206
+ if records.empty?
207
+ @builder.tag!(tag, type)
208
+ else
209
+ @builder.tag!(tag, type) do
210
+ records.each do |record|
211
+ if options[:skip_types]
212
+ record_type = {}
213
+ else
214
+ record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
215
+ record_type = {:type => record_class}
216
+ end
217
+
218
+ record.to_xml merged_options.merge(record_type)
219
+ end
220
+ end
221
+ end
222
+ elsif record = @serializable.send(association)
223
+ record.to_xml(merged_options)
224
+ end
225
+ end
226
+
227
+ class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
228
+ def compute_type
229
+ type = @serializable.class.serialized_attributes.has_key?(name) ?
230
+ super : @serializable.class.columns_hash[name].type
231
+
232
+ case type
233
+ when :text
234
+ :string
235
+ when :time
236
+ :datetime
237
+ else
238
+ type
239
+ end
240
+ end
241
+ protected :compute_type
242
+ end
243
+ end
244
+ end