pluginaweek-enumerate_by 0.4.2

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.
@@ -0,0 +1,117 @@
1
+ module EnumerateBy
2
+ module Extensions #:nodoc:
3
+ # Adds support for automatically converting enumeration attributes to the
4
+ # value represented by them.
5
+ #
6
+ # == Examples
7
+ #
8
+ # Suppose the following models are defined:
9
+ #
10
+ # class Color < ActiveRecord::Base
11
+ # enumerate_by :name
12
+ #
13
+ # bootstrap(
14
+ # {:id => 1, :name => 'red'},
15
+ # {:id => 2, :name => 'blue'},
16
+ # {:id => 3, :name => 'green'}
17
+ # )
18
+ # end
19
+ #
20
+ # class Car < ActiveRecord::Base
21
+ # belongs_to :color
22
+ # end
23
+ #
24
+ # Given the above, the enumerator for the car will be automatically
25
+ # used for serialization instead of the foreign key like so:
26
+ #
27
+ # car = Car.create(:color => 'red') # => #<Car id: 1, color_id: 1>
28
+ # car.to_xml # => "<car><id type=\"integer\">1</id><color>red</color></car>"
29
+ # car.to_json # => "{id: 1, color: \"red\"}"
30
+ #
31
+ # == Conversion options
32
+ #
33
+ # The actual conversion of enumeration associations can be controlled
34
+ # using the following options:
35
+ #
36
+ # car.to_json # => "{id: 1, color: \"red\"}"
37
+ # car.to_json(:enumerations => false) # => "{id: 1, color_id: 1}"
38
+ # car.to_json(:only => [:color_id]) # => "{color_id: 1}"
39
+ # car.to_json(:only => [:color]) # => "{color: \"red\"}"
40
+ # car.to_json(:include => :color) # => "{id: 1, color_id: 1, color: {id: 1, name: \"red\"}}"
41
+ #
42
+ # As can be seen from above, enumeration attributes can either be treated
43
+ # as pseudo-attributes on the record or its actual association.
44
+ module Serializer
45
+ def self.included(base) #:nodoc:
46
+ base.class_eval do
47
+ alias_method_chain :serializable_attribute_names, :enumerations
48
+ alias_method_chain :serializable_record, :enumerations
49
+ end
50
+ end
51
+
52
+ # Automatically converted enumeration attributes to their association
53
+ # names so that they *appear* as attributes
54
+ def serializable_attribute_names_with_enumerations
55
+ attribute_names = serializable_attribute_names_without_enumerations
56
+
57
+ # Adjust the serializable attributes by converting primary keys for
58
+ # enumeration associations to their association name (where possible)
59
+ if convert_enumerations?
60
+ @only_attributes = Array(options[:only]).map(&:to_s)
61
+ @include_associations = Array(options[:include]).map(&:to_s)
62
+
63
+ attribute_names.map! {|attribute| enumeration_association_for(attribute) || attribute}
64
+ attribute_names |= @record.class.enumeration_associations.values & @only_attributes
65
+ attribute_names.sort!
66
+ attribute_names -= options[:except].map(&:to_s) unless options[:only]
67
+ end
68
+
69
+ attribute_names
70
+ end
71
+
72
+ # Automatically casts enumerations to their public values
73
+ def serializable_record_with_enumerations
74
+ returning(serializable_record = serializable_record_without_enumerations) do
75
+ serializable_record.each do |attribute, value|
76
+ # Typecast to enumerator value
77
+ serializable_record[attribute] = value.enumerator if typecast_to_enumerator?(attribute, value)
78
+ end if convert_enumerations?
79
+ end
80
+ end
81
+
82
+ private
83
+ # Should enumeration attributes be automatically converted based on
84
+ # the serialization configuration
85
+ def convert_enumerations?
86
+ options[:enumerations] != false
87
+ end
88
+
89
+ # Should the given attribute be converted to the actual enumeration?
90
+ def convert_to_enumeration?(attribute)
91
+ !@only_attributes.include?(attribute)
92
+ end
93
+
94
+ # Gets the association name for the given enumeration attribute, if
95
+ # one exists
96
+ def enumeration_association_for(attribute)
97
+ association = @record.class.enumeration_associations[attribute]
98
+ association if association && convert_to_enumeration?(attribute) && !include_enumeration?(association)
99
+ end
100
+
101
+ # Is the given enumeration attribute being included as a whole record
102
+ # instead of just an individual attribute?
103
+ def include_enumeration?(association)
104
+ @include_associations.include?(association)
105
+ end
106
+
107
+ # Should the given value be typecasted to its enumerator value?
108
+ def typecast_to_enumerator?(association, value)
109
+ value.is_a?(ActiveRecord::Base) && value.class.enumeration? && !include_enumeration?(association)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ ActiveRecord::Serialization::Serializer.class_eval do
116
+ include EnumerateBy::Extensions::Serializer
117
+ end
@@ -0,0 +1,41 @@
1
+ module EnumerateBy
2
+ module Extensions #:nodoc:
3
+ module XmlSerializer #:nodoc:
4
+ # Adds support for xml serialization of enumeration associations as
5
+ # attributes
6
+ module Attribute
7
+ def self.included(base) #:nodoc:
8
+ base.class_eval do
9
+ alias_method_chain :compute_type, :enumerations
10
+ alias_method_chain :compute_value, :enumerations
11
+ end
12
+ end
13
+
14
+ protected
15
+ # Enumerator types are always strings
16
+ def compute_type_with_enumerations
17
+ enumeration_association? ? :string : compute_type_without_enumerations
18
+ end
19
+
20
+ # Gets the real value representing the enumerator
21
+ def compute_value_with_enumerations
22
+ if enumeration_association?
23
+ association = @record.send(name)
24
+ association.enumerator if association
25
+ else
26
+ compute_value_without_enumerations
27
+ end
28
+ end
29
+
30
+ # Is this attribute defined by an enumeration association?
31
+ def enumeration_association?
32
+ @enumeration_association ||= @record.enumeration_associations.value?(name)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ ActiveRecord::XmlSerializer::Attribute.class_eval do
40
+ include EnumerateBy::Extensions::XmlSerializer::Attribute
41
+ end
@@ -0,0 +1,395 @@
1
+ require 'enumerate_by/extensions/associations'
2
+ require 'enumerate_by/extensions/base_conditions'
3
+ require 'enumerate_by/extensions/serializer'
4
+ require 'enumerate_by/extensions/xml_serializer'
5
+
6
+ # An enumeration defines a finite set of enumerators which (often) have no
7
+ # numerical order. This extension provides a general technique for using
8
+ # ActiveRecord classes to define enumerations.
9
+ module EnumerateBy
10
+ # Whether to enable enumeration caching (default is true)
11
+ mattr_accessor :perform_caching
12
+ self.perform_caching = true
13
+
14
+ module MacroMethods
15
+ def self.extended(base) #:nodoc:
16
+ base.class_eval do
17
+ # Tracks which associations are backed by an enumeration
18
+ # {"foreign key" => "association name"}
19
+ class_inheritable_accessor :enumeration_associations
20
+ self.enumeration_associations = {}
21
+ end
22
+ end
23
+
24
+ # Indicates that this class is an enumeration.
25
+ #
26
+ # The default attribute used to enumerate the class is +name+. You can
27
+ # override this by specifying a custom attribute that will be used to
28
+ # *uniquely* reference a record.
29
+ #
30
+ # *Note* that a presence and uniqueness validation is automatically
31
+ # defined for the given attribute since all records must have this value
32
+ # in order to be properly enumerated.
33
+ #
34
+ # Configuration options:
35
+ # * <tt>:cache</tt> - Whether to cache all finder queries for this
36
+ # enumeration. Default is true.
37
+ #
38
+ # == Defining enumerators
39
+ #
40
+ # The enumerators of the class uniquely identify each record in the
41
+ # table. The enumerator value is based on the attribute described above.
42
+ # In scenarios where the records are managed in code (like colors,
43
+ # countries, states, etc.), records can be automatically synchronized
44
+ # via #bootstrap.
45
+ #
46
+ # == Accessing records
47
+ #
48
+ # The actual records for an enumeration can be accessed via shortcut
49
+ # helpers like so:
50
+ #
51
+ # Color['red'] # => #<Color id: 1, name: "red">
52
+ # Color['green'] # => #<Color id: 2, name: "green">
53
+ #
54
+ # When caching is enabled, these lookup queries are cached so that there
55
+ # is no performance hit.
56
+ #
57
+ # == Associations
58
+ #
59
+ # When using enumerations together with +belongs_to+ associations, the
60
+ # enumerator value can be used as a shortcut for assigning the
61
+ # association.
62
+ #
63
+ # In addition, the enumerator value is automatically used during
64
+ # serialization (xml and json) of the associated record instead of the
65
+ # foreign key for the association.
66
+ #
67
+ # For more information about how to use enumerations with associations,
68
+ # see EnumerateBy::Extensions::Associations and EnumerateBy::Extensions::Serializer.
69
+ #
70
+ # === Finders
71
+ #
72
+ # In order to be consistent by always using enumerators to reference
73
+ # records, a set of finder extensions are added to allow searching
74
+ # for records like so:
75
+ #
76
+ # class Car < ActiveRecord::Base
77
+ # belongs_to :color
78
+ # end
79
+ #
80
+ # Car.find_by_color('red')
81
+ # Car.all(:conditions => {:color => 'red'})
82
+ #
83
+ # For more information about finders, see EnumerateBy::Extensions::BaseConditions.
84
+ def enumerate_by(attribute = :name, options = {})
85
+ options.reverse_merge!(:cache => true)
86
+ options.assert_valid_keys(:cache)
87
+
88
+ extend EnumerateBy::ClassMethods
89
+ extend EnumerateBy::Bootstrapped
90
+ include EnumerateBy::InstanceMethods
91
+
92
+ # The attribute representing a record's enumerator
93
+ cattr_accessor :enumerator_attribute
94
+ self.enumerator_attribute = attribute
95
+
96
+ # Whether to perform caching of enumerators within finder queries
97
+ cattr_accessor :perform_enumerator_caching
98
+ self.perform_enumerator_caching = options[:cache]
99
+
100
+ # The cache store to use for queries (default is a memory store)
101
+ cattr_accessor :enumerator_cache_store
102
+ self.enumerator_cache_store = ActiveSupport::Cache::MemoryStore.new
103
+
104
+ validates_presence_of attribute
105
+ validates_uniqueness_of attribute
106
+ end
107
+
108
+ # Does this class define an enumeration? Always false.
109
+ def enumeration?
110
+ false
111
+ end
112
+ end
113
+
114
+ module ClassMethods
115
+ # Does this class define an enumeration? Always true.
116
+ def enumeration?
117
+ true
118
+ end
119
+
120
+ # Finds the record that is associated with the given enumerator. The
121
+ # attribute that defines the enumerator is based on what was specified
122
+ # when calling +enumerate_by+.
123
+ #
124
+ # For example,
125
+ #
126
+ # Color.find_by_enumerator('red') # => #<Color id: 1, name: "red">
127
+ # Color.find_by_enumerator('invalid') # => nil
128
+ def find_by_enumerator(enumerator)
129
+ first(:conditions => {enumerator_attribute => enumerator})
130
+ end
131
+
132
+ # Finds the record that is associated with the given enumerator. If no
133
+ # record is found, then an ActiveRecord::RecordNotFound exception is
134
+ # raised.
135
+ #
136
+ # For example,
137
+ #
138
+ # Color['red'] # => #<Color id: 1, name: "red">
139
+ # Color['invalid'] # => ActiveRecord::RecordNotFound: Couldn't find Color with name "red"
140
+ #
141
+ # To avoid raising an exception on invalid enumerators, use +find_by_enumerator+.
142
+ def find_by_enumerator!(enumerator)
143
+ find_by_enumerator(enumerator) || raise(ActiveRecord::RecordNotFound, "Couldn't find #{name} with #{enumerator_attribute} #{enumerator.inspect}")
144
+ end
145
+ alias_method :[], :find_by_enumerator!
146
+
147
+ # Finds records with the given enumerators.
148
+ #
149
+ # For example,
150
+ #
151
+ # Color.find_all_by_enumerator('red', 'green') # => [#<Color id: 1, name: "red">, #<Color id: 1, name: "green">]
152
+ # Color.find_all_by_enumerator('invalid') # => []
153
+ def find_all_by_enumerator(enumerators)
154
+ all(:conditions => {enumerator_attribute => enumerators})
155
+ end
156
+
157
+ # Finds records with the given enumerators. If no record is found for a
158
+ # particular enumerator, then an ActiveRecord::RecordNotFound exception
159
+ # is raised.
160
+ #
161
+ # For Example,
162
+ #
163
+ # Color.find_all_by_enumerator!('red', 'green') # => [#<Color id: 1, name: "red">, #<Color id: 1, name: "green">]
164
+ # Color.find_all_by_enumerator!('invalid') # => ActiveRecord::RecordNotFound: Couldn't find Color with name(s) "invalid"
165
+ #
166
+ # To avoid raising an exception on invalid enumerators, use +find_all_by_enumerator+.
167
+ def find_all_by_enumerator!(enumerators)
168
+ records = find_all_by_enumerator(enumerators)
169
+ missing = [enumerators].flatten - records.map(&:enumerator)
170
+ missing.empty? ? records : raise(ActiveRecord::RecordNotFound, "Couldn't find #{name} with #{enumerator_attribute}(s) #{missing.map(&:inspect).to_sentence}")
171
+ end
172
+
173
+ # Adds support for looking up results from the enumeration cache for
174
+ # before querying the database.
175
+ #
176
+ # This allows for enumerations to permanently cache find queries, avoiding
177
+ # unnecessary lookups in the database.
178
+ [:find_by_sql, :exists?, :calculate].each do |method|
179
+ define_method(method) do |*args|
180
+ if EnumerateBy.perform_caching && perform_enumerator_caching
181
+ enumerator_cache_store.fetch([method] + args) { super }
182
+ else
183
+ super
184
+ end
185
+ end
186
+ end
187
+
188
+ # Temporarily disables the enumeration cache (as well as the query cache)
189
+ # within the context of the given block if the enumeration is configured
190
+ # to allow caching.
191
+ def uncached
192
+ old = perform_enumerator_caching
193
+ self.perform_enumerator_caching = false
194
+ super
195
+ ensure
196
+ self.perform_enumerator_caching = old
197
+ end
198
+ end
199
+
200
+ module Bootstrapped
201
+ # Synchronizes the given records with existing ones. This ensures that
202
+ # only the correct and most up-to-date records exist in the database.
203
+ # The sync process is as follows:
204
+ # * Any existing record that doesn't match is deleted
205
+ # * Existing records with matches are updated based on the given attributes for that record
206
+ # * Records that don't exist are created
207
+ #
208
+ # To create records that can be referenced elsewhere in the database, an
209
+ # id should always be specified. Otherwise, records may change id each
210
+ # time they are bootstrapped.
211
+ #
212
+ # == Examples
213
+ #
214
+ # class Color < ActiveRecord::Base
215
+ # enumerate_by :name
216
+ #
217
+ # bootstrap(
218
+ # {:id => 1, :name => 'red'},
219
+ # {:id => 2, :name => 'blue'},
220
+ # {:id => 3, :name => 'green'}
221
+ # )
222
+ # end
223
+ #
224
+ # In the above model, the +colors+ table will be synchronized with the 3
225
+ # records passed into the +bootstrap+ helper. Any existing records that
226
+ # do not match those 3 are deleted. Otherwise, they are either created or
227
+ # updated with the attributes specified.
228
+ #
229
+ # == Defaults
230
+ #
231
+ # In addition to *always* synchronizing certain attributes, an additional
232
+ # +defaults+ option can be given to indicate that certain attributes
233
+ # should only be synchronized if they haven't been modified in the
234
+ # database.
235
+ #
236
+ # For example,
237
+ #
238
+ # class Color < ActiveRecord::Base
239
+ # enumerate_by :name
240
+ #
241
+ # bootstrap(
242
+ # {:id => 1, :name => 'red', :defaults => {:html => '#f00'}},
243
+ # {:id => 2, :name => 'blue', :defaults => {:html => '#0f0'}},
244
+ # {:id => 3, :name => 'green', :defaults => {:html => '#00f'}}
245
+ # )
246
+ # end
247
+ #
248
+ # In the above model, the +name+ attribute will always be updated on
249
+ # existing records in the database. However, the +html+ attribute will
250
+ # only be synchronized if the attribute is nil in the database.
251
+ # Otherwise, any changes to that column remain there.
252
+ def bootstrap(*records)
253
+ uncached do
254
+ # Remove records that are no longer being used
255
+ records.flatten!
256
+ ids = records.map {|record| record[:id]}.compact
257
+ delete_all(ids.any? ? ['id NOT IN (?)', ids] : nil)
258
+
259
+ # Find remaining existing records (to be updated)
260
+ existing = all.inject({}) {|existing, record| existing[record.id] = record; existing}
261
+
262
+ records.map! do |attributes|
263
+ attributes.symbolize_keys!
264
+ defaults = attributes.delete(:defaults)
265
+
266
+ # Update with new attributes
267
+ record =
268
+ if record = existing[attributes[:id]]
269
+ attributes.merge!(defaults.delete_if {|attribute, value| record.send("#{attribute}?")}) if defaults
270
+ record.attributes = attributes
271
+ record
272
+ else
273
+ attributes.merge!(defaults) if defaults
274
+ new(attributes)
275
+ end
276
+ record.id = attributes[:id]
277
+
278
+ # Force failed saves to stop execution
279
+ record.save!
280
+ record
281
+ end
282
+
283
+ records
284
+ end
285
+ end
286
+
287
+ # Quickly synchronizes the given records with the existing ones. This
288
+ # skips ActiveRecord altogether, interacting directly with the connection
289
+ # instead. As a result, certain features are not available when being
290
+ # bootstrapped, including:
291
+ # * Callbacks
292
+ # * Validations
293
+ # * Transactions
294
+ # * Timestamps
295
+ # * Dirty attributes
296
+ #
297
+ # Also note that records are created directly without creating instances
298
+ # of the model. As a result, all of the attributes for the record must
299
+ # be specified.
300
+ #
301
+ # This produces a significant performance increase when bootstrapping more
302
+ # than several hundred records.
303
+ #
304
+ # See EnumerateBy::Bootstrapped#bootstrap for information about usage.
305
+ def fast_bootstrap(*records)
306
+ # Remove records that are no longer being used
307
+ records.flatten!
308
+ ids = records.map {|record| record[:id]}.compact
309
+ delete_all(ids.any? ? ['id NOT IN (?)', ids] : nil)
310
+
311
+ # Find remaining existing records (to be updated)
312
+ quoted_table_name = self.quoted_table_name
313
+ existing = connection.select_all("SELECT * FROM #{quoted_table_name}").inject({}) {|existing, record| existing[record['id'].to_i] = record; existing}
314
+
315
+ records.each do |attributes|
316
+ attributes.stringify_keys!
317
+ if defaults = attributes.delete('defaults')
318
+ defaults.stringify_keys!
319
+ end
320
+
321
+ id = attributes['id']
322
+ if existing_attributes = existing[id]
323
+ # Record exists: Update attributes
324
+ attributes.delete('id')
325
+ attributes.merge!(defaults.delete_if {|attribute, value| !existing_attributes[attribute].nil?}) if defaults
326
+ update_all(attributes, :id => id)
327
+ else
328
+ # Record doesn't exist: create new one
329
+ attributes.merge!(defaults) if defaults
330
+ column_names = []
331
+ values = []
332
+
333
+ attributes.each do |column_name, value|
334
+ column_names << connection.quote_column_name(column_name)
335
+ values << connection.quote(value, columns_hash[column_name])
336
+ end
337
+
338
+ connection.insert(
339
+ "INSERT INTO #{quoted_table_name} (#{column_names * ', '}) VALUES(#{values * ', '})",
340
+ "#{name} Create", primary_key, id, sequence_name
341
+ )
342
+ end
343
+ end
344
+
345
+ true
346
+ end
347
+ end
348
+
349
+ module InstanceMethods
350
+ # Whether or not this record is equal to the given value. If the value is
351
+ # a String, then it is compared against the enumerator. Otherwise,
352
+ # ActiveRecord's default equality comparator is used.
353
+ def ==(arg)
354
+ arg.is_a?(String) ? self == self.class.find_by_enumerator!(arg) : super
355
+ end
356
+
357
+ # Determines whether this enumeration is in the given list.
358
+ #
359
+ # For example,
360
+ #
361
+ # color = Color.find_by_name('red') # => #<Color id: 1, name: "red">
362
+ # color.in?('green') # => false
363
+ # color.in?('red', 'green') # => true
364
+ def in?(*list)
365
+ list.any? {|item| self === item}
366
+ end
367
+
368
+ # A helper method for getting the current value of the enumerator
369
+ # attribute for this record. For example, if this record's model is
370
+ # enumerated by the attribute +name+, then this will return the current
371
+ # value for +name+.
372
+ def enumerator
373
+ send(enumerator_attribute)
374
+ end
375
+
376
+ # Stringifies the record typecasted to the enumerator value.
377
+ #
378
+ # For example,
379
+ #
380
+ # color = Color.find_by_name('red') # => #<Color id: 1, name: "red">
381
+ # color.to_s # => "red"
382
+ def to_s
383
+ to_str
384
+ end
385
+
386
+ # Add support for equality comparison with strings
387
+ def to_str
388
+ enumerator.to_s
389
+ end
390
+ end
391
+ end
392
+
393
+ ActiveRecord::Base.class_eval do
394
+ extend EnumerateBy::MacroMethods
395
+ end
@@ -0,0 +1,4 @@
1
+ class Car < ActiveRecord::Base
2
+ belongs_to :color
3
+ belongs_to :feature, :polymorphic => true
4
+ end
@@ -0,0 +1,3 @@
1
+ class Color < ActiveRecord::Base
2
+ enumerate_by :name
3
+ end
@@ -0,0 +1,12 @@
1
+ class CreateColors < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :colors do |t|
4
+ t.string :name, :null => false
5
+ t.string :html
6
+ end
7
+ end
8
+
9
+ def self.down
10
+ drop_table :colors
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCars < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :cars do |t|
4
+ t.string :name
5
+ t.references :color
6
+ t.references :feature, :class_name => 'Color', :polymorphic => true
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :cars
12
+ end
13
+ end
data/test/factory.rb ADDED
@@ -0,0 +1,48 @@
1
+ module Factory
2
+ # Build actions for the model
3
+ def self.build(model, &block)
4
+ name = model.to_s.underscore
5
+
6
+ define_method("#{name}_attributes", block)
7
+ define_method("valid_#{name}_attributes") {|*args| valid_attributes_for(model, *args)}
8
+ define_method("new_#{name}") {|*args| new_record(model, *args)}
9
+ define_method("create_#{name}") {|*args| create_record(model, *args)}
10
+ end
11
+
12
+ # Get valid attributes for the model
13
+ def valid_attributes_for(model, attributes = {})
14
+ name = model.to_s.underscore
15
+ send("#{name}_attributes", attributes)
16
+ attributes.stringify_keys!
17
+ attributes
18
+ end
19
+
20
+ # Build an unsaved record
21
+ def new_record(model, *args)
22
+ attributes = valid_attributes_for(model, *args)
23
+ record = model.new(attributes)
24
+ attributes.each {|attr, value| record.send("#{attr}=", value) if model.accessible_attributes && !model.accessible_attributes.include?(attr) || model.protected_attributes && model.protected_attributes.include?(attr)}
25
+ record
26
+ end
27
+
28
+ # Build and save/reload a record
29
+ def create_record(model, *args)
30
+ record = new_record(model, *args)
31
+ record.save!
32
+ record.reload
33
+ record
34
+ end
35
+
36
+ build Car do |attributes|
37
+ attributes[:color] = create_color unless attributes.include?(:color)
38
+ attributes.reverse_merge!(
39
+ :name => 'Ford Mustang'
40
+ )
41
+ end
42
+
43
+ build Color do |attributes|
44
+ attributes.reverse_merge!(
45
+ :name => 'red'
46
+ )
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ # Load the plugin testing framework
2
+ $:.unshift("#{File.dirname(__FILE__)}/../../plugin_test_helper/lib")
3
+ require 'rubygems'
4
+ require 'plugin_test_helper'
5
+
6
+ # Run the migrations
7
+ ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
8
+
9
+ # Mixin the factory helper
10
+ require File.expand_path("#{File.dirname(__FILE__)}/factory")
11
+ Test::Unit::TestCase.class_eval do
12
+ include Factory
13
+ end
14
+
15
+ # Add query counter
16
+ ActiveRecord::Base.connection.class.class_eval do
17
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/]
18
+
19
+ def execute_with_query_record(sql, name = nil, &block)
20
+ $queries_executed ||= []
21
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
22
+ execute_without_query_record(sql, name, &block)
23
+ end
24
+
25
+ alias_method_chain :execute, :query_record
26
+ end
27
+
28
+ EnumerateBy.perform_caching = false