square-activerecord 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6140 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +179 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +124 -0
- data/lib/active_record/aggregations.rb +277 -0
- data/lib/active_record/association_preload.rb +430 -0
- data/lib/active_record/associations.rb +2307 -0
- data/lib/active_record/associations/association_collection.rb +572 -0
- data/lib/active_record/associations/association_proxy.rb +299 -0
- data/lib/active_record/associations/belongs_to_association.rb +91 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
- data/lib/active_record/associations/has_many_association.rb +128 -0
- data/lib/active_record/associations/has_many_through_association.rb +115 -0
- data/lib/active_record/associations/has_one_association.rb +143 -0
- data/lib/active_record/associations/has_one_through_association.rb +40 -0
- data/lib/active_record/associations/through_association_scope.rb +154 -0
- data/lib/active_record/attribute_methods.rb +60 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +30 -0
- data/lib/active_record/attribute_methods/dirty.rb +95 -0
- data/lib/active_record/attribute_methods/primary_key.rb +56 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +145 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
- data/lib/active_record/attribute_methods/write.rb +43 -0
- data/lib/active_record/autosave_association.rb +369 -0
- data/lib/active_record/base.rb +1904 -0
- data/lib/active_record/callbacks.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +333 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +539 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
- data/lib/active_record/counter_cache.rb +115 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +172 -0
- data/lib/active_record/fixtures.rb +1006 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +172 -0
- data/lib/active_record/locking/pessimistic.rb +55 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +617 -0
- data/lib/active_record/named_scope.rb +138 -0
- data/lib/active_record/nested_attributes.rb +419 -0
- data/lib/active_record/observer.rb +125 -0
- data/lib/active_record/persistence.rb +290 -0
- data/lib/active_record/query_cache.rb +36 -0
- data/lib/active_record/railtie.rb +91 -0
- data/lib/active_record/railties/controller_runtime.rb +38 -0
- data/lib/active_record/railties/databases.rake +512 -0
- data/lib/active_record/reflection.rb +411 -0
- data/lib/active_record/relation.rb +394 -0
- data/lib/active_record/relation/batches.rb +89 -0
- data/lib/active_record/relation/calculations.rb +295 -0
- data/lib/active_record/relation/finder_methods.rb +363 -0
- data/lib/active_record/relation/predicate_builder.rb +48 -0
- data/lib/active_record/relation/query_methods.rb +303 -0
- data/lib/active_record/relation/spawn_methods.rb +132 -0
- data/lib/active_record/schema.rb +59 -0
- data/lib/active_record/schema_dumper.rb +195 -0
- data/lib/active_record/serialization.rb +60 -0
- data/lib/active_record/serializers/xml_serializer.rb +244 -0
- data/lib/active_record/session_store.rb +340 -0
- data/lib/active_record/test_case.rb +67 -0
- data/lib/active_record/timestamp.rb +88 -0
- data/lib/active_record/transactions.rb +359 -0
- data/lib/active_record/validations.rb +84 -0
- data/lib/active_record/validations/associated.rb +48 -0
- data/lib/active_record/validations/uniqueness.rb +190 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +19 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
- 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
|