activerecord 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (311) hide show
  1. data/CHANGELOG +4928 -3
  2. data/README +45 -46
  3. data/RUNNING_UNIT_TESTS +8 -11
  4. data/Rakefile +247 -0
  5. data/install.rb +8 -38
  6. data/lib/active_record/aggregations.rb +64 -49
  7. data/lib/active_record/associations/association_collection.rb +217 -47
  8. data/lib/active_record/associations/association_proxy.rb +159 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
  12. data/lib/active_record/associations/has_many_association.rb +145 -75
  13. data/lib/active_record/associations/has_many_through_association.rb +283 -0
  14. data/lib/active_record/associations/has_one_association.rb +96 -0
  15. data/lib/active_record/associations.rb +1537 -304
  16. data/lib/active_record/attribute_methods.rb +328 -0
  17. data/lib/active_record/base.rb +2001 -588
  18. data/lib/active_record/calculations.rb +269 -0
  19. data/lib/active_record/callbacks.rb +169 -165
  20. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  23. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
  31. data/lib/active_record/fixtures.rb +946 -100
  32. data/lib/active_record/locking/optimistic.rb +144 -0
  33. data/lib/active_record/locking/pessimistic.rb +77 -0
  34. data/lib/active_record/migration.rb +417 -0
  35. data/lib/active_record/observer.rb +142 -32
  36. data/lib/active_record/query_cache.rb +23 -0
  37. data/lib/active_record/reflection.rb +163 -70
  38. data/lib/active_record/schema.rb +58 -0
  39. data/lib/active_record/schema_dumper.rb +171 -0
  40. data/lib/active_record/serialization.rb +98 -0
  41. data/lib/active_record/serializers/json_serializer.rb +71 -0
  42. data/lib/active_record/serializers/xml_serializer.rb +315 -0
  43. data/lib/active_record/timestamp.rb +41 -0
  44. data/lib/active_record/transactions.rb +87 -57
  45. data/lib/active_record/validations.rb +909 -122
  46. data/lib/active_record/vendor/db2.rb +362 -0
  47. data/lib/active_record/vendor/mysql.rb +126 -29
  48. data/lib/active_record/version.rb +9 -0
  49. data/lib/active_record.rb +35 -7
  50. data/lib/activerecord.rb +1 -0
  51. data/test/aaa_create_tables_test.rb +72 -0
  52. data/test/abstract_unit.rb +73 -5
  53. data/test/active_schema_test_mysql.rb +43 -0
  54. data/test/adapter_test.rb +105 -0
  55. data/test/adapter_test_sqlserver.rb +95 -0
  56. data/test/aggregations_test.rb +110 -16
  57. data/test/all.sh +2 -2
  58. data/test/ar_schema_test.rb +33 -0
  59. data/test/association_inheritance_reload.rb +14 -0
  60. data/test/associations/ar_joins_test.rb +0 -0
  61. data/test/associations/callbacks_test.rb +147 -0
  62. data/test/associations/cascaded_eager_loading_test.rb +110 -0
  63. data/test/associations/eager_singularization_test.rb +145 -0
  64. data/test/associations/eager_test.rb +442 -0
  65. data/test/associations/extension_test.rb +47 -0
  66. data/test/associations/inner_join_association_test.rb +88 -0
  67. data/test/associations/join_model_test.rb +553 -0
  68. data/test/associations_test.rb +1930 -267
  69. data/test/attribute_methods_test.rb +146 -0
  70. data/test/base_test.rb +1316 -84
  71. data/test/binary_test.rb +32 -0
  72. data/test/calculations_test.rb +251 -0
  73. data/test/callbacks_test.rb +400 -0
  74. data/test/class_inheritable_attributes_test.rb +3 -4
  75. data/test/column_alias_test.rb +17 -0
  76. data/test/connection_test_firebird.rb +8 -0
  77. data/test/connection_test_mysql.rb +30 -0
  78. data/test/connections/native_db2/connection.rb +25 -0
  79. data/test/connections/native_firebird/connection.rb +26 -0
  80. data/test/connections/native_frontbase/connection.rb +27 -0
  81. data/test/connections/native_mysql/connection.rb +21 -18
  82. data/test/connections/native_openbase/connection.rb +21 -0
  83. data/test/connections/native_oracle/connection.rb +27 -0
  84. data/test/connections/native_postgresql/connection.rb +17 -18
  85. data/test/connections/native_sqlite/connection.rb +17 -16
  86. data/test/connections/native_sqlite3/connection.rb +25 -0
  87. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  88. data/test/connections/native_sybase/connection.rb +23 -0
  89. data/test/copy_table_test_sqlite.rb +69 -0
  90. data/test/datatype_test_postgresql.rb +203 -0
  91. data/test/date_time_test.rb +37 -0
  92. data/test/default_test_firebird.rb +16 -0
  93. data/test/defaults_test.rb +67 -0
  94. data/test/deprecated_finder_test.rb +30 -0
  95. data/test/finder_test.rb +607 -32
  96. data/test/fixtures/accounts.yml +28 -0
  97. data/test/fixtures/all/developers.yml +0 -0
  98. data/test/fixtures/all/people.csv +0 -0
  99. data/test/fixtures/all/tasks.yml +0 -0
  100. data/test/fixtures/author.rb +107 -0
  101. data/test/fixtures/author_favorites.yml +4 -0
  102. data/test/fixtures/authors.yml +7 -0
  103. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  104. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  105. data/test/fixtures/bad_fixtures/blank_line +3 -0
  106. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  107. data/test/fixtures/bad_fixtures/missing_value +1 -0
  108. data/test/fixtures/binaries.yml +132 -0
  109. data/test/fixtures/binary.rb +2 -0
  110. data/test/fixtures/book.rb +4 -0
  111. data/test/fixtures/books.yml +7 -0
  112. data/test/fixtures/categories/special_categories.yml +9 -0
  113. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  114. data/test/fixtures/categories.yml +14 -0
  115. data/test/fixtures/categories_ordered.yml +7 -0
  116. data/test/fixtures/categories_posts.yml +23 -0
  117. data/test/fixtures/categorization.rb +5 -0
  118. data/test/fixtures/categorizations.yml +17 -0
  119. data/test/fixtures/category.rb +26 -0
  120. data/test/fixtures/citation.rb +6 -0
  121. data/test/fixtures/comment.rb +23 -0
  122. data/test/fixtures/comments.yml +59 -0
  123. data/test/fixtures/companies.yml +55 -0
  124. data/test/fixtures/company.rb +81 -4
  125. data/test/fixtures/company_in_module.rb +32 -6
  126. data/test/fixtures/computer.rb +4 -0
  127. data/test/fixtures/computers.yml +4 -0
  128. data/test/fixtures/contact.rb +16 -0
  129. data/test/fixtures/courses.yml +7 -0
  130. data/test/fixtures/customer.rb +28 -3
  131. data/test/fixtures/customers.yml +17 -0
  132. data/test/fixtures/db_definitions/db2.drop.sql +33 -0
  133. data/test/fixtures/db_definitions/db2.sql +235 -0
  134. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  135. data/test/fixtures/db_definitions/db22.sql +5 -0
  136. data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
  137. data/test/fixtures/db_definitions/firebird.sql +310 -0
  138. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  139. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  140. data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
  141. data/test/fixtures/db_definitions/frontbase.sql +273 -0
  142. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  143. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  144. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  145. data/test/fixtures/db_definitions/openbase.sql +318 -0
  146. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  148. data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
  149. data/test/fixtures/db_definitions/oracle.sql +330 -0
  150. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  151. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  152. data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
  153. data/test/fixtures/db_definitions/postgresql.sql +217 -38
  154. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/postgresql2.sql +2 -2
  156. data/test/fixtures/db_definitions/schema.rb +354 -0
  157. data/test/fixtures/db_definitions/schema2.rb +11 -0
  158. data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
  159. data/test/fixtures/db_definitions/sqlite.sql +139 -5
  160. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  161. data/test/fixtures/db_definitions/sqlite2.sql +1 -0
  162. data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
  163. data/test/fixtures/db_definitions/sybase.sql +222 -0
  164. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  165. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  166. data/test/fixtures/developer.rb +70 -6
  167. data/test/fixtures/developers.yml +21 -0
  168. data/test/fixtures/developers_projects/david_action_controller +2 -1
  169. data/test/fixtures/developers_projects/david_active_record +2 -1
  170. data/test/fixtures/developers_projects.yml +17 -0
  171. data/test/fixtures/edge.rb +5 -0
  172. data/test/fixtures/edges.yml +6 -0
  173. data/test/fixtures/entrants.yml +14 -0
  174. data/test/fixtures/example.log +1 -0
  175. data/test/fixtures/fk_test_has_fk.yml +3 -0
  176. data/test/fixtures/fk_test_has_pk.yml +2 -0
  177. data/test/fixtures/flowers.jpg +0 -0
  178. data/test/fixtures/funny_jokes.yml +10 -0
  179. data/test/fixtures/item.rb +7 -0
  180. data/test/fixtures/items.yml +4 -0
  181. data/test/fixtures/joke.rb +3 -0
  182. data/test/fixtures/keyboard.rb +3 -0
  183. data/test/fixtures/legacy_thing.rb +3 -0
  184. data/test/fixtures/legacy_things.yml +3 -0
  185. data/test/fixtures/matey.rb +4 -0
  186. data/test/fixtures/mateys.yml +4 -0
  187. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  188. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  189. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  190. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  191. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  192. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  193. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  194. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  195. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  196. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  197. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  198. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  199. data/test/fixtures/minimalistic.rb +2 -0
  200. data/test/fixtures/minimalistics.yml +2 -0
  201. data/test/fixtures/mixed_case_monkey.rb +3 -0
  202. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  203. data/test/fixtures/mixins.yml +29 -0
  204. data/test/fixtures/movies.yml +7 -0
  205. data/test/fixtures/naked/csv/accounts.csv +1 -0
  206. data/test/fixtures/naked/yml/accounts.yml +1 -0
  207. data/test/fixtures/naked/yml/companies.yml +1 -0
  208. data/test/fixtures/naked/yml/courses.yml +1 -0
  209. data/test/fixtures/order.rb +4 -0
  210. data/test/fixtures/parrot.rb +13 -0
  211. data/test/fixtures/parrots.yml +27 -0
  212. data/test/fixtures/parrots_pirates.yml +7 -0
  213. data/test/fixtures/people.yml +3 -0
  214. data/test/fixtures/person.rb +4 -0
  215. data/test/fixtures/pirate.rb +5 -0
  216. data/test/fixtures/pirates.yml +9 -0
  217. data/test/fixtures/post.rb +59 -0
  218. data/test/fixtures/posts.yml +48 -0
  219. data/test/fixtures/project.rb +27 -2
  220. data/test/fixtures/projects.yml +7 -0
  221. data/test/fixtures/reader.rb +4 -0
  222. data/test/fixtures/readers.yml +4 -0
  223. data/test/fixtures/reply.rb +18 -2
  224. data/test/fixtures/reserved_words/distinct.yml +5 -0
  225. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  226. data/test/fixtures/reserved_words/group.yml +14 -0
  227. data/test/fixtures/reserved_words/select.yml +8 -0
  228. data/test/fixtures/reserved_words/values.yml +7 -0
  229. data/test/fixtures/ship.rb +3 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/subject.rb +4 -0
  232. data/test/fixtures/subscriber.rb +4 -3
  233. data/test/fixtures/tag.rb +7 -0
  234. data/test/fixtures/tagging.rb +10 -0
  235. data/test/fixtures/taggings.yml +25 -0
  236. data/test/fixtures/tags.yml +7 -0
  237. data/test/fixtures/task.rb +3 -0
  238. data/test/fixtures/tasks.yml +7 -0
  239. data/test/fixtures/topic.rb +20 -3
  240. data/test/fixtures/topics.yml +22 -0
  241. data/test/fixtures/treasure.rb +4 -0
  242. data/test/fixtures/treasures.yml +10 -0
  243. data/test/fixtures/vertex.rb +9 -0
  244. data/test/fixtures/vertices.yml +4 -0
  245. data/test/fixtures_test.rb +574 -8
  246. data/test/inheritance_test.rb +113 -27
  247. data/test/json_serialization_test.rb +180 -0
  248. data/test/lifecycle_test.rb +56 -29
  249. data/test/locking_test.rb +273 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +933 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_test.rb +95 -0
  254. data/test/modules_test.rb +23 -10
  255. data/test/multiple_db_test.rb +17 -3
  256. data/test/pk_test.rb +59 -15
  257. data/test/query_cache_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +124 -27
  260. data/test/reserved_word_test_mysql.rb +177 -0
  261. data/test/schema_authorization_test_postgresql.rb +75 -0
  262. data/test/schema_dumper_test.rb +131 -0
  263. data/test/schema_test_postgresql.rb +64 -0
  264. data/test/serialization_test.rb +47 -0
  265. data/test/synonym_test_oracle.rb +17 -0
  266. data/test/table_name_test_sqlserver.rb +23 -0
  267. data/test/threaded_connections_test.rb +48 -0
  268. data/test/transactions_test.rb +227 -29
  269. data/test/unconnected_test.rb +14 -6
  270. data/test/validations_test.rb +1293 -32
  271. data/test/xml_serialization_test.rb +202 -0
  272. metadata +347 -143
  273. data/dev-utils/eval_debugger.rb +0 -9
  274. data/examples/associations.rb +0 -87
  275. data/examples/shared_setup.rb +0 -15
  276. data/examples/validation.rb +0 -88
  277. data/lib/active_record/deprecated_associations.rb +0 -70
  278. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  279. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  280. data/lib/active_record/support/clean_logger.rb +0 -10
  281. data/lib/active_record/support/inflector.rb +0 -70
  282. data/lib/active_record/vendor/simple.rb +0 -702
  283. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  284. data/lib/active_record/wrappings.rb +0 -59
  285. data/rakefile +0 -122
  286. data/test/deprecated_associations_test.rb +0 -336
  287. data/test/fixtures/accounts/signals37 +0 -3
  288. data/test/fixtures/accounts/unknown +0 -2
  289. data/test/fixtures/companies/first_client +0 -6
  290. data/test/fixtures/companies/first_firm +0 -4
  291. data/test/fixtures/companies/second_client +0 -6
  292. data/test/fixtures/courses/java +0 -2
  293. data/test/fixtures/courses/ruby +0 -2
  294. data/test/fixtures/customers/david +0 -6
  295. data/test/fixtures/db_definitions/mysql.sql +0 -96
  296. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  297. data/test/fixtures/developers/david +0 -2
  298. data/test/fixtures/developers/jamis +0 -2
  299. data/test/fixtures/entrants/first +0 -3
  300. data/test/fixtures/entrants/second +0 -3
  301. data/test/fixtures/entrants/third +0 -3
  302. data/test/fixtures/fixture_database.sqlite +0 -0
  303. data/test/fixtures/fixture_database_2.sqlite +0 -0
  304. data/test/fixtures/movies/first +0 -2
  305. data/test/fixtures/movies/second +0 -2
  306. data/test/fixtures/projects/action_controller +0 -2
  307. data/test/fixtures/projects/active_record +0 -2
  308. data/test/fixtures/topics/first +0 -9
  309. data/test/fixtures/topics/second +0 -8
  310. data/test/inflector_test.rb +0 -104
  311. data/test/thread_safety_test.rb +0 -33
@@ -0,0 +1,171 @@
1
+ require 'stringio'
2
+ require 'bigdecimal'
3
+
4
+ module ActiveRecord
5
+ # This class is used to dump the database schema for some connection to some
6
+ # output format (i.e., ActiveRecord::Schema).
7
+ class SchemaDumper #:nodoc:
8
+ private_class_method :new
9
+
10
+ # A list of tables which should not be dumped to the schema.
11
+ # Acceptable values are strings as well as regexp.
12
+ # This setting is only used if ActiveRecord::Base.schema_format == :ruby
13
+ cattr_accessor :ignore_tables
14
+ @@ignore_tables = []
15
+
16
+ def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
17
+ new(connection).dump(stream)
18
+ stream
19
+ end
20
+
21
+ def dump(stream)
22
+ header(stream)
23
+ tables(stream)
24
+ trailer(stream)
25
+ stream
26
+ end
27
+
28
+ private
29
+
30
+ def initialize(connection)
31
+ @connection = connection
32
+ @types = @connection.native_database_types
33
+ @info = @connection.select_one("SELECT * FROM schema_info") rescue nil
34
+ end
35
+
36
+ def header(stream)
37
+ define_params = @info ? ":version => #{@info['version']}" : ""
38
+
39
+ stream.puts <<HEADER
40
+ # This file is auto-generated from the current state of the database. Instead of editing this file,
41
+ # please use the migrations feature of ActiveRecord to incrementally modify your database, and
42
+ # then regenerate this schema definition.
43
+ #
44
+ # Note that this schema.rb definition is the authoritative source for your database schema. If you need
45
+ # to create the application database on another system, you should be using db:schema:load, not running
46
+ # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
47
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
48
+ #
49
+ # It's strongly recommended to check this file into your version control system.
50
+
51
+ ActiveRecord::Schema.define(#{define_params}) do
52
+
53
+ HEADER
54
+ end
55
+
56
+ def trailer(stream)
57
+ stream.puts "end"
58
+ end
59
+
60
+ def tables(stream)
61
+ @connection.tables.sort.each do |tbl|
62
+ next if ["schema_info", ignore_tables].flatten.any? do |ignored|
63
+ case ignored
64
+ when String; tbl == ignored
65
+ when Regexp; tbl =~ ignored
66
+ else
67
+ raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
68
+ end
69
+ end
70
+ table(tbl, stream)
71
+ end
72
+ end
73
+
74
+ def table(table, stream)
75
+ columns = @connection.columns(table)
76
+ begin
77
+ tbl = StringIO.new
78
+
79
+ if @connection.respond_to?(:pk_and_sequence_for)
80
+ pk, pk_seq = @connection.pk_and_sequence_for(table)
81
+ end
82
+ pk ||= 'id'
83
+
84
+ tbl.print " create_table #{table.inspect}"
85
+ if columns.detect { |c| c.name == pk }
86
+ if pk != 'id'
87
+ tbl.print %Q(, :primary_key => "#{pk}")
88
+ end
89
+ else
90
+ tbl.print ", :id => false"
91
+ end
92
+ tbl.print ", :force => true"
93
+ tbl.puts " do |t|"
94
+
95
+ column_specs = columns.map do |column|
96
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
97
+ next if column.name == pk
98
+ spec = {}
99
+ spec[:name] = column.name.inspect
100
+ spec[:type] = column.type.to_s
101
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
102
+ spec[:precision] = column.precision.inspect if !column.precision.nil?
103
+ spec[:scale] = column.scale.inspect if !column.scale.nil?
104
+ spec[:null] = 'false' if !column.null
105
+ spec[:default] = default_string(column.default) if !column.default.nil?
106
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
107
+ spec
108
+ end.compact
109
+
110
+ # find all migration keys used in this table
111
+ keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map(&:keys).flatten
112
+
113
+ # figure out the lengths for each column based on above keys
114
+ lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
115
+
116
+ # the string we're going to sprintf our values against, with standardized column widths
117
+ format_string = lengths.map{ |len| "%-#{len}s" }
118
+
119
+ # find the max length for the 'type' column, which is special
120
+ type_length = column_specs.map{ |column| column[:type].length }.max
121
+
122
+ # add column type definition to our format string
123
+ format_string.unshift " t.%-#{type_length}s "
124
+
125
+ format_string *= ''
126
+
127
+ column_specs.each do |colspec|
128
+ values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
129
+ values.unshift colspec[:type]
130
+ tbl.print((format_string % values).gsub(/,\s*$/, ''))
131
+ tbl.puts
132
+ end
133
+
134
+ tbl.puts " end"
135
+ tbl.puts
136
+
137
+ indexes(table, tbl)
138
+
139
+ tbl.rewind
140
+ stream.print tbl.read
141
+ rescue => e
142
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
143
+ stream.puts "# #{e.message}"
144
+ stream.puts
145
+ end
146
+
147
+ stream
148
+ end
149
+
150
+ def default_string(value)
151
+ case value
152
+ when BigDecimal
153
+ value.to_s
154
+ when Date, DateTime, Time
155
+ "'" + value.to_s(:db) + "'"
156
+ else
157
+ value.inspect
158
+ end
159
+ end
160
+
161
+ def indexes(table, stream)
162
+ indexes = @connection.indexes(table)
163
+ indexes.each do |index|
164
+ stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
165
+ stream.print ", :unique => true" if index.unique
166
+ stream.puts
167
+ end
168
+ stream.puts unless indexes.empty?
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,98 @@
1
+ module ActiveRecord #:nodoc:
2
+ module Serialization
3
+ class Serializer #:nodoc:
4
+ attr_reader :options
5
+
6
+ def initialize(record, options = {})
7
+ @record, @options = record, options.dup
8
+ end
9
+
10
+ # To replicate the behavior in ActiveRecord#attributes,
11
+ # :except takes precedence over :only. If :only is not set
12
+ # for a N level model but is set for the N+1 level models,
13
+ # then because :except is set to a default value, the second
14
+ # level model can have both :except and :only set. So if
15
+ # :only is set, always delete :except.
16
+ def serializable_attribute_names
17
+ attribute_names = @record.attribute_names
18
+
19
+ if options[:only]
20
+ options.delete(:except)
21
+ attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
22
+ else
23
+ options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column)
24
+ attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
25
+ end
26
+
27
+ attribute_names
28
+ end
29
+
30
+ def serializable_method_names
31
+ Array(options[:methods]).inject([]) do |method_attributes, name|
32
+ method_attributes << name if @record.respond_to?(name.to_s)
33
+ method_attributes
34
+ end
35
+ end
36
+
37
+ def serializable_names
38
+ serializable_attribute_names + serializable_method_names
39
+ end
40
+
41
+ # Add associations specified via the :includes option.
42
+ # Expects a block that takes as arguments:
43
+ # +association+ - name of the association
44
+ # +records+ - the association record(s) to be serialized
45
+ # +opts+ - options for the association records
46
+ def add_includes(&block)
47
+ if include_associations = options.delete(:include)
48
+ base_only_or_except = { :except => options[:except],
49
+ :only => options[:only] }
50
+
51
+ include_has_options = include_associations.is_a?(Hash)
52
+ associations = include_has_options ? include_associations.keys : Array(include_associations)
53
+
54
+ for association in associations
55
+ records = case @record.class.reflect_on_association(association).macro
56
+ when :has_many, :has_and_belongs_to_many
57
+ @record.send(association).to_a
58
+ when :has_one, :belongs_to
59
+ @record.send(association)
60
+ end
61
+
62
+ unless records.nil?
63
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
64
+ opts = options.merge(association_options)
65
+ yield(association, records, opts)
66
+ end
67
+ end
68
+
69
+ options[:include] = include_associations
70
+ end
71
+ end
72
+
73
+ def serializable_record
74
+ returning(serializable_record = {}) do
75
+ serializable_names.each { |name| serializable_record[name] = @record.send(name) }
76
+ add_includes do |association, records, opts|
77
+ if records.is_a?(Enumerable)
78
+ serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record }
79
+ else
80
+ serializable_record[association] = self.class.new(records, opts).serializable_record
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def serialize
87
+ # overwrite to implement
88
+ end
89
+
90
+ def to_s(&block)
91
+ serialize(&block)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ require 'active_record/serializers/xml_serializer'
98
+ require 'active_record/serializers/json_serializer'
@@ -0,0 +1,71 @@
1
+ module ActiveRecord #:nodoc:
2
+ module Serialization
3
+ # Returns a JSON string representing the model. Some configuration is
4
+ # available through +options+.
5
+ #
6
+ # Without any +options+, the returned JSON string will include all
7
+ # the model's attributes. For example:
8
+ #
9
+ # konata = User.find(1)
10
+ # konata.to_json
11
+ #
12
+ # {"id": 1, "name": "Konata Izumi", "age": 16,
13
+ # "created_at": "2006/08/01", "awesome": true}
14
+ #
15
+ # The :only and :except options can be used to limit the attributes
16
+ # included, and work similar to the #attributes method. For example:
17
+ #
18
+ # konata.to_json(:only => [ :id, :name ])
19
+ #
20
+ # {"id": 1, "name": "Konata Izumi"}
21
+ #
22
+ # konata.to_json(:except => [ :id, :created_at, :age ])
23
+ #
24
+ # {"name": "Konata Izumi", "awesome": true}
25
+ #
26
+ # To include any methods on the model, use :methods.
27
+ #
28
+ # konata.to_json(:methods => :permalink)
29
+ #
30
+ # {"id": 1, "name": "Konata Izumi", "age": 16,
31
+ # "created_at": "2006/08/01", "awesome": true,
32
+ # "permalink": "1-konata-izumi"}
33
+ #
34
+ # To include associations, use :include.
35
+ #
36
+ # konata.to_json(:include => :posts)
37
+ #
38
+ # {"id": 1, "name": "Konata Izumi", "age": 16,
39
+ # "created_at": "2006/08/01", "awesome": true,
40
+ # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
41
+ # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
42
+ #
43
+ # 2nd level and higher order associations work as well:
44
+ #
45
+ # konata.to_json(:include => { :posts => {
46
+ # :include => { :comments => {
47
+ # :only => :body } },
48
+ # :only => :title } })
49
+ #
50
+ # {"id": 1, "name": "Konata Izumi", "age": 16,
51
+ # "created_at": "2006/08/01", "awesome": true,
52
+ # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
53
+ # "title": "Welcome to the weblog"},
54
+ # {"comments": [{"body": "Don't think too hard"}],
55
+ # "title": "So I was thinking"}]}
56
+ def to_json(options = {})
57
+ JsonSerializer.new(self, options).to_s
58
+ end
59
+
60
+ def from_json(json)
61
+ self.attributes = ActiveSupport::JSON.decode(json)
62
+ self
63
+ end
64
+
65
+ class JsonSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
66
+ def serialize
67
+ serializable_record.to_json
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,315 @@
1
+ module ActiveRecord #:nodoc:
2
+ module Serialization
3
+ # Builds an XML document to represent the model. Some configuration is
4
+ # available through +options+, however more complicated cases should
5
+ # override ActiveRecord's to_xml.
6
+ #
7
+ # By default the generated XML document will include the processing
8
+ # instruction and all object's attributes. For example:
9
+ #
10
+ # <?xml version="1.0" encoding="UTF-8"?>
11
+ # <topic>
12
+ # <title>The First Topic</title>
13
+ # <author-name>David</author-name>
14
+ # <id type="integer">1</id>
15
+ # <approved type="boolean">false</approved>
16
+ # <replies-count type="integer">0</replies-count>
17
+ # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
18
+ # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
19
+ # <content>Have a nice day</content>
20
+ # <author-email-address>david@loudthinking.com</author-email-address>
21
+ # <parent-id></parent-id>
22
+ # <last-read type="date">2004-04-15</last-read>
23
+ # </topic>
24
+ #
25
+ # This behavior can be controlled with :only, :except,
26
+ # :skip_instruct, :skip_types and :dasherize. The :only and
27
+ # :except options are the same as for the #attributes method.
28
+ # The default is to dasherize all column names, to disable this,
29
+ # set :dasherize to false. To not have the column type included
30
+ # in the XML output, set :skip_types to true.
31
+ #
32
+ # For instance:
33
+ #
34
+ # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
35
+ #
36
+ # <topic>
37
+ # <title>The First Topic</title>
38
+ # <author-name>David</author-name>
39
+ # <approved type="boolean">false</approved>
40
+ # <content>Have a nice day</content>
41
+ # <author-email-address>david@loudthinking.com</author-email-address>
42
+ # <parent-id></parent-id>
43
+ # <last-read type="date">2004-04-15</last-read>
44
+ # </topic>
45
+ #
46
+ # To include first level associations use :include
47
+ #
48
+ # firm.to_xml :include => [ :account, :clients ]
49
+ #
50
+ # <?xml version="1.0" encoding="UTF-8"?>
51
+ # <firm>
52
+ # <id type="integer">1</id>
53
+ # <rating type="integer">1</rating>
54
+ # <name>37signals</name>
55
+ # <clients type="array">
56
+ # <client>
57
+ # <rating type="integer">1</rating>
58
+ # <name>Summit</name>
59
+ # </client>
60
+ # <client>
61
+ # <rating type="integer">1</rating>
62
+ # <name>Microsoft</name>
63
+ # </client>
64
+ # </clients>
65
+ # <account>
66
+ # <id type="integer">1</id>
67
+ # <credit-limit type="integer">50</credit-limit>
68
+ # </account>
69
+ # </firm>
70
+ #
71
+ # To include any methods on the object(s) being called use :methods
72
+ #
73
+ # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
74
+ #
75
+ # <firm>
76
+ # # ... normal attributes as shown above ...
77
+ # <calculated-earnings>100000000000000000</calculated-earnings>
78
+ # <real-earnings>5</real-earnings>
79
+ # </firm>
80
+ #
81
+ # To call any Proc's on the object(s) use :procs. The Proc's
82
+ # are passed a modified version of the options hash that was
83
+ # given to #to_xml.
84
+ #
85
+ # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
86
+ # firm.to_xml :procs => [ proc ]
87
+ #
88
+ # <firm>
89
+ # # ... normal attributes as shown above ...
90
+ # <abc>def</abc>
91
+ # </firm>
92
+ #
93
+ # Alternatively, you can also just yield the builder object as part of the to_xml call:
94
+ #
95
+ # firm.to_xml do |xml|
96
+ # xml.creator do
97
+ # xml.first_name "David"
98
+ # xml.last_name "Heinemeier Hansson"
99
+ # end
100
+ # end
101
+ #
102
+ # <firm>
103
+ # # ... normal attributes as shown above ...
104
+ # <creator>
105
+ # <first_name>David</first_name>
106
+ # <last_name>Heinemeier Hansson</last_name>
107
+ # </creator>
108
+ # </firm>
109
+ #
110
+ # You can override the to_xml method in your ActiveRecord::Base
111
+ # subclasses if you need to. The general form of doing this is
112
+ #
113
+ # class IHaveMyOwnXML < ActiveRecord::Base
114
+ # def to_xml(options = {})
115
+ # options[:indent] ||= 2
116
+ # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
117
+ # xml.instruct! unless options[:skip_instruct]
118
+ # xml.level_one do
119
+ # xml.tag!(:second_level, 'content')
120
+ # end
121
+ # end
122
+ # end
123
+ def to_xml(options = {}, &block)
124
+ serializer = XmlSerializer.new(self, options)
125
+ block_given? ? serializer.to_s(&block) : serializer.to_s
126
+ end
127
+
128
+ def from_xml(xml)
129
+ self.attributes = Hash.from_xml(xml).values.first
130
+ self
131
+ end
132
+ end
133
+
134
+ class XmlSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
135
+ def builder
136
+ @builder ||= begin
137
+ options[:indent] ||= 2
138
+ builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
139
+
140
+ unless options[:skip_instruct]
141
+ builder.instruct!
142
+ options[:skip_instruct] = true
143
+ end
144
+
145
+ builder
146
+ end
147
+ end
148
+
149
+ def root
150
+ root = (options[:root] || @record.class.to_s.underscore).to_s
151
+ dasherize? ? root.dasherize : root
152
+ end
153
+
154
+ def dasherize?
155
+ !options.has_key?(:dasherize) || options[:dasherize]
156
+ end
157
+
158
+
159
+ # To replicate the behavior in ActiveRecord#attributes,
160
+ # :except takes precedence over :only. If :only is not set
161
+ # for a N level model but is set for the N+1 level models,
162
+ # then because :except is set to a default value, the second
163
+ # level model can have both :except and :only set. So if
164
+ # :only is set, always delete :except.
165
+ def serializable_attributes
166
+ serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
167
+ end
168
+
169
+ def serializable_method_attributes
170
+ Array(options[:methods]).inject([]) do |method_attributes, name|
171
+ method_attributes << MethodAttribute.new(name.to_s, @record) if @record.respond_to?(name.to_s)
172
+ method_attributes
173
+ end
174
+ end
175
+
176
+ def add_attributes
177
+ (serializable_attributes + serializable_method_attributes).each do |attribute|
178
+ add_tag(attribute)
179
+ end
180
+ end
181
+
182
+ def add_procs
183
+ if procs = options.delete(:procs)
184
+ [ *procs ].each do |proc|
185
+ proc.call(options)
186
+ end
187
+ end
188
+ end
189
+
190
+ def add_tag(attribute)
191
+ builder.tag!(
192
+ dasherize? ? attribute.name.dasherize : attribute.name,
193
+ attribute.value.to_s,
194
+ attribute.decorations(!options[:skip_types])
195
+ )
196
+ end
197
+
198
+ def add_associations(association, records, opts)
199
+ if records.is_a?(Enumerable)
200
+ tag = association.to_s
201
+ tag = tag.dasherize if dasherize?
202
+ if records.empty?
203
+ builder.tag!(tag, :type => :array)
204
+ else
205
+ builder.tag!(tag, :type => :array) do
206
+ association_name = association.to_s.singularize
207
+ records.each do |record|
208
+ record.to_xml opts.merge(
209
+ :root => association_name,
210
+ :type => (record.class.to_s.underscore == association_name ? nil : record.class.name)
211
+ )
212
+ end
213
+ end
214
+ end
215
+ else
216
+ if record = @record.send(association)
217
+ record.to_xml(opts.merge(:root => association))
218
+ end
219
+ end
220
+ end
221
+
222
+ def serialize
223
+ args = [root]
224
+ if options[:namespace]
225
+ args << {:xmlns=>options[:namespace]}
226
+ end
227
+
228
+ if options[:type]
229
+ args << {:type=>options[:type]}
230
+ end
231
+
232
+ builder.tag!(*args) do
233
+ add_attributes
234
+ procs = options.delete(:procs)
235
+ add_includes { |association, records, opts| add_associations(association, records, opts) }
236
+ options[:procs] = procs
237
+ add_procs
238
+ yield builder if block_given?
239
+ end
240
+ end
241
+
242
+ class Attribute #:nodoc:
243
+ attr_reader :name, :value, :type
244
+
245
+ def initialize(name, record)
246
+ @name, @record = name, record
247
+
248
+ @type = compute_type
249
+ @value = compute_value
250
+ end
251
+
252
+ # There is a significant speed improvement if the value
253
+ # does not need to be escaped, as #tag! escapes all values
254
+ # to ensure that valid XML is generated. For known binary
255
+ # values, it is at least an order of magnitude faster to
256
+ # Base64 encode binary values and directly put them in the
257
+ # output XML than to pass the original value or the Base64
258
+ # encoded value to the #tag! method. It definitely makes
259
+ # no sense to Base64 encode the value and then give it to
260
+ # #tag!, since that just adds additional overhead.
261
+ def needs_encoding?
262
+ ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
263
+ end
264
+
265
+ def decorations(include_types = true)
266
+ decorations = {}
267
+
268
+ if type == :binary
269
+ decorations[:encoding] = 'base64'
270
+ end
271
+
272
+ if include_types && type != :string
273
+ decorations[:type] = type
274
+ end
275
+
276
+ if value.nil?
277
+ decorations[:nil] = true
278
+ end
279
+
280
+ decorations
281
+ end
282
+
283
+ protected
284
+ def compute_type
285
+ type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
286
+
287
+ case type
288
+ when :text
289
+ :string
290
+ when :time
291
+ :datetime
292
+ else
293
+ type
294
+ end
295
+ end
296
+
297
+ def compute_value
298
+ value = @record.send(name)
299
+
300
+ if formatter = Hash::XML_FORMATTING[type.to_s]
301
+ value ? formatter.call(value) : nil
302
+ else
303
+ value
304
+ end
305
+ end
306
+ end
307
+
308
+ class MethodAttribute < Attribute #:nodoc:
309
+ protected
310
+ def compute_type
311
+ Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveRecord
2
+ # Active Record automatically timestamps create and update operations if the table has fields
3
+ # named created_at/created_on or updated_at/updated_on.
4
+ #
5
+ # Timestamping can be turned off by setting
6
+ # <tt>ActiveRecord::Base.record_timestamps = false</tt>
7
+ #
8
+ # Timestamps are in the local timezone by default but can use UTC by setting
9
+ # <tt>ActiveRecord::Base.default_timezone = :utc</tt>
10
+ module Timestamp
11
+ def self.included(base) #:nodoc:
12
+ base.alias_method_chain :create, :timestamps
13
+ base.alias_method_chain :update, :timestamps
14
+
15
+ base.class_inheritable_accessor :record_timestamps, :instance_writer => false
16
+ base.record_timestamps = true
17
+ end
18
+
19
+ private
20
+ def create_with_timestamps #:nodoc:
21
+ if record_timestamps
22
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
23
+ write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
24
+ write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
25
+
26
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
27
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
28
+ end
29
+ create_without_timestamps
30
+ end
31
+
32
+ def update_with_timestamps #:nodoc:
33
+ if record_timestamps
34
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
35
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
36
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
37
+ end
38
+ update_without_timestamps
39
+ end
40
+ end
41
+ end