activerecord 1.5.1 → 1.6.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.

data/CHANGELOG CHANGED
@@ -1,3 +1,37 @@
1
+ *1.6.0* (January 25th, 2005)
2
+
3
+ * Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once.
4
+
5
+ * Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336
6
+
7
+ * Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once.
8
+
9
+ * Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example
10
+
11
+ people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
12
+ Person.update(people.keys, people.values)
13
+
14
+ * Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 [Scott Baron]
15
+
16
+ * Added that update_all calls sanitize_sql on its updates argument, so stuff like MyRecord.update_all(['time = ?', Time.now]) works #519 [notahat]
17
+
18
+ * Fixed that the dynamic finders didn't treat nil as a "IS NULL" but rather "= NULL" case #515 [Demetrius]
19
+
20
+ * Added bind-named arrays for interpolating a group of ids or strings in conditions #528 [bitsweat]
21
+
22
+ * Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 [Eric Anderson]
23
+
24
+ * Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 [Eric Anderson]
25
+
26
+ * Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
27
+
28
+ * Fixed that association proxies would fail === tests like PremiumSubscription === @account.subscription
29
+
30
+ * Fixed that column aliases didn't work as expected with the new MySql411 driver #507 [Demetrius]
31
+
32
+ * Fixed that find_all would produce invalid sql when called sequentialy #490 [Scott Baron]
33
+
34
+
1
35
  *1.5.1* (January 18th, 2005)
2
36
 
3
37
  * Fixed that the belongs_to and has_one proxy would fail a test like 'if project.manager' -- this unfortunately also means that you can't call methods like project.manager.build unless there already is a manager on the project #492 [Tim Bates]
data/install.rb CHANGED
@@ -18,7 +18,7 @@ unless $sitedir
18
18
  end
19
19
  end
20
20
 
21
- makedirs = %w{ active_record/associations active_record/connection_adapters active_record/support active_record/vendor active_record/acts }
21
+ makedirs = %w{ active_record/associations active_record/connection_adapters active_record/support active_record/vendor active_record/acts active_record/support/core_ext active_record/support/core_ext/hash active_record/support/core_ext/numeric active_record/support/core_ext/string }
22
22
  makedirs.each {|f| File::makedirs(File.join($sitedir, *f.split(/\//)))}
23
23
 
24
24
  # deprecated files that should be removed
@@ -50,8 +50,18 @@ files = %w-
50
50
  active_record/support/class_attribute_accessors.rb
51
51
  active_record/support/class_inheritable_attributes.rb
52
52
  active_record/support/clean_logger.rb
53
+ active_record/support/core_ext/hash/keys.rb
54
+ active_record/support/core_ext/hash.rb
55
+ active_record/support/core_ext/object_and_class.rb
56
+ active_record/support/core_ext/numeric/bytes.rb
57
+ active_record/support/core_ext/numeric/time.rb
58
+ active_record/support/core_ext/numeric.rb
59
+ active_record/support/core_ext/string/inflections.rb
60
+ active_record/support/core_ext/string.rb
61
+ active_record/support/core_ext.rb
53
62
  active_record/support/inflector.rb
54
63
  active_record/support/misc.rb
64
+ active_record/support/module_attribute_accessors.rb
55
65
  active_record/timestamp.rb
56
66
  active_record/transactions.rb
57
67
  active_record/validations.rb
@@ -35,7 +35,6 @@ module ActiveRecord
35
35
  # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
36
36
  # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
37
37
  # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
38
- # <tt>Project#project_manager.build, Project#project_manager.create</tt>
39
38
  # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
40
39
  # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt>
41
40
  # <tt>Project#milestones.build, Project#milestones.create</tt>
@@ -249,9 +248,11 @@ module ActiveRecord
249
248
  # and saves the associate object.
250
249
  # * <tt>association.nil?</tt> - returns true if there is no associated object.
251
250
  # * <tt>association.build(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
252
- # with +attributes+ and linked to this object through a foreign key but has not yet been saved.
251
+ # with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if
252
+ # an association already exists. It will NOT work if the association is nil.
253
253
  # * <tt>association.create(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
254
254
  # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
255
+ # Note: This ONLY works if an association already exists. It will NOT work if the association is nil.
255
256
  #
256
257
  # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
257
258
  # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find_first "account_id = #{id}"</tt>)
@@ -53,9 +53,13 @@ module ActiveRecord
53
53
 
54
54
  def create(attributes = {})
55
55
  # Can't use Base.create since the foreign key may be a protected attribute.
56
- record = build(attributes)
57
- record.save unless @owner.new_record?
58
- record
56
+ if attributes.is_a?(Array)
57
+ attributes.collect { |attr| create(attr) }
58
+ else
59
+ record = build(attributes)
60
+ record.save unless @owner.new_record?
61
+ record
62
+ end
59
63
  end
60
64
 
61
65
  # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
@@ -81,8 +81,8 @@ module ActiveRecord
81
81
 
82
82
  def push_with_attributes(record, join_attributes = {})
83
83
  raise_on_type_mismatch(record)
84
- insert_record_with_join_attributes(record, join_attributes)
85
- join_attributes.each { |key, value| record.send(:write_attribute, key, value) }
84
+ join_attributes.each { |key, value| record[key.to_s] = value }
85
+ insert_record(record) unless @owner.new_record?
86
86
  @target << record
87
87
  self
88
88
  end
@@ -105,22 +105,33 @@ module ActiveRecord
105
105
 
106
106
  def insert_record(record)
107
107
  return false unless record.save
108
+
108
109
  if @options[:insert_sql]
109
110
  @owner.connection.execute(interpolate_sql(@options[:insert_sql], record))
110
111
  else
111
- sql = "INSERT INTO #{@join_table} (#{@association_class_primary_key_name}, #{@association_foreign_key}) " +
112
- "VALUES (#{@owner.quoted_id},#{record.quoted_id})"
112
+ columns = @owner.connection.columns(@join_table, "#{@join_table} Columns")
113
+
114
+ attributes = columns.inject({}) do |attributes, column|
115
+ case column.name
116
+ when @association_class_primary_key_name
117
+ attributes[column.name] = @owner.quoted_id
118
+ when @association_foreign_key
119
+ attributes[column.name] = record.quoted_id
120
+ else
121
+ value = record[column.name]
122
+ attributes[column.name] = value unless value.nil?
123
+ end
124
+ attributes
125
+ end
126
+
127
+ sql =
128
+ "INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
129
+ "VALUES (#{attributes.values.collect { |value| @owner.send(:quote, value) }.join(', ')})"
130
+
113
131
  @owner.connection.execute(sql)
114
132
  end
115
- true
116
- end
117
-
118
- def insert_record_with_join_attributes(record, join_attributes)
119
- attributes = { @association_class_primary_key_name => @owner.id, @association_foreign_key => record.id }.update(join_attributes)
120
- sql =
121
- "INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
122
- "VALUES (#{attributes.values.collect { |value| @owner.send(:quote, value) }.join(', ')})"
123
- @owner.connection.execute(sql)
133
+
134
+ return true
124
135
  end
125
136
 
126
137
  def delete_records(records)
@@ -9,18 +9,22 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def build(attributes = {})
12
- load_target
13
- record = @association_class.new(attributes)
14
- record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
15
- @target << record
16
- record
12
+ if attributes.is_a?(Array)
13
+ attributes.collect { |attr| create(attr) }
14
+ else
15
+ load_target
16
+ record = @association_class.new(attributes)
17
+ record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
18
+ @target << record
19
+ record
20
+ end
17
21
  end
18
22
 
19
23
  def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
20
24
  if @options[:finder_sql]
21
25
  records = @association_class.find_by_sql(@finder_sql)
22
26
  else
23
- sql = @finder_sql
27
+ sql = @finder_sql.dup
24
28
  sql << " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
25
29
  orderings ||= @options[:order]
26
30
  records = @association_class.find_all(sql, orderings, limit, joins)
@@ -124,11 +124,11 @@ module ActiveRecord #:nodoc:
124
124
  # user = User.create("preferences" => { "background" => "black", "display" => large })
125
125
  # User.find(user.id).preferences # => { "background" => "black", "display" => large }
126
126
  #
127
- # You can also specify an optional :class_name option that'll raise an exception if a serialized object is retrieved as a
127
+ # You can also specify an class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
128
128
  # descendent of a class not in the hierarchy. Example:
129
129
  #
130
130
  # class User < ActiveRecord::Base
131
- # serialize :preferences, :class_name => "Hash"
131
+ # serialize :preferences, Hash
132
132
  # end
133
133
  #
134
134
  # user = User.create("preferences" => %w( one two three ))
@@ -171,8 +171,7 @@ module ActiveRecord #:nodoc:
171
171
  # * +AdapterNotSpecified+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an unexisting adapter
172
172
  # (or a bad spelling of an existing one).
173
173
  # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
174
- # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified in the <tt>:class_name</tt> option of
175
- # the serialize definition.
174
+ # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
176
175
  # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
177
176
  # * +RecordNotFound+ -- no record responded to the find* method.
178
177
  # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
@@ -289,7 +288,7 @@ module ActiveRecord #:nodoc:
289
288
  if result.size == ids.size
290
289
  result
291
290
  else
292
- raise RecordNotFound, "Couldn't find #{name} with ID in (#{ids_list})#{conditions}"
291
+ raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
293
292
  end
294
293
  end
295
294
  end
@@ -340,39 +339,49 @@ module ActiveRecord #:nodoc:
340
339
  # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
341
340
  # fail under validations, the unsaved object is still returned.
342
341
  def create(attributes = nil)
343
- object = new(attributes)
344
- object.save
345
- object
342
+ if attributes.is_a?(Array)
343
+ attributes.collect { |attr| create(attr) }
344
+ else
345
+ object = new(attributes)
346
+ object.save
347
+ object
348
+ end
346
349
  end
347
350
 
348
351
  # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
349
352
  # and returns it. If the save fail under validations, the unsaved object is still returned.
350
353
  def update(id, attributes)
351
- object = find(id)
352
- object.attributes = attributes
353
- object.save
354
- object
354
+ if id.is_a?(Array)
355
+ idx = -1
356
+ id.collect { |id| idx += 1; update(id, attributes[idx]) }
357
+ else
358
+ object = find(id)
359
+ object.update_attributes(attributes)
360
+ object
361
+ end
355
362
  end
356
363
 
357
- # Deletes the record with the given +id+ without instantiating an object first.
364
+ # Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them
365
+ # are deleted.
358
366
  def delete(id)
359
- delete_all([ "#{primary_key} = ?", id ])
367
+ delete_all([ "#{primary_key} IN (?)", id ])
360
368
  end
361
369
 
362
370
  # Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
371
+ # If an array of ids is provided, all of them are destroyed.
363
372
  def destroy(id)
364
- find(id).destroy
373
+ id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
365
374
  end
366
375
 
367
376
  # Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updates.
368
377
  # A subset of the records can be selected by specifying +conditions+. Example:
369
378
  # Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
370
379
  def update_all(updates, conditions = nil)
371
- sql = "UPDATE #{table_name} SET #{updates} "
380
+ sql = "UPDATE #{table_name} SET #{sanitize_sql(updates)} "
372
381
  add_conditions!(sql, conditions)
373
382
  return connection.update(sql, "#{name} Update")
374
383
  end
375
-
384
+
376
385
  # Destroys the objects for all the records that matches the +condition+ by instantiating each object and calling
377
386
  # the destroy method. Example:
378
387
  # Person.destroy_all "last_login < '2004-04-04'"
@@ -598,6 +607,11 @@ module ActiveRecord #:nodoc:
598
607
  return result
599
608
  end
600
609
 
610
+ # Overwrite the default class equality method to provide support for association proxies.
611
+ def ===(object)
612
+ object.is_a?(self)
613
+ end
614
+
601
615
  private
602
616
  # Finder methods must instantiate through this method to work with the single-table inheritance model
603
617
  # that makes it possible to create objects of different types from the same table.
@@ -663,7 +677,9 @@ module ActiveRecord #:nodoc:
663
677
  if method_name =~ /find_(all_by|by)_([_a-z]+)/
664
678
  finder, attributes = ($1 == "all_by" ? :find_all : :find_first), $2.split("_and_")
665
679
  attributes.each { |attr_name| super unless column_methods_hash[attr_name.intern] }
666
- conditions = attributes.collect { |attr_name| "#{attr_name} = ? "}.join(" AND ")
680
+
681
+ attr_index = -1
682
+ conditions = attributes.collect { |attr_name| attr_index += 1; "#{attr_name} #{arguments[attr_index] ? "=" : "IS"} ? " }.join(" AND ")
667
683
  send(finder, [conditions, *arguments[0...attributes.length]], *arguments[attributes.length..-1])
668
684
  else
669
685
  super
@@ -716,7 +732,7 @@ module ActiveRecord #:nodoc:
716
732
  def replace_bind_variables(statement, values)
717
733
  raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
718
734
  bound = values.dup
719
- statement.gsub('?') { connection.quote(bound.shift) }
735
+ statement.gsub('?') { quote_bound_value(bound.shift) }
720
736
  end
721
737
 
722
738
  def replace_named_bind_variables(statement, bind_vars)
@@ -724,13 +740,22 @@ module ActiveRecord #:nodoc:
724
740
  statement.gsub(/:(\w+)/) do
725
741
  match = $1.to_sym
726
742
  if bind_vars.has_key?(match)
727
- connection.quote(bind_vars[match])
743
+ quote_bound_value(bind_vars[match])
728
744
  else
729
745
  raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
730
746
  end
731
747
  end
732
748
  end
733
749
 
750
+ def quote_bound_value(value)
751
+ case value
752
+ when Array
753
+ value.map { |v| connection.quote(v) }.join(',')
754
+ else
755
+ connection.quote(value)
756
+ end
757
+ end
758
+
734
759
  def raise_if_bind_arity_mismatch(statement, expected, provided)
735
760
  unless expected == provided
736
761
  raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
@@ -1093,22 +1118,30 @@ module ActiveRecord #:nodoc:
1093
1118
 
1094
1119
  def remove_attributes_protected_from_mass_assignment(attributes)
1095
1120
  if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
1096
- attributes.reject { |key, value| key == self.class.primary_key }
1121
+ attributes.reject { |key, value| attributes_protected_by_default.include?(key) }
1097
1122
  elsif self.class.protected_attributes.nil?
1098
- attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.intern) || key == self.class.primary_key }
1123
+ attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.intern) || attributes_protected_by_default.include?(key) }
1099
1124
  elsif self.class.accessible_attributes.nil?
1100
- attributes.reject { |key, value| self.class.protected_attributes.include?(key.intern) || key == self.class.primary_key }
1125
+ attributes.reject { |key, value| self.class.protected_attributes.include?(key.intern) || attributes_protected_by_default.include?(key) }
1101
1126
  end
1102
1127
  end
1103
1128
 
1129
+ # The primary key and inheritance column can never be set by mass-assignment for security reasons.
1130
+ def attributes_protected_by_default
1131
+ [ self.class.primary_key, self.class.inheritance_column ]
1132
+ end
1133
+
1104
1134
  # Returns copy of the attributes hash where all the values have been safely quoted for use in
1105
1135
  # an SQL statement.
1106
1136
  def attributes_with_quotes(include_primary_key = true)
1107
1137
  columns_hash = self.class.columns_hash
1108
- @attributes.inject({}) do |attrs_quoted, pair|
1138
+
1139
+ attrs_quoted = @attributes.inject({}) do |attrs_quoted, pair|
1109
1140
  attrs_quoted[pair.first] = quote(pair.last, columns_hash[pair.first]) unless !include_primary_key && pair.first == self.class.primary_key
1110
1141
  attrs_quoted
1111
1142
  end
1143
+
1144
+ attrs_quoted.delete_if { |key, value| !self.class.columns_hash.keys.include?(key) }
1112
1145
  end
1113
1146
 
1114
1147
  # Quote strings appropriately for SQL statements.
@@ -1167,7 +1200,7 @@ module ActiveRecord #:nodoc:
1167
1200
 
1168
1201
  unless value.empty?
1169
1202
  attributes[attribute_name] <<
1170
- [find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value)]
1203
+ [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
1171
1204
  end
1172
1205
  end
1173
1206
 
@@ -1192,10 +1225,10 @@ module ActiveRecord #:nodoc:
1192
1225
  end
1193
1226
 
1194
1227
  def quote_columns(column_quoter, hash)
1195
- hash.inject({}) {|list, pair|
1228
+ hash.inject({}) do |list, pair|
1196
1229
  list[column_quoter.quote_column_name(pair.first)] = pair.last
1197
1230
  list
1198
- }
1231
+ end
1199
1232
  end
1200
1233
 
1201
1234
  def quoted_comma_pair_list(column_quoter, hash)
@@ -1203,7 +1236,7 @@ module ActiveRecord #:nodoc:
1203
1236
  end
1204
1237
 
1205
1238
  def object_from_yaml(string)
1206
- return string unless String === string
1239
+ return string unless string.is_a?(String)
1207
1240
  if has_yaml_encoding_header?(string)
1208
1241
  begin
1209
1242
  YAML::load(string)
@@ -1220,4 +1253,4 @@ module ActiveRecord #:nodoc:
1220
1253
  string[0..3] == "--- "
1221
1254
  end
1222
1255
  end
1223
- end
1256
+ end
@@ -217,21 +217,21 @@ module ActiveRecord
217
217
 
218
218
  private
219
219
  def string_to_date(string)
220
- return string if Date === string
220
+ return string if string.is_a?(Date)
221
221
  date_array = ParseDate.parsedate(string)
222
222
  # treat 0000-00-00 as nil
223
223
  Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
224
224
  end
225
225
 
226
226
  def string_to_time(string)
227
- return string if Time === string
227
+ return string if string.is_a?(Time)
228
228
  time_array = ParseDate.parsedate(string).compact
229
229
  # treat 0000-00-00 00:00:00 as nil
230
230
  Time.send(Base.default_timezone, *time_array) rescue nil
231
231
  end
232
232
 
233
233
  def string_to_dummy_time(string)
234
- return string if Time === string
234
+ return string if string.is_a?(Time)
235
235
  time_array = ParseDate.parsedate(string)
236
236
  # pad the resulting array with dummy date information
237
237
  time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
@@ -166,7 +166,7 @@ class Fixtures < Hash
166
166
  fixtures.each { |fixture| fixture.insert_fixtures }
167
167
  end
168
168
 
169
- reset_sequences(connection, table_names) if ActiveRecord::ConnectionAdapters::PostgreSQLAdapter === connection
169
+ reset_sequences(connection, table_names) if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
170
170
 
171
171
  return fixtures.size > 1 ? fixtures : fixtures.first
172
172
  ensure
@@ -1,85 +1,83 @@
1
- begin
2
- require 'simplecc'
3
- rescue LoadError
4
- class Continuation #:nodoc:
5
- def self.create(*args, &block)
6
- cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
7
- result ||= args
8
- return *[cc, *result]
9
- end
10
- end
11
- end
12
-
13
- class Binding #:nodoc:
14
- # This method returns the binding of the method that called your
15
- # method. It will raise an Exception when you're not inside a method.
16
- #
17
- # It's used like this:
18
- # def inc_counter(amount = 1)
19
- # Binding.of_caller do |binding|
20
- # # Create a lambda that will increase the variable 'counter'
21
- # # in the caller of this method when called.
22
- # inc = eval("lambda { |arg| counter += arg }", binding)
23
- # # We can refer to amount from inside this block safely.
24
- # inc.call(amount)
25
- # end
26
- # # No other statements can go here. Put them inside the block.
27
- # end
28
- # counter = 0
29
- # 2.times { inc_counter }
30
- # counter # => 2
31
- #
32
- # Binding.of_caller must be the last statement in the method.
33
- # This means that you will have to put everything you want to
34
- # do after the call to Binding.of_caller into the block of it.
35
- # This should be no problem however, because Ruby has closures.
36
- # If you don't do this an Exception will be raised. Because of
37
- # the way that Binding.of_caller is implemented it has to be
38
- # done this way.
39
- def self.of_caller(&block)
40
- old_critical = Thread.critical
41
- Thread.critical = true
42
- count = 0
43
- cc, result, error, extra_data = Continuation.create(nil, nil)
44
- error.call if error
45
-
46
- tracer = lambda do |*args|
47
- type, context, extra_data = args[0], args[4], args
48
- if type == "return"
49
- count += 1
50
- # First this method and then calling one will return --
51
- # the trace event of the second event gets the context
52
- # of the method which called the method that called this
53
- # method.
54
- if count == 2
55
- # It would be nice if we could restore the trace_func
56
- # that was set before we swapped in our own one, but
57
- # this is impossible without overloading set_trace_func
58
- # in current Ruby.
59
- set_trace_func(nil)
60
- cc.call(eval("binding", context), nil, extra_data)
61
- end
62
- elsif type == "line" then
63
- nil
64
- elsif type == "c-return" and extra_data[3] == :set_trace_func then
65
- nil
66
- else
67
- set_trace_func(nil)
68
- error_msg = "Binding.of_caller used in non-method context or " +
69
- "trailing statements of method using it aren't in the block."
70
- cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
71
- end
72
- end
73
-
74
- unless result
75
- set_trace_func(tracer)
76
- return nil
77
- else
78
- Thread.critical = old_critical
79
- case block.arity
80
- when 1 then yield(result)
81
- else yield(result, extra_data)
82
- end
83
- end
84
- end
85
- end
1
+ begin
2
+ require 'simplecc'
3
+ rescue LoadError
4
+ class Continuation; end # :nodoc: # for RDoc
5
+ def Continuation.create(*args, &block) # :nodoc:
6
+ cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
7
+ result ||= args
8
+ return *[cc, *result]
9
+ end
10
+ end
11
+
12
+ class Binding; end # for RDoc
13
+ # This method returns the binding of the method that called your
14
+ # method. It will raise an Exception when you're not inside a method.
15
+ #
16
+ # It's used like this:
17
+ # def inc_counter(amount = 1)
18
+ # Binding.of_caller do |binding|
19
+ # # Create a lambda that will increase the variable 'counter'
20
+ # # in the caller of this method when called.
21
+ # inc = eval("lambda { |arg| counter += arg }", binding)
22
+ # # We can refer to amount from inside this block safely.
23
+ # inc.call(amount)
24
+ # end
25
+ # # No other statements can go here. Put them inside the block.
26
+ # end
27
+ # counter = 0
28
+ # 2.times { inc_counter }
29
+ # counter # => 2
30
+ #
31
+ # Binding.of_caller must be the last statement in the method.
32
+ # This means that you will have to put everything you want to
33
+ # do after the call to Binding.of_caller into the block of it.
34
+ # This should be no problem however, because Ruby has closures.
35
+ # If you don't do this an Exception will be raised. Because of
36
+ # the way that Binding.of_caller is implemented it has to be
37
+ # done this way.
38
+ def Binding.of_caller(&block)
39
+ old_critical = Thread.critical
40
+ Thread.critical = true
41
+ count = 0
42
+ cc, result, error, extra_data = Continuation.create(nil, nil)
43
+ error.call if error
44
+
45
+ tracer = lambda do |*args|
46
+ type, context, extra_data = args[0], args[4], args
47
+ if type == "return"
48
+ count += 1
49
+ # First this method and then calling one will return --
50
+ # the trace event of the second event gets the context
51
+ # of the method which called the method that called this
52
+ # method.
53
+ if count == 2
54
+ # It would be nice if we could restore the trace_func
55
+ # that was set before we swapped in our own one, but
56
+ # this is impossible without overloading set_trace_func
57
+ # in current Ruby.
58
+ set_trace_func(nil)
59
+ cc.call(eval("binding", context), nil, extra_data)
60
+ end
61
+ elsif type == "line" then
62
+ nil
63
+ elsif type == "c-return" and extra_data[3] == :set_trace_func then
64
+ nil
65
+ else
66
+ set_trace_func(nil)
67
+ error_msg = "Binding.of_caller used in non-method context or " +
68
+ "trailing statements of method using it aren't in the block."
69
+ cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
70
+ end
71
+ end
72
+
73
+ unless result
74
+ set_trace_func(tracer)
75
+ return nil
76
+ else
77
+ Thread.critical = old_critical
78
+ case block.arity
79
+ when 1 then yield(result)
80
+ else yield(result, extra_data)
81
+ end
82
+ end
83
+ end