activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,65 +0,0 @@
1
- begin
2
- require 'psych'
3
- rescue LoadError
4
- end
5
-
6
- require 'erb'
7
- require 'yaml'
8
-
9
- module ActiveRecord
10
- class Fixtures
11
- class File
12
- include Enumerable
13
-
14
- ##
15
- # Open a fixture file named +file+. When called with a block, the block
16
- # is called with the filehandle and the filehandle is automatically closed
17
- # when the block finishes.
18
- def self.open(file)
19
- x = new file
20
- block_given? ? yield(x) : x
21
- end
22
-
23
- def initialize(file)
24
- @file = file
25
- @rows = nil
26
- end
27
-
28
- def each(&block)
29
- rows.each(&block)
30
- end
31
-
32
- RESCUE_ERRORS = [ ArgumentError ] # :nodoc:
33
-
34
- private
35
- if defined?(Psych) && defined?(Psych::SyntaxError)
36
- RESCUE_ERRORS << Psych::SyntaxError
37
- end
38
-
39
- def rows
40
- return @rows if @rows
41
-
42
- begin
43
- data = YAML.load(render(IO.read(@file)))
44
- rescue *RESCUE_ERRORS => error
45
- raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
46
- end
47
- @rows = data ? validate(data).to_a : []
48
- end
49
-
50
- def render(content)
51
- ERB.new(content).result
52
- end
53
-
54
- # Validate our unmarshalled data.
55
- def validate(data)
56
- unless Hash === data || YAML::Omap === data
57
- raise Fixture::FormatError, 'fixture is not a hash'
58
- end
59
-
60
- raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
61
- data
62
- end
63
- end
64
- end
65
- end
@@ -1,162 +0,0 @@
1
- module ActiveRecord
2
- # = Active Record Identity Map
3
- #
4
- # Ensures that each object gets loaded only once by keeping every loaded
5
- # object in a map. Looks up objects using the map when referring to them.
6
- #
7
- # More information on Identity Map pattern:
8
- # http://www.martinfowler.com/eaaCatalog/identityMap.html
9
- #
10
- # == Configuration
11
- #
12
- # In order to enable IdentityMap, set <tt>config.active_record.identity_map = true</tt>
13
- # in your <tt>config/application.rb</tt> file.
14
- #
15
- # IdentityMap is disabled by default and still in development (i.e. use it with care).
16
- #
17
- # == Associations
18
- #
19
- # Active Record Identity Map does not track associations yet. For example:
20
- #
21
- # comment = @post.comments.first
22
- # comment.post = nil
23
- # @post.comments.include?(comment) #=> true
24
- #
25
- # Ideally, the example above would return false, removing the comment object from the
26
- # post association when the association is nullified. This may cause side effects, as
27
- # in the situation below, if Identity Map is enabled:
28
- #
29
- # Post.has_many :comments, :dependent => :destroy
30
- #
31
- # comment = @post.comments.first
32
- # comment.post = nil
33
- # comment.save
34
- # Post.destroy(@post.id)
35
- #
36
- # Without using Identity Map, the code above will destroy the @post object leaving
37
- # the comment object intact. However, once we enable Identity Map, the post loaded
38
- # by Post.destroy is exactly the same object as the object @post. As the object @post
39
- # still has the comment object in @post.comments, once Identity Map is enabled, the
40
- # comment object will be accidently removed.
41
- #
42
- # This inconsistency is meant to be fixed in future Rails releases.
43
- #
44
- module IdentityMap
45
-
46
- class << self
47
- def enabled=(flag)
48
- Thread.current[:identity_map_enabled] = flag
49
- end
50
-
51
- def enabled
52
- Thread.current[:identity_map_enabled]
53
- end
54
- alias enabled? enabled
55
-
56
- def repository
57
- Thread.current[:identity_map] ||= Hash.new { |h,k| h[k] = {} }
58
- end
59
-
60
- def use
61
- old, self.enabled = enabled, true
62
-
63
- yield if block_given?
64
- ensure
65
- self.enabled = old
66
- clear
67
- end
68
-
69
- def without
70
- old, self.enabled = enabled, false
71
-
72
- yield if block_given?
73
- ensure
74
- self.enabled = old
75
- end
76
-
77
- def get(klass, primary_key)
78
- record = repository[klass.symbolized_sti_name][primary_key]
79
-
80
- if record.is_a?(klass)
81
- ActiveSupport::Notifications.instrument("identity.active_record",
82
- :line => "From Identity Map (id: #{primary_key})",
83
- :name => "#{klass} Loaded",
84
- :connection_id => object_id)
85
-
86
- record
87
- else
88
- nil
89
- end
90
- end
91
-
92
- def add(record)
93
- repository[record.class.symbolized_sti_name][record.id] = record if contain_all_columns?(record)
94
- end
95
-
96
- def remove(record)
97
- repository[record.class.symbolized_sti_name].delete(record.id)
98
- end
99
-
100
- def remove_by_id(symbolized_sti_name, id)
101
- repository[symbolized_sti_name].delete(id)
102
- end
103
-
104
- def clear
105
- repository.clear
106
- end
107
-
108
- private
109
-
110
- def contain_all_columns?(record)
111
- (record.class.column_names - record.attribute_names).empty?
112
- end
113
- end
114
-
115
- # Reinitialize an Identity Map model object from +coder+.
116
- # +coder+ must contain the attributes necessary for initializing an empty
117
- # model object.
118
- def reinit_with(coder)
119
- @attributes_cache = {}
120
- dirty = @changed_attributes.keys
121
- attributes = self.class.initialize_attributes(coder['attributes'].except(*dirty))
122
- @attributes.update(attributes)
123
- @changed_attributes.update(coder['attributes'].slice(*dirty))
124
- @changed_attributes.delete_if{|k,v| v.eql? @attributes[k]}
125
-
126
- run_callbacks :find
127
-
128
- self
129
- end
130
-
131
- class Middleware
132
- class Body #:nodoc:
133
- def initialize(target, original)
134
- @target = target
135
- @original = original
136
- end
137
-
138
- def each(&block)
139
- @target.each(&block)
140
- end
141
-
142
- def close
143
- @target.close if @target.respond_to?(:close)
144
- ensure
145
- IdentityMap.enabled = @original
146
- IdentityMap.clear
147
- end
148
- end
149
-
150
- def initialize(app)
151
- @app = app
152
- end
153
-
154
- def call(env)
155
- enabled = IdentityMap.enabled
156
- IdentityMap.enabled = true
157
- status, headers, body = @app.call(env)
158
- [status, headers, Body.new(body, enabled)]
159
- end
160
- end
161
- end
162
- end
@@ -1,121 +0,0 @@
1
- require 'active_support/core_ext/class/attribute'
2
-
3
- module ActiveRecord
4
- # = Active Record Observer
5
- #
6
- # Observer classes respond to life cycle callbacks to implement trigger-like
7
- # behavior outside the original class. This is a great way to reduce the
8
- # clutter that normally comes when the model class is burdened with
9
- # functionality that doesn't pertain to the core responsibility of the
10
- # class. Example:
11
- #
12
- # class CommentObserver < ActiveRecord::Observer
13
- # def after_save(comment)
14
- # Notifications.comment("admin@do.com", "New comment was posted", comment).deliver
15
- # end
16
- # end
17
- #
18
- # This Observer sends an email when a Comment#save is finished.
19
- #
20
- # class ContactObserver < ActiveRecord::Observer
21
- # def after_create(contact)
22
- # contact.logger.info('New contact added!')
23
- # end
24
- #
25
- # def after_destroy(contact)
26
- # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
27
- # end
28
- # end
29
- #
30
- # This Observer uses logger to log when specific callbacks are triggered.
31
- #
32
- # == Observing a class that can't be inferred
33
- #
34
- # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
35
- # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
36
- # differently than the class you're interested in observing, you can use the Observer.observe class method which takes
37
- # either the concrete class (Product) or a symbol for that class (:product):
38
- #
39
- # class AuditObserver < ActiveRecord::Observer
40
- # observe :account
41
- #
42
- # def after_update(account)
43
- # AuditTrail.new(account, "UPDATED")
44
- # end
45
- # end
46
- #
47
- # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
48
- #
49
- # class AuditObserver < ActiveRecord::Observer
50
- # observe :account, :balance
51
- #
52
- # def after_update(record)
53
- # AuditTrail.new(record, "UPDATED")
54
- # end
55
- # end
56
- #
57
- # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
58
- #
59
- # == Available callback methods
60
- #
61
- # The observer can implement callback methods for each of the methods described in the Callbacks module.
62
- #
63
- # == Storing Observers in Rails
64
- #
65
- # If you're using Active Record within Rails, observer classes are usually stored in app/models with the
66
- # naming convention of app/models/audit_observer.rb.
67
- #
68
- # == Configuration
69
- #
70
- # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration
71
- # setting in your <tt>config/application.rb</tt> file.
72
- #
73
- # config.active_record.observers = :comment_observer, :signup_observer
74
- #
75
- # Observers will not be invoked unless you define these in your application configuration.
76
- #
77
- # == Loading
78
- #
79
- # Observers register themselves in the model class they observe, since it is the class that
80
- # notifies them of events when they occur. As a side-effect, when an observer is loaded its
81
- # corresponding model class is loaded.
82
- #
83
- # Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
84
- # application initializers. Now observers are loaded after application initializers,
85
- # so observed models can make use of extensions.
86
- #
87
- # If by any chance you are using observed models in the initialization you can still
88
- # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
89
- # singletons and that call instantiates and registers them.
90
- #
91
- class Observer < ActiveModel::Observer
92
-
93
- protected
94
-
95
- def observed_classes
96
- klasses = super
97
- klasses + klasses.map { |klass| klass.descendants }.flatten
98
- end
99
-
100
- def add_observer!(klass)
101
- super
102
- define_callbacks klass
103
- end
104
-
105
- def define_callbacks(klass)
106
- observer = self
107
- observer_name = observer.class.name.underscore.gsub('/', '__')
108
-
109
- ActiveRecord::Callbacks::CALLBACKS.each do |callback|
110
- next unless respond_to?(callback)
111
- callback_meth = :"_notify_#{observer_name}_for_#{callback}"
112
- unless klass.respond_to?(callback_meth)
113
- klass.send(:define_method, callback_meth) do |&block|
114
- observer.update(callback, self, &block)
115
- end
116
- klass.send(callback, callback_meth)
117
- end
118
- end
119
- end
120
- end
121
- end
@@ -1,360 +0,0 @@
1
- require 'action_dispatch/middleware/session/abstract_store'
2
-
3
- module ActiveRecord
4
- # = Active Record Session Store
5
- #
6
- # A session store backed by an Active Record class. A default class is
7
- # provided, but any object duck-typing to an Active Record Session class
8
- # with text +session_id+ and +data+ attributes is sufficient.
9
- #
10
- # The default assumes a +sessions+ tables with columns:
11
- # +id+ (numeric primary key),
12
- # +session_id+ (string, :limit => 255), and
13
- # +data+ (text or longtext; careful if your session data exceeds 65KB).
14
- #
15
- # The +session_id+ column should always be indexed for speedy lookups.
16
- # Session data is marshaled to the +data+ column in Base64 format.
17
- # If the data you write is larger than the column's size limit,
18
- # ActionController::SessionOverflowError will be raised.
19
- #
20
- # You may configure the table name, primary key, and data column.
21
- # For example, at the end of <tt>config/application.rb</tt>:
22
- #
23
- # ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
24
- # ActiveRecord::SessionStore::Session.primary_key = 'session_id'
25
- # ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
26
- #
27
- # Note that setting the primary key to the +session_id+ frees you from
28
- # having a separate +id+ column if you don't want it. However, you must
29
- # set <tt>session.model.id = session.session_id</tt> by hand! A before filter
30
- # on ApplicationController is a good place.
31
- #
32
- # Since the default class is a simple Active Record, you get timestamps
33
- # for free if you add +created_at+ and +updated_at+ datetime columns to
34
- # the +sessions+ table, making periodic session expiration a snap.
35
- #
36
- # You may provide your own session class implementation, whether a
37
- # feature-packed Active Record or a bare-metal high-performance SQL
38
- # store, by setting
39
- #
40
- # ActiveRecord::SessionStore.session_class = MySessionClass
41
- #
42
- # You must implement these methods:
43
- #
44
- # self.find_by_session_id(session_id)
45
- # initialize(hash_of_session_id_and_data, options_hash = {})
46
- # attr_reader :session_id
47
- # attr_accessor :data
48
- # save
49
- # destroy
50
- #
51
- # The example SqlBypass class is a generic SQL session store. You may
52
- # use it as a basis for high-performance database-specific stores.
53
- class SessionStore < ActionDispatch::Session::AbstractStore
54
- module ClassMethods # :nodoc:
55
- def marshal(data)
56
- ::Base64.encode64(Marshal.dump(data)) if data
57
- end
58
-
59
- def unmarshal(data)
60
- Marshal.load(::Base64.decode64(data)) if data
61
- end
62
-
63
- def drop_table!
64
- connection.schema_cache.clear_table_cache!(table_name)
65
- connection.drop_table table_name
66
- end
67
-
68
- def create_table!
69
- connection.schema_cache.clear_table_cache!(table_name)
70
- connection.create_table(table_name) do |t|
71
- t.string session_id_column, :limit => 255
72
- t.text data_column_name
73
- end
74
- connection.add_index table_name, session_id_column, :unique => true
75
- end
76
- end
77
-
78
- # The default Active Record class.
79
- class Session < ActiveRecord::Base
80
- extend ClassMethods
81
-
82
- ##
83
- # :singleton-method:
84
- # Customizable data column name. Defaults to 'data'.
85
- cattr_accessor :data_column_name
86
- self.data_column_name = 'data'
87
-
88
- attr_accessible :session_id, :data, :marshaled_data
89
-
90
- before_save :marshal_data!
91
- before_save :raise_on_session_data_overflow!
92
-
93
- class << self
94
- def data_column_size_limit
95
- @data_column_size_limit ||= columns_hash[data_column_name].limit
96
- end
97
-
98
- # Hook to set up sessid compatibility.
99
- def find_by_session_id(session_id)
100
- setup_sessid_compatibility!
101
- find_by_session_id(session_id)
102
- end
103
-
104
- private
105
- def session_id_column
106
- 'session_id'
107
- end
108
-
109
- # Compatibility with tables using sessid instead of session_id.
110
- def setup_sessid_compatibility!
111
- # Reset column info since it may be stale.
112
- reset_column_information
113
- if columns_hash['sessid']
114
- def self.find_by_session_id(*args)
115
- find_by_sessid(*args)
116
- end
117
-
118
- define_method(:session_id) { sessid }
119
- define_method(:session_id=) { |session_id| self.sessid = session_id }
120
- else
121
- class << self; remove_method :find_by_session_id; end
122
-
123
- def self.find_by_session_id(session_id)
124
- find :first, :conditions => {:session_id=>session_id}
125
- end
126
- end
127
- end
128
- end
129
-
130
- def initialize(attributes = nil, options = {})
131
- @data = nil
132
- super
133
- end
134
-
135
- # Lazy-unmarshal session state.
136
- def data
137
- @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
138
- end
139
-
140
- attr_writer :data
141
-
142
- # Has the session been loaded yet?
143
- def loaded?
144
- @data
145
- end
146
-
147
- private
148
- def marshal_data!
149
- return false unless loaded?
150
- write_attribute(@@data_column_name, self.class.marshal(data))
151
- end
152
-
153
- # Ensures that the data about to be stored in the database is not
154
- # larger than the data storage column. Raises
155
- # ActionController::SessionOverflowError.
156
- def raise_on_session_data_overflow!
157
- return false unless loaded?
158
- limit = self.class.data_column_size_limit
159
- if limit and read_attribute(@@data_column_name).size > limit
160
- raise ActionController::SessionOverflowError
161
- end
162
- end
163
- end
164
-
165
- # A barebones session store which duck-types with the default session
166
- # store but bypasses Active Record and issues SQL directly. This is
167
- # an example session model class meant as a basis for your own classes.
168
- #
169
- # The database connection, table name, and session id and data columns
170
- # are configurable class attributes. Marshaling and unmarshaling
171
- # are implemented as class methods that you may override. By default,
172
- # marshaling data is
173
- #
174
- # ::Base64.encode64(Marshal.dump(data))
175
- #
176
- # and unmarshaling data is
177
- #
178
- # Marshal.load(::Base64.decode64(data))
179
- #
180
- # This marshaling behavior is intended to store the widest range of
181
- # binary session data in a +text+ column. For higher performance,
182
- # store in a +blob+ column instead and forgo the Base64 encoding.
183
- class SqlBypass
184
- extend ClassMethods
185
-
186
- ##
187
- # :singleton-method:
188
- # The table name defaults to 'sessions'.
189
- cattr_accessor :table_name
190
- @@table_name = 'sessions'
191
-
192
- ##
193
- # :singleton-method:
194
- # The session id field defaults to 'session_id'.
195
- cattr_accessor :session_id_column
196
- @@session_id_column = 'session_id'
197
-
198
- ##
199
- # :singleton-method:
200
- # The data field defaults to 'data'.
201
- cattr_accessor :data_column
202
- @@data_column = 'data'
203
-
204
- class << self
205
- alias :data_column_name :data_column
206
-
207
- # Use the ActiveRecord::Base.connection by default.
208
- attr_writer :connection
209
-
210
- # Use the ActiveRecord::Base.connection_pool by default.
211
- attr_writer :connection_pool
212
-
213
- def connection
214
- @connection ||= ActiveRecord::Base.connection
215
- end
216
-
217
- def connection_pool
218
- @connection_pool ||= ActiveRecord::Base.connection_pool
219
- end
220
-
221
- # Look up a session by id and unmarshal its data if found.
222
- def find_by_session_id(session_id)
223
- if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
224
- new(:session_id => session_id, :marshaled_data => record['data'])
225
- end
226
- end
227
- end
228
-
229
- delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self
230
-
231
- attr_reader :session_id, :new_record
232
- alias :new_record? :new_record
233
-
234
- attr_writer :data
235
-
236
- # Look for normal and marshaled data, self.find_by_session_id's way of
237
- # telling us to postpone unmarshaling until the data is requested.
238
- # We need to handle a normal data attribute in case of a new record.
239
- def initialize(attributes)
240
- @session_id = attributes[:session_id]
241
- @data = attributes[:data]
242
- @marshaled_data = attributes[:marshaled_data]
243
- @new_record = @marshaled_data.nil?
244
- end
245
-
246
- # Lazy-unmarshal session state.
247
- def data
248
- unless @data
249
- if @marshaled_data
250
- @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
251
- else
252
- @data = {}
253
- end
254
- end
255
- @data
256
- end
257
-
258
- def loaded?
259
- @data
260
- end
261
-
262
- def save
263
- return false unless loaded?
264
- marshaled_data = self.class.marshal(data)
265
- connect = connection
266
-
267
- if @new_record
268
- @new_record = false
269
- connect.update <<-end_sql, 'Create session'
270
- INSERT INTO #{table_name} (
271
- #{connect.quote_column_name(session_id_column)},
272
- #{connect.quote_column_name(data_column)} )
273
- VALUES (
274
- #{connect.quote(session_id)},
275
- #{connect.quote(marshaled_data)} )
276
- end_sql
277
- else
278
- connect.update <<-end_sql, 'Update session'
279
- UPDATE #{table_name}
280
- SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)}
281
- WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
282
- end_sql
283
- end
284
- end
285
-
286
- def destroy
287
- return if @new_record
288
-
289
- connect = connection
290
- connect.delete <<-end_sql, 'Destroy session'
291
- DELETE FROM #{table_name}
292
- WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
293
- end_sql
294
- end
295
- end
296
-
297
- # The class used for session storage. Defaults to
298
- # ActiveRecord::SessionStore::Session
299
- cattr_accessor :session_class
300
- self.session_class = Session
301
-
302
- SESSION_RECORD_KEY = 'rack.session.record'
303
- ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY
304
-
305
- private
306
- def get_session(env, sid)
307
- Base.silence do
308
- unless sid and session = @@session_class.find_by_session_id(sid)
309
- # If the sid was nil or if there is no pre-existing session under the sid,
310
- # force the generation of a new sid and associate a new session associated with the new sid
311
- sid = generate_sid
312
- session = @@session_class.new(:session_id => sid, :data => {})
313
- end
314
- env[SESSION_RECORD_KEY] = session
315
- [sid, session.data]
316
- end
317
- end
318
-
319
- def set_session(env, sid, session_data, options)
320
- Base.silence do
321
- record = get_session_model(env, sid)
322
- record.data = session_data
323
- return false unless record.save
324
-
325
- session_data = record.data
326
- if session_data && session_data.respond_to?(:each_value)
327
- session_data.each_value do |obj|
328
- obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
329
- end
330
- end
331
- end
332
-
333
- sid
334
- end
335
-
336
- def destroy_session(env, session_id, options)
337
- if sid = current_session_id(env)
338
- Base.silence do
339
- get_session_model(env, sid).destroy
340
- env[SESSION_RECORD_KEY] = nil
341
- end
342
- end
343
-
344
- generate_sid unless options[:drop]
345
- end
346
-
347
- def get_session_model(env, sid)
348
- if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
349
- env[SESSION_RECORD_KEY] = find_session(sid)
350
- else
351
- env[SESSION_RECORD_KEY] ||= find_session(sid)
352
- end
353
- end
354
-
355
- def find_session(id)
356
- @@session_class.find_by_session_id(id) ||
357
- @@session_class.new(:session_id => id, :data => {})
358
- end
359
- end
360
- end