activerecord 1.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 (106) hide show
  1. data/CHANGELOG +581 -0
  2. data/README +361 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/dev-utils/eval_debugger.rb +9 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +88 -0
  9. data/install.rb +60 -0
  10. data/lib/active_record.rb +48 -0
  11. data/lib/active_record/aggregations.rb +165 -0
  12. data/lib/active_record/associations.rb +536 -0
  13. data/lib/active_record/associations/association_collection.rb +70 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -0
  15. data/lib/active_record/associations/has_many_association.rb +104 -0
  16. data/lib/active_record/base.rb +985 -0
  17. data/lib/active_record/callbacks.rb +337 -0
  18. data/lib/active_record/connection_adapters/abstract_adapter.rb +326 -0
  19. data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -0
  20. data/lib/active_record/connection_adapters/postgresql_adapter.rb +177 -0
  21. data/lib/active_record/connection_adapters/sqlite_adapter.rb +107 -0
  22. data/lib/active_record/deprecated_associations.rb +70 -0
  23. data/lib/active_record/fixtures.rb +172 -0
  24. data/lib/active_record/observer.rb +71 -0
  25. data/lib/active_record/reflection.rb +126 -0
  26. data/lib/active_record/support/class_attribute_accessors.rb +43 -0
  27. data/lib/active_record/support/class_inheritable_attributes.rb +37 -0
  28. data/lib/active_record/support/clean_logger.rb +10 -0
  29. data/lib/active_record/support/inflector.rb +70 -0
  30. data/lib/active_record/transactions.rb +102 -0
  31. data/lib/active_record/validations.rb +205 -0
  32. data/lib/active_record/vendor/mysql.rb +1117 -0
  33. data/lib/active_record/vendor/simple.rb +702 -0
  34. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  35. data/lib/active_record/wrappings.rb +59 -0
  36. data/rakefile +122 -0
  37. data/test/abstract_unit.rb +16 -0
  38. data/test/aggregations_test.rb +34 -0
  39. data/test/all.sh +8 -0
  40. data/test/associations_test.rb +477 -0
  41. data/test/base_test.rb +513 -0
  42. data/test/class_inheritable_attributes_test.rb +33 -0
  43. data/test/connections/native_mysql/connection.rb +24 -0
  44. data/test/connections/native_postgresql/connection.rb +24 -0
  45. data/test/connections/native_sqlite/connection.rb +24 -0
  46. data/test/deprecated_associations_test.rb +336 -0
  47. data/test/finder_test.rb +67 -0
  48. data/test/fixtures/accounts/signals37 +3 -0
  49. data/test/fixtures/accounts/unknown +2 -0
  50. data/test/fixtures/auto_id.rb +4 -0
  51. data/test/fixtures/column_name.rb +3 -0
  52. data/test/fixtures/companies/first_client +6 -0
  53. data/test/fixtures/companies/first_firm +4 -0
  54. data/test/fixtures/companies/second_client +6 -0
  55. data/test/fixtures/company.rb +37 -0
  56. data/test/fixtures/company_in_module.rb +33 -0
  57. data/test/fixtures/course.rb +3 -0
  58. data/test/fixtures/courses/java +2 -0
  59. data/test/fixtures/courses/ruby +2 -0
  60. data/test/fixtures/customer.rb +30 -0
  61. data/test/fixtures/customers/david +6 -0
  62. data/test/fixtures/db_definitions/mysql.sql +96 -0
  63. data/test/fixtures/db_definitions/mysql2.sql +4 -0
  64. data/test/fixtures/db_definitions/postgresql.sql +113 -0
  65. data/test/fixtures/db_definitions/postgresql2.sql +4 -0
  66. data/test/fixtures/db_definitions/sqlite.sql +85 -0
  67. data/test/fixtures/db_definitions/sqlite2.sql +4 -0
  68. data/test/fixtures/default.rb +2 -0
  69. data/test/fixtures/developer.rb +8 -0
  70. data/test/fixtures/developers/david +2 -0
  71. data/test/fixtures/developers/jamis +2 -0
  72. data/test/fixtures/developers_projects/david_action_controller +2 -0
  73. data/test/fixtures/developers_projects/david_active_record +2 -0
  74. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  75. data/test/fixtures/entrant.rb +3 -0
  76. data/test/fixtures/entrants/first +3 -0
  77. data/test/fixtures/entrants/second +3 -0
  78. data/test/fixtures/entrants/third +3 -0
  79. data/test/fixtures/fixture_database.sqlite +0 -0
  80. data/test/fixtures/fixture_database_2.sqlite +0 -0
  81. data/test/fixtures/movie.rb +5 -0
  82. data/test/fixtures/movies/first +2 -0
  83. data/test/fixtures/movies/second +2 -0
  84. data/test/fixtures/project.rb +3 -0
  85. data/test/fixtures/projects/action_controller +2 -0
  86. data/test/fixtures/projects/active_record +2 -0
  87. data/test/fixtures/reply.rb +21 -0
  88. data/test/fixtures/subscriber.rb +5 -0
  89. data/test/fixtures/subscribers/first +2 -0
  90. data/test/fixtures/subscribers/second +2 -0
  91. data/test/fixtures/topic.rb +20 -0
  92. data/test/fixtures/topics/first +9 -0
  93. data/test/fixtures/topics/second +8 -0
  94. data/test/fixtures_test.rb +20 -0
  95. data/test/inflector_test.rb +104 -0
  96. data/test/inheritance_test.rb +125 -0
  97. data/test/lifecycle_test.rb +110 -0
  98. data/test/modules_test.rb +21 -0
  99. data/test/multiple_db_test.rb +46 -0
  100. data/test/pk_test.rb +57 -0
  101. data/test/reflection_test.rb +78 -0
  102. data/test/thread_safety_test.rb +33 -0
  103. data/test/transactions_test.rb +83 -0
  104. data/test/unconnected_test.rb +24 -0
  105. data/test/validations_test.rb +126 -0
  106. metadata +166 -0
@@ -0,0 +1,131 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'parsedate'
3
+
4
+ begin
5
+ begin
6
+ # Only include the MySQL driver if one hasn't already been loaded
7
+ require 'mysql' unless self.class.const_defined?(:Mysql)
8
+ rescue LoadError
9
+ # Only use the supplied backup Ruby/MySQL driver if no driver is already in place
10
+ require 'active_record/vendor/mysql'
11
+ end
12
+
13
+ module ActiveRecord
14
+ class Base
15
+ # Establishes a connection to the database that's used by all Active Record objects
16
+ def self.mysql_connection(config) # :nodoc:
17
+ symbolize_strings_in_hash(config)
18
+ host = config[:host]
19
+ port = config[:port]
20
+ socket = config[:socket]
21
+ username = config[:username] || "root"
22
+ password = config[:password]
23
+
24
+ if config.has_key?(:database)
25
+ database = config[:database]
26
+ else
27
+ raise ArgumentError, "No database specified. Missing argument: database."
28
+ end
29
+
30
+ ConnectionAdapters::MysqlAdapter.new(
31
+ Mysql::real_connect(host, username, password, database, port, socket), logger
32
+ )
33
+ end
34
+ end
35
+
36
+ module ConnectionAdapters
37
+ class MysqlAdapter < AbstractAdapter # :nodoc:
38
+ def select_all(sql, name = nil)
39
+ select(sql, name)
40
+ end
41
+
42
+ def select_one(sql, name = nil)
43
+ result = select(sql, name)
44
+ result.nil? ? nil : result.first
45
+ end
46
+
47
+ def columns(table_name, name = nil)
48
+ sql = "SHOW FIELDS FROM #{table_name}"
49
+ result = nil
50
+ log(sql, name, @connection) { |connection| result = connection.query(sql) }
51
+
52
+ columns = []
53
+ result.each { |field| columns << Column.new(field[0], field[4], field[1]) }
54
+ columns
55
+ end
56
+
57
+ def insert(sql, name = nil, pk = nil, id_value = nil)
58
+ execute(sql, name = nil)
59
+ return id_value || @connection.insert_id
60
+ end
61
+
62
+ def execute(sql, name = nil)
63
+ log(sql, name, @connection) { |connection| connection.query(sql) }
64
+ end
65
+
66
+ alias_method :update, :execute
67
+ alias_method :delete, :execute
68
+
69
+ def begin_db_transaction
70
+ begin
71
+ execute "BEGIN"
72
+ rescue Exception
73
+ # Transactions aren't supported
74
+ end
75
+ end
76
+
77
+ def commit_db_transaction
78
+ begin
79
+ execute "COMMIT"
80
+ rescue Exception
81
+ # Transactions aren't supported
82
+ end
83
+ end
84
+
85
+ def rollback_db_transaction
86
+ begin
87
+ execute "ROLLBACK"
88
+ rescue Exception
89
+ # Transactions aren't supported
90
+ end
91
+ end
92
+
93
+ def quote_column_name(name)
94
+ return "`#{name}`"
95
+ end
96
+
97
+ def structure_dump
98
+ select_all("SHOW TABLES").inject("") do |structure, table|
99
+ structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
100
+ end
101
+ end
102
+
103
+ def recreate_database(name)
104
+ drop_database(name)
105
+ create_database(name)
106
+ end
107
+
108
+ def drop_database(name)
109
+ execute "DROP DATABASE IF EXISTS #{name}"
110
+ end
111
+
112
+ def create_database(name)
113
+ execute "CREATE DATABASE #{name}"
114
+ end
115
+
116
+ private
117
+ def select(sql, name = nil)
118
+ result = nil
119
+ log(sql, name, @connection) { |connection| connection.query_with_result = true; result = connection.query(sql) }
120
+ rows = []
121
+ all_fields_initialized = result.fetch_fields.inject({}) { |all_fields, f| all_fields[f.name] = nil; all_fields }
122
+ result.each_hash { |row| rows << all_fields_initialized.dup.update(row) }
123
+ rows
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ rescue LoadError
130
+ # MySQL is not available, so neither should the adapter be
131
+ end
@@ -0,0 +1,177 @@
1
+
2
+ # postgresql_adaptor.rb
3
+ # author: Luke Holden <lholden@cablelan.net>
4
+ # notes: Currently this adaptor does not pass the test_zero_date_fields
5
+ # and test_zero_datetime_fields unit tests in the BasicsTest test
6
+ # group.
7
+ #
8
+ # This is due to the fact that, in postgresql you can not have a
9
+ # totally zero timestamp. Instead null/nil should be used to
10
+ # represent no value.
11
+ #
12
+
13
+ require 'active_record/connection_adapters/abstract_adapter'
14
+ require 'parsedate'
15
+
16
+ begin
17
+ # Only include the PostgreSQL driver if one hasn't already been loaded
18
+ require 'postgres' unless self.class.const_defined?(:PGconn)
19
+
20
+ module ActiveRecord
21
+ class Base
22
+ # Establishes a connection to the database that's used by all Active Record objects
23
+ def self.postgresql_connection(config) # :nodoc:
24
+ symbolize_strings_in_hash(config)
25
+ host = config[:host]
26
+ port = config[:port] || 5432 unless host.nil?
27
+ username = config[:username] || ""
28
+ password = config[:password] || ""
29
+
30
+ if config.has_key?(:database)
31
+ database = config[:database]
32
+ else
33
+ raise ArgumentError, "No database specified. Missing argument: database."
34
+ end
35
+
36
+ ConnectionAdapters::PostgreSQLAdapter.new(
37
+ PGconn.connect(host, port, "", "", database, username, password), logger
38
+ )
39
+ end
40
+ end
41
+
42
+ module ConnectionAdapters
43
+ class PostgreSQLAdapter < AbstractAdapter # :nodoc:
44
+
45
+ def select_all(sql, name = nil)
46
+ select(sql, name)
47
+ end
48
+
49
+ def select_one(sql, name = nil)
50
+ result = select(sql, name)
51
+ result.nil? ? nil : result.first
52
+ end
53
+
54
+ def columns(table_name, name = nil)
55
+ table_structure(table_name).inject([]) do |columns, field|
56
+ columns << Column.new(field[0], field[2], field[1])
57
+ columns
58
+ end
59
+ end
60
+
61
+ def insert(sql, name = nil, pk = nil, id_value = nil)
62
+ execute(sql, name = nil)
63
+ table = sql.split(" ", 4)[2]
64
+ return id_value || last_insert_id(table, pk)
65
+ end
66
+
67
+ def execute(sql, name = nil)
68
+ log(sql, name, @connection) { |connection| connection.query(sql) }
69
+ end
70
+
71
+ alias_method :update, :execute
72
+ alias_method :delete, :execute
73
+
74
+ def begin_db_transaction() execute "BEGIN" end
75
+ def commit_db_transaction() execute "COMMIT" end
76
+ def rollback_db_transaction() execute "ROLLBACK" end
77
+
78
+ def quote_column_name(name)
79
+ return "\"#{name}\""
80
+ end
81
+
82
+ private
83
+ def last_insert_id(table, column = "id")
84
+ sequence_name = "#{table}_#{column || 'id'}_seq"
85
+ @connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i
86
+ end
87
+
88
+ def select(sql, name = nil)
89
+ res = nil
90
+ log(sql, name, @connection) { |connection| res = connection.exec(sql) }
91
+
92
+ results = res.result
93
+ rows = []
94
+ if results.length > 0
95
+ fields = res.fields
96
+ results.each do |row|
97
+ hashed_row = {}
98
+ row.each_index { |cel_index| hashed_row[fields[cel_index]] = row[cel_index] }
99
+ rows << hashed_row
100
+ end
101
+ end
102
+ return rows
103
+ end
104
+
105
+ def split_table_schema(table_name)
106
+ schema_split = table_name.split('.')
107
+ schema_name = "public"
108
+ if schema_split.length > 1
109
+ schema_name = schema_split.first.strip
110
+ table_name = schema_split.last.strip
111
+ end
112
+ return [schema_name, table_name]
113
+ end
114
+
115
+ def table_structure(table_name)
116
+ database_name = @connection.db
117
+ schema_name, table_name = split_table_schema(table_name)
118
+
119
+ # Grab a list of all the default values for the columns.
120
+ sql = "SELECT column_name, column_default, character_maximum_length, data_type "
121
+ sql << " FROM information_schema.columns "
122
+ sql << " WHERE table_catalog = '#{database_name}' "
123
+ sql << " AND table_schema = '#{schema_name}' "
124
+ sql << " AND table_name = '#{table_name}';"
125
+
126
+ column_defaults = nil
127
+ log(sql, nil, @connection) { |connection| column_defaults = connection.query(sql) }
128
+ column_defaults.collect do |row|
129
+ field = row[0]
130
+ type = type_as_string(row[3], row[2])
131
+ default = default_value(row[1])
132
+ length = row[2]
133
+
134
+ [field, type, default, length]
135
+ end
136
+ end
137
+
138
+ def type_as_string(field_type, field_length)
139
+ type = case field_type
140
+ when 'numeric', 'real', 'money' then 'float'
141
+ when 'character varying', 'interval' then 'string'
142
+ when 'timestamp without time zone' then 'datetime'
143
+ else field_type
144
+ end
145
+
146
+ size = field_length.nil? ? "" : "(#{field_length})"
147
+
148
+ return type + size
149
+ end
150
+
151
+ def default_value(value)
152
+ # Boolean types
153
+ return "t" if value =~ /true/i
154
+ return "f" if value =~ /false/i
155
+
156
+ # Char/String type values
157
+ return $1 if value =~ /^'(.*)'::(bpchar|text|character varying)$/
158
+
159
+ # Numeric values
160
+ return value if value =~ /^[0-9]+(\.[0-9]*)?/
161
+
162
+ # Date / Time magic values
163
+ return Time.now.to_s if value =~ /^\('now'::text\)::(date|timestamp)/
164
+
165
+ # Fixed dates / times
166
+ return $1 if value =~ /^'(.+)'::(date|timestamp)/
167
+
168
+ # Anything else is blank, some user type, or some function
169
+ # and we can't know the value of that, so return nil.
170
+ return nil
171
+ end
172
+ end
173
+ end
174
+ end
175
+ rescue LoadError
176
+ # PostgreSQL driver is not availible
177
+ end
@@ -0,0 +1,107 @@
1
+ # sqlite_adapter.rb
2
+ # author: Luke Holden <lholden@cablelan.net>
3
+
4
+ require 'active_record/connection_adapters/abstract_adapter'
5
+
6
+ begin
7
+ require 'sqlite' unless self.class.const_defined?(:SQLite)
8
+
9
+ module ActiveRecord
10
+ class Base
11
+ # Establishes a connection to the database that's used by all Active Record objects
12
+ def self.sqlite_connection(config) # :nodoc:
13
+ symbolize_strings_in_hash(config)
14
+ unless config.has_key?(:dbfile)
15
+ raise ArgumentError, "No database file specified. Missing argument: dbfile"
16
+ end
17
+
18
+ db = SQLite::Database.new(config[:dbfile], 0)
19
+
20
+ db.show_datatypes = "ON" if !defined? SQLite::Version
21
+ db.results_as_hash = true if defined? SQLite::Version
22
+ db.type_translation = false
23
+
24
+ ConnectionAdapters::SQLiteAdapter.new(db, logger)
25
+ end
26
+ end
27
+
28
+ module ConnectionAdapters
29
+ class SQLiteAdapter < AbstractAdapter # :nodoc:
30
+ def select_all(sql, name = nil)
31
+ select(sql, name)
32
+ end
33
+
34
+ def select_one(sql, name = nil)
35
+ result = select(sql, name)
36
+ result.nil? ? nil : result.first
37
+ end
38
+
39
+ def columns(table_name, name = nil)
40
+ table_structure(table_name).inject([]) do |columns, field|
41
+ columns << Column.new(field['name'], field['dflt_value'], field['type'])
42
+ columns
43
+ end
44
+ end
45
+
46
+ def insert(sql, name = nil, pk = nil, id_value = nil)
47
+ execute(sql, name = nil)
48
+ id_value || @connection.send( defined?( SQLite::Version ) ? :last_insert_row_id : :last_insert_rowid )
49
+ end
50
+
51
+ def execute(sql, name = nil)
52
+ log(sql, name, @connection) do |connection|
53
+ if defined?( SQLite::Version )
54
+ case sql
55
+ when "BEGIN" then connection.transaction
56
+ when "COMMIT" then connection.commit
57
+ when "ROLLBACK" then connection.rollback
58
+ else connection.execute(sql)
59
+ end
60
+ else
61
+ connection.execute( sql )
62
+ end
63
+ end
64
+ end
65
+
66
+ alias_method :update, :execute
67
+ alias_method :delete, :execute
68
+
69
+ def begin_db_transaction() execute "BEGIN" end
70
+ def commit_db_transaction() execute "COMMIT" end
71
+ def rollback_db_transaction() execute "ROLLBACK" end
72
+
73
+ def quote_column_name(name)
74
+ return "'#{name}'"
75
+ end
76
+
77
+ private
78
+ def select(sql, name = nil)
79
+ results = nil
80
+ log(sql, name, @connection) { |connection| results = connection.execute(sql) }
81
+
82
+ rows = []
83
+
84
+ results.each do |row|
85
+ hash_only_row = {}
86
+ row.each_key do |key|
87
+ hash_only_row[key.gsub(/\w\./, "")] = row[key] unless key.class == Fixnum
88
+ end
89
+ rows << hash_only_row
90
+ end
91
+
92
+ return rows
93
+ end
94
+
95
+ def table_structure(table_name)
96
+ sql = "PRAGMA table_info(#{table_name});"
97
+ results = nil
98
+ log(sql, nil, @connection) { |connection| results = connection.execute(sql) }
99
+ return results
100
+ end
101
+ end
102
+ end
103
+ end
104
+ rescue LoadError
105
+ retry if require('rubygems') rescue LoadError
106
+ # SQLite driver is not availible
107
+ end
@@ -0,0 +1,70 @@
1
+ module ActiveRecord
2
+ module Associations # :nodoc:
3
+ module ClassMethods
4
+ def deprecated_collection_count_method(collection_name)# :nodoc:
5
+ module_eval <<-"end_eval", __FILE__, __LINE__
6
+ def #{collection_name}_count(force_reload = false)
7
+ #{collection_name}.reload if force_reload
8
+ #{collection_name}.size
9
+ end
10
+ end_eval
11
+ end
12
+
13
+ def deprecated_add_association_relation(association_name)# :nodoc:
14
+ module_eval <<-"end_eval", __FILE__, __LINE__
15
+ def add_#{association_name}(*items)
16
+ #{association_name}.concat(items)
17
+ end
18
+ end_eval
19
+ end
20
+
21
+ def deprecated_remove_association_relation(association_name)# :nodoc:
22
+ module_eval <<-"end_eval", __FILE__, __LINE__
23
+ def remove_#{association_name}(items)
24
+ #{association_name}.delete(items)
25
+ end
26
+ end_eval
27
+ end
28
+
29
+ def deprecated_has_collection_method(collection_name)# :nodoc:
30
+ module_eval <<-"end_eval", __FILE__, __LINE__
31
+ def has_#{collection_name}?(force_reload = false)
32
+ !#{collection_name}(force_reload).empty?
33
+ end
34
+ end_eval
35
+ end
36
+
37
+ def deprecated_find_in_collection_method(collection_name)# :nodoc:
38
+ module_eval <<-"end_eval", __FILE__, __LINE__
39
+ def find_in_#{collection_name}(association_id)
40
+ #{collection_name}.find(association_id)
41
+ end
42
+ end_eval
43
+ end
44
+
45
+ def deprecated_find_all_in_collection_method(collection_name)# :nodoc:
46
+ module_eval <<-"end_eval", __FILE__, __LINE__
47
+ def find_all_in_#{collection_name}(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
48
+ #{collection_name}.find_all(runtime_conditions, orderings, limit, joins)
49
+ end
50
+ end_eval
51
+ end
52
+
53
+ def deprecated_create_method(collection_name)# :nodoc:
54
+ module_eval <<-"end_eval", __FILE__, __LINE__
55
+ def create_in_#{collection_name}(attributes = {})
56
+ #{collection_name}.create(attributes)
57
+ end
58
+ end_eval
59
+ end
60
+
61
+ def deprecated_build_method(collection_name)# :nodoc:
62
+ module_eval <<-"end_eval", __FILE__, __LINE__
63
+ def build_to_#{collection_name}(attributes = {})
64
+ #{collection_name}.build(attributes)
65
+ end
66
+ end_eval
67
+ end
68
+ end
69
+ end
70
+ end