activerecord_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. data/CHANGELOG +5858 -0
  2. data/README +351 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/Rakefile +270 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/performance.rb +162 -0
  7. data/install.rb +30 -0
  8. data/lib/active_record/aggregations.rb +261 -0
  9. data/lib/active_record/association_preload.rb +389 -0
  10. data/lib/active_record/associations/association_collection.rb +475 -0
  11. data/lib/active_record/associations/association_proxy.rb +278 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +76 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +53 -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 +122 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +266 -0
  17. data/lib/active_record/associations/has_one_association.rb +133 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +37 -0
  19. data/lib/active_record/associations.rb +2241 -0
  20. data/lib/active_record/attribute_methods.rb +388 -0
  21. data/lib/active_record/autosave_association.rb +364 -0
  22. data/lib/active_record/base.rb +3171 -0
  23. data/lib/active_record/batches.rb +81 -0
  24. data/lib/active_record/calculations.rb +311 -0
  25. data/lib/active_record/callbacks.rb +360 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +371 -0
  27. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +139 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +289 -0
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +94 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +722 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +434 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +241 -0
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +630 -0
  35. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1113 -0
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  37. data/lib/active_record/connection_adapters/sqlite_adapter.rb +453 -0
  38. data/lib/active_record/dirty.rb +183 -0
  39. data/lib/active_record/dynamic_finder_match.rb +41 -0
  40. data/lib/active_record/dynamic_scope_match.rb +25 -0
  41. data/lib/active_record/fixtures.rb +996 -0
  42. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  43. data/lib/active_record/locale/en.yml +58 -0
  44. data/lib/active_record/locking/optimistic.rb +148 -0
  45. data/lib/active_record/locking/pessimistic.rb +55 -0
  46. data/lib/active_record/migration.rb +566 -0
  47. data/lib/active_record/named_scope.rb +192 -0
  48. data/lib/active_record/nested_attributes.rb +392 -0
  49. data/lib/active_record/observer.rb +197 -0
  50. data/lib/active_record/query_cache.rb +33 -0
  51. data/lib/active_record/reflection.rb +320 -0
  52. data/lib/active_record/schema.rb +51 -0
  53. data/lib/active_record/schema_dumper.rb +182 -0
  54. data/lib/active_record/serialization.rb +101 -0
  55. data/lib/active_record/serializers/json_serializer.rb +91 -0
  56. data/lib/active_record/serializers/xml_serializer.rb +357 -0
  57. data/lib/active_record/session_store.rb +326 -0
  58. data/lib/active_record/test_case.rb +66 -0
  59. data/lib/active_record/timestamp.rb +71 -0
  60. data/lib/active_record/transactions.rb +235 -0
  61. data/lib/active_record/validations.rb +1135 -0
  62. data/lib/active_record/version.rb +9 -0
  63. data/lib/active_record.rb +84 -0
  64. data/lib/activerecord.rb +2 -0
  65. data/test/assets/example.log +1 -0
  66. data/test/assets/flowers.jpg +0 -0
  67. data/test/cases/aaa_create_tables_test.rb +24 -0
  68. data/test/cases/active_schema_test_mysql.rb +100 -0
  69. data/test/cases/active_schema_test_postgresql.rb +24 -0
  70. data/test/cases/adapter_test.rb +145 -0
  71. data/test/cases/aggregations_test.rb +167 -0
  72. data/test/cases/ar_schema_test.rb +32 -0
  73. data/test/cases/associations/belongs_to_associations_test.rb +425 -0
  74. data/test/cases/associations/callbacks_test.rb +161 -0
  75. data/test/cases/associations/cascaded_eager_loading_test.rb +131 -0
  76. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  77. data/test/cases/associations/eager_load_nested_include_test.rb +130 -0
  78. data/test/cases/associations/eager_singularization_test.rb +145 -0
  79. data/test/cases/associations/eager_test.rb +834 -0
  80. data/test/cases/associations/extension_test.rb +62 -0
  81. data/test/cases/associations/habtm_join_table_test.rb +56 -0
  82. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +822 -0
  83. data/test/cases/associations/has_many_associations_test.rb +1134 -0
  84. data/test/cases/associations/has_many_through_associations_test.rb +346 -0
  85. data/test/cases/associations/has_one_associations_test.rb +330 -0
  86. data/test/cases/associations/has_one_through_associations_test.rb +209 -0
  87. data/test/cases/associations/inner_join_association_test.rb +93 -0
  88. data/test/cases/associations/join_model_test.rb +712 -0
  89. data/test/cases/associations_test.rb +262 -0
  90. data/test/cases/attribute_methods_test.rb +305 -0
  91. data/test/cases/autosave_association_test.rb +1142 -0
  92. data/test/cases/base_test.rb +2154 -0
  93. data/test/cases/batches_test.rb +61 -0
  94. data/test/cases/binary_test.rb +30 -0
  95. data/test/cases/calculations_test.rb +348 -0
  96. data/test/cases/callbacks_observers_test.rb +38 -0
  97. data/test/cases/callbacks_test.rb +438 -0
  98. data/test/cases/class_inheritable_attributes_test.rb +32 -0
  99. data/test/cases/column_alias_test.rb +17 -0
  100. data/test/cases/column_definition_test.rb +70 -0
  101. data/test/cases/connection_pool_test.rb +25 -0
  102. data/test/cases/connection_test_firebird.rb +8 -0
  103. data/test/cases/connection_test_mysql.rb +64 -0
  104. data/test/cases/copy_table_test_sqlite.rb +80 -0
  105. data/test/cases/database_statements_test.rb +12 -0
  106. data/test/cases/datatype_test_postgresql.rb +204 -0
  107. data/test/cases/date_time_test.rb +37 -0
  108. data/test/cases/default_test_firebird.rb +16 -0
  109. data/test/cases/defaults_test.rb +111 -0
  110. data/test/cases/deprecated_finder_test.rb +30 -0
  111. data/test/cases/dirty_test.rb +316 -0
  112. data/test/cases/finder_respond_to_test.rb +76 -0
  113. data/test/cases/finder_test.rb +1066 -0
  114. data/test/cases/fixtures_test.rb +656 -0
  115. data/test/cases/helper.rb +68 -0
  116. data/test/cases/i18n_test.rb +46 -0
  117. data/test/cases/inheritance_test.rb +262 -0
  118. data/test/cases/invalid_date_test.rb +24 -0
  119. data/test/cases/json_serialization_test.rb +205 -0
  120. data/test/cases/lifecycle_test.rb +193 -0
  121. data/test/cases/locking_test.rb +304 -0
  122. data/test/cases/method_scoping_test.rb +704 -0
  123. data/test/cases/migration_test.rb +1523 -0
  124. data/test/cases/migration_test_firebird.rb +124 -0
  125. data/test/cases/mixin_test.rb +96 -0
  126. data/test/cases/modules_test.rb +81 -0
  127. data/test/cases/multiple_db_test.rb +85 -0
  128. data/test/cases/named_scope_test.rb +361 -0
  129. data/test/cases/nested_attributes_test.rb +581 -0
  130. data/test/cases/pk_test.rb +119 -0
  131. data/test/cases/pooled_connections_test.rb +103 -0
  132. data/test/cases/query_cache_test.rb +123 -0
  133. data/test/cases/readonly_test.rb +107 -0
  134. data/test/cases/reflection_test.rb +194 -0
  135. data/test/cases/reload_models_test.rb +22 -0
  136. data/test/cases/repair_helper.rb +50 -0
  137. data/test/cases/reserved_word_test_mysql.rb +176 -0
  138. data/test/cases/sanitize_test.rb +25 -0
  139. data/test/cases/schema_authorization_test_postgresql.rb +75 -0
  140. data/test/cases/schema_dumper_test.rb +211 -0
  141. data/test/cases/schema_test_postgresql.rb +178 -0
  142. data/test/cases/serialization_test.rb +47 -0
  143. data/test/cases/synonym_test_oracle.rb +17 -0
  144. data/test/cases/timestamp_test.rb +75 -0
  145. data/test/cases/transactions_test.rb +522 -0
  146. data/test/cases/unconnected_test.rb +32 -0
  147. data/test/cases/validations_i18n_test.rb +955 -0
  148. data/test/cases/validations_test.rb +1640 -0
  149. data/test/cases/xml_serialization_test.rb +240 -0
  150. data/test/config.rb +5 -0
  151. data/test/connections/jdbc_jdbcderby/connection.rb +18 -0
  152. data/test/connections/jdbc_jdbch2/connection.rb +18 -0
  153. data/test/connections/jdbc_jdbchsqldb/connection.rb +18 -0
  154. data/test/connections/jdbc_jdbcmysql/connection.rb +26 -0
  155. data/test/connections/jdbc_jdbcpostgresql/connection.rb +26 -0
  156. data/test/connections/jdbc_jdbcsqlite3/connection.rb +25 -0
  157. data/test/connections/native_db2/connection.rb +25 -0
  158. data/test/connections/native_firebird/connection.rb +26 -0
  159. data/test/connections/native_frontbase/connection.rb +27 -0
  160. data/test/connections/native_mysql/connection.rb +25 -0
  161. data/test/connections/native_openbase/connection.rb +21 -0
  162. data/test/connections/native_oracle/connection.rb +27 -0
  163. data/test/connections/native_postgresql/connection.rb +25 -0
  164. data/test/connections/native_sqlite/connection.rb +25 -0
  165. data/test/connections/native_sqlite3/connection.rb +25 -0
  166. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  167. data/test/connections/native_sybase/connection.rb +23 -0
  168. data/test/fixtures/accounts.yml +29 -0
  169. data/test/fixtures/all/developers.yml +0 -0
  170. data/test/fixtures/all/people.csv +0 -0
  171. data/test/fixtures/all/tasks.yml +0 -0
  172. data/test/fixtures/author_addresses.yml +5 -0
  173. data/test/fixtures/author_favorites.yml +4 -0
  174. data/test/fixtures/authors.yml +9 -0
  175. data/test/fixtures/binaries.yml +132 -0
  176. data/test/fixtures/books.yml +7 -0
  177. data/test/fixtures/categories/special_categories.yml +9 -0
  178. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  179. data/test/fixtures/categories.yml +14 -0
  180. data/test/fixtures/categories_ordered.yml +7 -0
  181. data/test/fixtures/categories_posts.yml +23 -0
  182. data/test/fixtures/categorizations.yml +17 -0
  183. data/test/fixtures/clubs.yml +6 -0
  184. data/test/fixtures/comments.yml +59 -0
  185. data/test/fixtures/companies.yml +56 -0
  186. data/test/fixtures/computers.yml +4 -0
  187. data/test/fixtures/courses.yml +7 -0
  188. data/test/fixtures/customers.yml +26 -0
  189. data/test/fixtures/developers.yml +21 -0
  190. data/test/fixtures/developers_projects.yml +17 -0
  191. data/test/fixtures/edges.yml +6 -0
  192. data/test/fixtures/entrants.yml +14 -0
  193. data/test/fixtures/fixture_database.sqlite3 +0 -0
  194. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  195. data/test/fixtures/fk_test_has_fk.yml +3 -0
  196. data/test/fixtures/fk_test_has_pk.yml +2 -0
  197. data/test/fixtures/funny_jokes.yml +10 -0
  198. data/test/fixtures/items.yml +4 -0
  199. data/test/fixtures/jobs.yml +7 -0
  200. data/test/fixtures/legacy_things.yml +3 -0
  201. data/test/fixtures/mateys.yml +4 -0
  202. data/test/fixtures/member_types.yml +6 -0
  203. data/test/fixtures/members.yml +6 -0
  204. data/test/fixtures/memberships.yml +20 -0
  205. data/test/fixtures/minimalistics.yml +2 -0
  206. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  207. data/test/fixtures/mixins.yml +29 -0
  208. data/test/fixtures/movies.yml +7 -0
  209. data/test/fixtures/naked/csv/accounts.csv +1 -0
  210. data/test/fixtures/naked/yml/accounts.yml +1 -0
  211. data/test/fixtures/naked/yml/companies.yml +1 -0
  212. data/test/fixtures/naked/yml/courses.yml +1 -0
  213. data/test/fixtures/organizations.yml +5 -0
  214. data/test/fixtures/owners.yml +7 -0
  215. data/test/fixtures/parrots.yml +27 -0
  216. data/test/fixtures/parrots_pirates.yml +7 -0
  217. data/test/fixtures/people.yml +15 -0
  218. data/test/fixtures/pets.yml +14 -0
  219. data/test/fixtures/pirates.yml +9 -0
  220. data/test/fixtures/posts.yml +52 -0
  221. data/test/fixtures/price_estimates.yml +7 -0
  222. data/test/fixtures/projects.yml +7 -0
  223. data/test/fixtures/readers.yml +9 -0
  224. data/test/fixtures/references.yml +17 -0
  225. data/test/fixtures/reserved_words/distinct.yml +5 -0
  226. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  227. data/test/fixtures/reserved_words/group.yml +14 -0
  228. data/test/fixtures/reserved_words/select.yml +8 -0
  229. data/test/fixtures/reserved_words/values.yml +7 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/sponsors.yml +9 -0
  232. data/test/fixtures/subscribers.yml +7 -0
  233. data/test/fixtures/subscriptions.yml +12 -0
  234. data/test/fixtures/taggings.yml +28 -0
  235. data/test/fixtures/tags.yml +7 -0
  236. data/test/fixtures/tasks.yml +7 -0
  237. data/test/fixtures/topics.yml +42 -0
  238. data/test/fixtures/toys.yml +4 -0
  239. data/test/fixtures/treasures.yml +10 -0
  240. data/test/fixtures/vertices.yml +4 -0
  241. data/test/fixtures/warehouse-things.yml +3 -0
  242. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  243. data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -0
  244. data/test/migrations/duplicate/1_people_have_last_names.rb +9 -0
  245. data/test/migrations/duplicate/2_we_need_reminders.rb +12 -0
  246. data/test/migrations/duplicate/3_foo.rb +7 -0
  247. data/test/migrations/duplicate/3_innocent_jointable.rb +12 -0
  248. data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
  249. data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
  250. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +12 -0
  251. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +9 -0
  252. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +12 -0
  253. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +9 -0
  254. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
  255. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
  256. data/test/migrations/missing/1000_people_have_middle_names.rb +9 -0
  257. data/test/migrations/missing/1_people_have_last_names.rb +9 -0
  258. data/test/migrations/missing/3_we_need_reminders.rb +12 -0
  259. data/test/migrations/missing/4_innocent_jointable.rb +12 -0
  260. data/test/migrations/valid/1_people_have_last_names.rb +9 -0
  261. data/test/migrations/valid/2_we_need_reminders.rb +12 -0
  262. data/test/migrations/valid/3_innocent_jointable.rb +12 -0
  263. data/test/models/author.rb +146 -0
  264. data/test/models/auto_id.rb +4 -0
  265. data/test/models/binary.rb +2 -0
  266. data/test/models/bird.rb +3 -0
  267. data/test/models/book.rb +4 -0
  268. data/test/models/categorization.rb +5 -0
  269. data/test/models/category.rb +34 -0
  270. data/test/models/citation.rb +6 -0
  271. data/test/models/club.rb +13 -0
  272. data/test/models/column_name.rb +3 -0
  273. data/test/models/comment.rb +29 -0
  274. data/test/models/company.rb +171 -0
  275. data/test/models/company_in_module.rb +61 -0
  276. data/test/models/computer.rb +3 -0
  277. data/test/models/contact.rb +16 -0
  278. data/test/models/contract.rb +5 -0
  279. data/test/models/course.rb +3 -0
  280. data/test/models/customer.rb +73 -0
  281. data/test/models/default.rb +2 -0
  282. data/test/models/developer.rb +101 -0
  283. data/test/models/edge.rb +5 -0
  284. data/test/models/entrant.rb +3 -0
  285. data/test/models/essay.rb +3 -0
  286. data/test/models/event.rb +3 -0
  287. data/test/models/guid.rb +2 -0
  288. data/test/models/item.rb +7 -0
  289. data/test/models/job.rb +5 -0
  290. data/test/models/joke.rb +3 -0
  291. data/test/models/keyboard.rb +3 -0
  292. data/test/models/legacy_thing.rb +3 -0
  293. data/test/models/matey.rb +4 -0
  294. data/test/models/member.rb +12 -0
  295. data/test/models/member_detail.rb +5 -0
  296. data/test/models/member_type.rb +3 -0
  297. data/test/models/membership.rb +9 -0
  298. data/test/models/minimalistic.rb +2 -0
  299. data/test/models/mixed_case_monkey.rb +3 -0
  300. data/test/models/movie.rb +5 -0
  301. data/test/models/order.rb +4 -0
  302. data/test/models/organization.rb +6 -0
  303. data/test/models/owner.rb +5 -0
  304. data/test/models/parrot.rb +16 -0
  305. data/test/models/person.rb +16 -0
  306. data/test/models/pet.rb +5 -0
  307. data/test/models/pirate.rb +70 -0
  308. data/test/models/post.rb +100 -0
  309. data/test/models/price_estimate.rb +3 -0
  310. data/test/models/project.rb +30 -0
  311. data/test/models/reader.rb +4 -0
  312. data/test/models/reference.rb +4 -0
  313. data/test/models/reply.rb +46 -0
  314. data/test/models/ship.rb +10 -0
  315. data/test/models/ship_part.rb +5 -0
  316. data/test/models/sponsor.rb +4 -0
  317. data/test/models/subject.rb +4 -0
  318. data/test/models/subscriber.rb +8 -0
  319. data/test/models/subscription.rb +4 -0
  320. data/test/models/tag.rb +7 -0
  321. data/test/models/tagging.rb +10 -0
  322. data/test/models/task.rb +3 -0
  323. data/test/models/topic.rb +80 -0
  324. data/test/models/toy.rb +6 -0
  325. data/test/models/treasure.rb +8 -0
  326. data/test/models/vertex.rb +9 -0
  327. data/test/models/warehouse_thing.rb +5 -0
  328. data/test/schema/mysql_specific_schema.rb +24 -0
  329. data/test/schema/postgresql_specific_schema.rb +114 -0
  330. data/test/schema/schema.rb +493 -0
  331. data/test/schema/schema2.rb +6 -0
  332. data/test/schema/sqlite_specific_schema.rb +25 -0
  333. metadata +420 -0
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env ruby -KU
2
+
3
+ TIMES = (ENV['N'] || 10000).to_i
4
+
5
+ require 'rubygems'
6
+ gem 'addressable', '~>2.0'
7
+ gem 'faker', '~>0.3.1'
8
+ gem 'rbench', '~>0.2.3'
9
+ require 'addressable/uri'
10
+ require 'faker'
11
+ require 'rbench'
12
+
13
+ __DIR__ = File.dirname(__FILE__)
14
+ $:.unshift "#{__DIR__}/../lib"
15
+ require 'active_record'
16
+
17
+ conn = { :adapter => 'mysql',
18
+ :database => 'activerecord_unittest',
19
+ :username => 'rails', :password => '',
20
+ :encoding => 'utf8' }
21
+
22
+ conn[:socket] = Pathname.glob(%w[
23
+ /opt/local/var/run/mysql5/mysqld.sock
24
+ /tmp/mysqld.sock
25
+ /tmp/mysql.sock
26
+ /var/mysql/mysql.sock
27
+ /var/run/mysqld/mysqld.sock
28
+ ]).find { |path| path.socket? }
29
+
30
+ ActiveRecord::Base.establish_connection(conn)
31
+
32
+ class User < ActiveRecord::Base
33
+ connection.create_table :users, :force => true do |t|
34
+ t.string :name, :email
35
+ t.timestamps
36
+ end
37
+
38
+ has_many :exhibits
39
+ end
40
+
41
+ class Exhibit < ActiveRecord::Base
42
+ connection.create_table :exhibits, :force => true do |t|
43
+ t.belongs_to :user
44
+ t.string :name
45
+ t.text :notes
46
+ t.timestamps
47
+ end
48
+
49
+ belongs_to :user
50
+
51
+ def look; attributes end
52
+ def feel; look; user.name end
53
+
54
+ def self.look(exhibits) exhibits.each { |e| e.look } end
55
+ def self.feel(exhibits) exhibits.each { |e| e.feel } end
56
+ end
57
+
58
+ sqlfile = "#{__DIR__}/performance.sql"
59
+
60
+ if File.exists?(sqlfile)
61
+ mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
62
+ `#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
63
+ else
64
+ puts 'Generating data...'
65
+
66
+ # pre-compute the insert statements and fake data compilation,
67
+ # so the benchmarks below show the actual runtime for the execute
68
+ # method, minus the setup steps
69
+
70
+ # Using the same paragraph for all exhibits because it is very slow
71
+ # to generate unique paragraphs for all exhibits.
72
+ notes = Faker::Lorem.paragraphs.join($/)
73
+ today = Date.today
74
+
75
+ puts 'Inserting 10,000 users and exhibits...'
76
+ 10_000.times do
77
+ user = User.create(
78
+ :created_at => today,
79
+ :name => Faker::Name.name,
80
+ :email => Faker::Internet.email
81
+ )
82
+
83
+ Exhibit.create(
84
+ :created_at => today,
85
+ :name => Faker::Company.name,
86
+ :user => user,
87
+ :notes => notes
88
+ )
89
+ end
90
+
91
+ mysqldump_bin = %w[mysqldump mysqldump5].select { |bin| `which #{bin}`.length > 0 }
92
+ `#{mysqldump_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} exhibits users > #{sqlfile}`
93
+ end
94
+
95
+ RBench.run(TIMES) do
96
+ column :times
97
+ column :ar
98
+
99
+ report 'Model#id', (TIMES * 100).ceil do
100
+ ar_obj = Exhibit.find(1)
101
+
102
+ ar { ar_obj.id }
103
+ end
104
+
105
+ report 'Model.new (instantiation)' do
106
+ ar { Exhibit.new }
107
+ end
108
+
109
+ report 'Model.new (setting attributes)' do
110
+ attrs = { :name => 'sam' }
111
+ ar { Exhibit.new(attrs) }
112
+ end
113
+
114
+ report 'Model.first' do
115
+ ar { Exhibit.first.look }
116
+ end
117
+
118
+ report 'Model.all limit(100)', (TIMES / 10).ceil do
119
+ ar { Exhibit.look Exhibit.all(:limit => 100) }
120
+ end
121
+
122
+ report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do
123
+ ar { Exhibit.feel Exhibit.all(:limit => 100, :include => :user) }
124
+ end
125
+
126
+ report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
127
+ ar { Exhibit.look Exhibit.all(:limit => 10000) }
128
+ end
129
+
130
+ exhibit = {
131
+ :name => Faker::Company.name,
132
+ :notes => Faker::Lorem.paragraphs.join($/),
133
+ :created_at => Date.today
134
+ }
135
+
136
+ report 'Model.create' do
137
+ ar { Exhibit.create(exhibit) }
138
+ end
139
+
140
+ report 'Resource#attributes=' do
141
+ attrs_first = { :name => 'sam' }
142
+ attrs_second = { :name => 'tom' }
143
+ ar { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
144
+ end
145
+
146
+ report 'Resource#update' do
147
+ ar { Exhibit.first.update_attributes(:name => 'bob') }
148
+ end
149
+
150
+ report 'Resource#destroy' do
151
+ ar { Exhibit.first.destroy }
152
+ end
153
+
154
+ report 'Model.transaction' do
155
+ ar { Exhibit.transaction { Exhibit.new } }
156
+ end
157
+
158
+ summary 'Total'
159
+ end
160
+
161
+ ActiveRecord::Migration.drop_table "exhibits"
162
+ ActiveRecord::Migration.drop_table "users"
data/install.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
4
+
5
+ include Config
6
+
7
+ # this was adapted from rdoc's install.rb by ways of Log4r
8
+
9
+ $sitedir = CONFIG["sitelibdir"]
10
+ unless $sitedir
11
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
12
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
13
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
14
+ if !$sitedir
15
+ $sitedir = File.join($libdir, "site_ruby")
16
+ elsif $sitedir !~ Regexp.quote(version)
17
+ $sitedir = File.join($sitedir, version)
18
+ end
19
+ end
20
+
21
+ # the actual gruntwork
22
+ Dir.chdir("lib")
23
+
24
+ Find.find("active_record", "active_record.rb") { |f|
25
+ if f[-3..-1] == ".rb"
26
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
27
+ else
28
+ File::makedirs(File.join($sitedir, *f.split(/\//)))
29
+ end
30
+ }
@@ -0,0 +1,261 @@
1
+ module ActiveRecord
2
+ module Aggregations # :nodoc:
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ def clear_aggregation_cache #:nodoc:
8
+ self.class.reflect_on_all_aggregations.to_a.each do |assoc|
9
+ instance_variable_set "@#{assoc.name}", nil
10
+ end unless self.new_record?
11
+ end
12
+
13
+ # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
14
+ # as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
15
+ # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
16
+ # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
17
+ # and how it can be turned back into attributes (when the entity is saved to the database). Example:
18
+ #
19
+ # class Customer < ActiveRecord::Base
20
+ # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
21
+ # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
22
+ # end
23
+ #
24
+ # The customer class now has the following methods to manipulate the value objects:
25
+ # * <tt>Customer#balance, Customer#balance=(money)</tt>
26
+ # * <tt>Customer#address, Customer#address=(address)</tt>
27
+ #
28
+ # These methods will operate with value objects like the ones described below:
29
+ #
30
+ # class Money
31
+ # include Comparable
32
+ # attr_reader :amount, :currency
33
+ # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
34
+ #
35
+ # def initialize(amount, currency = "USD")
36
+ # @amount, @currency = amount, currency
37
+ # end
38
+ #
39
+ # def exchange_to(other_currency)
40
+ # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
41
+ # Money.new(exchanged_amount, other_currency)
42
+ # end
43
+ #
44
+ # def ==(other_money)
45
+ # amount == other_money.amount && currency == other_money.currency
46
+ # end
47
+ #
48
+ # def <=>(other_money)
49
+ # if currency == other_money.currency
50
+ # amount <=> amount
51
+ # else
52
+ # amount <=> other_money.exchange_to(currency).amount
53
+ # end
54
+ # end
55
+ # end
56
+ #
57
+ # class Address
58
+ # attr_reader :street, :city
59
+ # def initialize(street, city)
60
+ # @street, @city = street, city
61
+ # end
62
+ #
63
+ # def close_to?(other_address)
64
+ # city == other_address.city
65
+ # end
66
+ #
67
+ # def ==(other_address)
68
+ # city == other_address.city && street == other_address.street
69
+ # end
70
+ # end
71
+ #
72
+ # Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
73
+ # composition the same as the attribute's name, it will be the only way to access that attribute. That's the case with our
74
+ # +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
75
+ #
76
+ # customer.balance = Money.new(20) # sets the Money value object and the attribute
77
+ # customer.balance # => Money value object
78
+ # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
79
+ # customer.balance > Money.new(10) # => true
80
+ # customer.balance == Money.new(20) # => true
81
+ # customer.balance < Money.new(5) # => false
82
+ #
83
+ # Value objects can also be composed of multiple attributes, such as the case of Address. The order of the mappings will
84
+ # determine the order of the parameters. Example:
85
+ #
86
+ # customer.address_street = "Hyancintvej"
87
+ # customer.address_city = "Copenhagen"
88
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
89
+ # customer.address = Address.new("May Street", "Chicago")
90
+ # customer.address_street # => "May Street"
91
+ # customer.address_city # => "Chicago"
92
+ #
93
+ # == Writing value objects
94
+ #
95
+ # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
96
+ # $5. Two Money objects both representing $5 should be equal (through methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking
97
+ # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
98
+ # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
99
+ # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
100
+ #
101
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
102
+ # creation. Create a new Money object with the new value instead. This is exemplified by the Money#exchange_to method that
103
+ # returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
104
+ # changed through means other than the writer method.
105
+ #
106
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
107
+ # change it afterwards will result in a ActiveSupport::FrozenObjectError.
108
+ #
109
+ # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
110
+ # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
111
+ #
112
+ # == Custom constructors and converters
113
+ #
114
+ # By default value objects are initialized by calling the <tt>new</tt> constructor of the value class passing each of the
115
+ # mapped attributes, in the order specified by the <tt>:mapping</tt> option, as arguments. If the value class doesn't support
116
+ # this convention then +composed_of+ allows a custom constructor to be specified.
117
+ #
118
+ # When a new value is assigned to the value object the default assumption is that the new value is an instance of the value
119
+ # class. Specifying a custom converter allows the new value to be automatically converted to an instance of value class if
120
+ # necessary.
121
+ #
122
+ # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be aggregated using the
123
+ # NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor for the value class is called +create+ and it
124
+ # expects a CIDR address string as a parameter. New values can be assigned to the value object using either another
125
+ # NetAddr::CIDR object, a string or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to
126
+ # meet these requirements:
127
+ #
128
+ # class NetworkResource < ActiveRecord::Base
129
+ # composed_of :cidr,
130
+ # :class_name => 'NetAddr::CIDR',
131
+ # :mapping => [ %w(network_address network), %w(cidr_range bits) ],
132
+ # :allow_nil => true,
133
+ # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
134
+ # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
135
+ # end
136
+ #
137
+ # # This calls the :constructor
138
+ # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
139
+ #
140
+ # # These assignments will both use the :converter
141
+ # network_resource.cidr = [ '192.168.2.1', 8 ]
142
+ # network_resource.cidr = '192.168.0.1/24'
143
+ #
144
+ # # This assignment won't use the :converter as the value is already an instance of the value class
145
+ # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
146
+ #
147
+ # # Saving and then reloading will use the :constructor on reload
148
+ # network_resource.save
149
+ # network_resource.reload
150
+ #
151
+ # == Finding records by a value object
152
+ #
153
+ # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance
154
+ # of the value object in the conditions hash. The following example finds all customers with +balance_amount+ equal to 20 and
155
+ # +balance_currency+ equal to "USD":
156
+ #
157
+ # Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")})
158
+ #
159
+ module ClassMethods
160
+ # Adds reader and writer methods for manipulating a value object:
161
+ # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
162
+ #
163
+ # Options are:
164
+ # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name can't be inferred
165
+ # from the part id. So <tt>composed_of :address</tt> will by default be linked to the Address class, but
166
+ # if the real class name is CompanyAddress, you'll have to specify it with this option.
167
+ # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value object. Each mapping
168
+ # is represented as an array where the first item is the name of the entity attribute and the second item is the
169
+ # name the attribute in the value object. The order in which mappings are defined determine the order in which
170
+ # attributes are sent to the value class constructor.
171
+ # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
172
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all mapped attributes.
173
+ # This defaults to +false+.
174
+ # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that is called to
175
+ # initialize the value object. The constructor is passed all of the mapped attributes, in the order that they
176
+ # are defined in the <tt>:mapping option</tt>, as arguments and uses them to instantiate a <tt>:class_name</tt> object.
177
+ # The default is <tt>:new</tt>.
178
+ # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> or a Proc that is
179
+ # called when a new value is assigned to the value object. The converter is passed the single value that is used
180
+ # in the assignment and is only called if the new value is not an instance of <tt>:class_name</tt>.
181
+ #
182
+ # Option examples:
183
+ # composed_of :temperature, :mapping => %w(reading celsius)
184
+ # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
185
+ # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
186
+ # composed_of :gps_location
187
+ # composed_of :gps_location, :allow_nil => true
188
+ # composed_of :ip_address,
189
+ # :class_name => 'IPAddr',
190
+ # :mapping => %w(ip to_i),
191
+ # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
192
+ # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
193
+ #
194
+ def composed_of(part_id, options = {}, &block)
195
+ options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
196
+
197
+ name = part_id.id2name
198
+ class_name = options[:class_name] || name.camelize
199
+ mapping = options[:mapping] || [ name, name ]
200
+ mapping = [ mapping ] unless mapping.first.is_a?(Array)
201
+ allow_nil = options[:allow_nil] || false
202
+ constructor = options[:constructor] || :new
203
+ converter = options[:converter] || block
204
+
205
+ ActiveSupport::Deprecation.warn('The conversion block has been deprecated, use the :converter option instead.', caller) if block_given?
206
+
207
+ reader_method(name, class_name, mapping, allow_nil, constructor)
208
+ writer_method(name, class_name, mapping, allow_nil, converter)
209
+
210
+ create_reflection(:composed_of, part_id, options, self)
211
+ end
212
+
213
+ private
214
+ def reader_method(name, class_name, mapping, allow_nil, constructor)
215
+ module_eval do
216
+ define_method(name) do |*args|
217
+ force_reload = args.first || false
218
+ if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
219
+ attrs = mapping.collect {|pair| read_attribute(pair.first)}
220
+ object = case constructor
221
+ when Symbol
222
+ class_name.constantize.send(constructor, *attrs)
223
+ when Proc, Method
224
+ constructor.call(*attrs)
225
+ else
226
+ raise ArgumentError, 'Constructor must be a symbol denoting the constructor method to call or a Proc to be invoked.'
227
+ end
228
+ instance_variable_set("@#{name}", object)
229
+ end
230
+ instance_variable_get("@#{name}")
231
+ end
232
+ end
233
+
234
+ end
235
+
236
+ def writer_method(name, class_name, mapping, allow_nil, converter)
237
+ module_eval do
238
+ define_method("#{name}=") do |part|
239
+ if part.nil? && allow_nil
240
+ mapping.each { |pair| self[pair.first] = nil }
241
+ instance_variable_set("@#{name}", nil)
242
+ else
243
+ unless part.is_a?(class_name.constantize) || converter.nil?
244
+ part = case converter
245
+ when Symbol
246
+ class_name.constantize.send(converter, part)
247
+ when Proc, Method
248
+ converter.call(part)
249
+ else
250
+ raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
251
+ end
252
+ end
253
+ mapping.each { |pair| self[pair.first] = part.send(pair.last) }
254
+ instance_variable_set("@#{name}", part.freeze)
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end