-
Notifications
You must be signed in to change notification settings - Fork 21.6k
/
base.rb
executable file
·1609 lines (1430 loc) · 70.9 KB
/
base.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
require 'yaml'
require 'active_record/deprecated_finders'
module ActiveRecord #:nodoc:
class ActiveRecordError < StandardError #:nodoc:
end
class SubclassNotFound < ActiveRecordError #:nodoc:
end
class AssociationTypeMismatch < ActiveRecordError #:nodoc:
end
class SerializationTypeMismatch < ActiveRecordError #:nodoc:
end
class AdapterNotSpecified < ActiveRecordError # :nodoc:
end
class AdapterNotFound < ActiveRecordError # :nodoc:
end
class ConnectionNotEstablished < ActiveRecordError #:nodoc:
end
class ConnectionFailed < ActiveRecordError #:nodoc:
end
class RecordNotFound < ActiveRecordError #:nodoc:
end
class StatementInvalid < ActiveRecordError #:nodoc:
end
class PreparedStatementInvalid < ActiveRecordError #:nodoc:
end
class StaleObjectError < ActiveRecordError #:nodoc:
end
class ConfigurationError < StandardError #:nodoc:
end
class AttributeAssignmentError < ActiveRecordError #:nodoc:
attr_reader :exception, :attribute
def initialize(message, exception, attribute)
@exception = exception
@attribute = attribute
@message = message
end
end
class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc:
attr_reader :errors
def initialize(errors)
@errors = errors
end
end
# Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
# which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
# is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
#
# See the mapping rules in table_name and the full example in link:files/README.html for more insight.
#
# == Creation
#
# Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
# you're receiving the data from somewhere else, like a HTTP request. It works like this:
#
# user = User.new(:name => "David", :occupation => "Code Artist")
# user.name # => "David"
#
# You can also use block initialization:
#
# user = User.new do |u|
# u.name = "David"
# u.occupation = "Code Artist"
# end
#
# And of course you can just create a bare object and specify the attributes after the fact:
#
# user = User.new
# user.name = "David"
# user.occupation = "Code Artist"
#
# == Conditions
#
# Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
# be used for statements that don't involve tainted data. Examples:
#
# User < ActiveRecord::Base
# def self.authenticate_unsafely(user_name, password)
# find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
# end
#
# def self.authenticate_safely(user_name, password)
# find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
# end
# end
#
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
# attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> method,
# on the other hand, will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query, which will ensure that
# an attacker can't escape the query and fake the login (or worse).
#
# When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
# question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
# the question marks with symbols and supplying a hash with values for the matching symbol keys:
#
# Company.find(:first, [
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
# ])
#
# == Overwriting default accessors
#
# All column values are automatically available through basic accessors on the Active Record object, but some times you
# want to specialize this behavior. This can be done by either by overwriting the default accessors (using the same
# name as the attribute) calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
# Example:
#
# class Song < ActiveRecord::Base
# # Uses an integer of seconds to hold the length of the song
#
# def length=(minutes)
# write_attribute(:length, minutes * 60)
# end
#
# def length
# read_attribute(:length) / 60
# end
# end
#
# You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and
# read_attribute(:attribute) as a shorter form.
#
# == Accessing attributes before they have been typecasted
#
# Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
# That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
# has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
#
# This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
# the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
# want.
#
# == Dynamic attribute-based finders
#
# Dynamic attribute-based finders are a cleaner way of getting objects by simple queries without turning to SQL. They work by
# appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name,
# Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
# <tt>Person.find(:first, ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
# And instead of writing <tt>Person.find(:all, ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
#
# It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
# <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
# <tt>Person.find(:first, ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
# <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
#
# It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
# is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
# actually Person.find_by_user_name(user_name, options). So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
#
# == Saving arrays, hashes, and other non-mappable objects in text columns
#
# Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
#
# class User < ActiveRecord::Base
# serialize :preferences
# end
#
# user = User.create(:preferences) => { "background" => "black", "display" => large })
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
#
# You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
# descendent of a class not in the hierarchy. Example:
#
# class User < ActiveRecord::Base
# serialize :preferences, Hash
# end
#
# user = User.create(:preferences => %w( one two three ))
# User.find(user.id).preferences # raises SerializationTypeMismatch
#
# == Single table inheritance
#
# Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
# by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
#
# class Company < ActiveRecord::Base; end
# class Firm < Company; end
# class Client < Company; end
# class PriorityClient < Client; end
#
# When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
# fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
#
# If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
# like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
#
# Note, all the attributes for all the cases are kept in the same table. Read more:
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
#
# == Connection to multiple databases in different models
#
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
# All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
# For example, if Course is a ActiveRecord::Base, but resides in a different database you can just say Course.establish_connection
# and Course *and all its subclasses* will use this connection instead.
#
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
# requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
#
# == Exceptions
#
# * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
# * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
# <tt>:adapter</tt> key.
# * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
# (or a bad spelling of an existing one).
# * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
# * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
# * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
# * +RecordNotFound+ -- no record responded to the find* method.
# Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
# * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message.
# Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
# * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
# +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
# objects that should be inspected to determine which attributes triggered the errors.
# * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
# You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
#
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
# So it's possible to assign a logger to the class through Base.logger= which will then be used by all
# instances in the current object space.
class Base
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
cattr_accessor :logger
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work unrelated
# to any of the specific Active Records.
def self.connection
if allow_concurrency
retrieve_connection
else
@connection ||= retrieve_connection
end
end
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work that isn't
# easily done without going straight to SQL.
def connection
self.class.connection
end
def self.inherited(child) #:nodoc:
@@subclasses[self] ||= []
@@subclasses[self] << child
super
end
def self.reset_subclasses
@@subclasses.clear
end
@@subclasses = {}
cattr_accessor :configurations
@@configurations = {}
# Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
# :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
# the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
# that this is a global setting for all Active Records.
cattr_accessor :primary_key_prefix_type
@@primary_key_prefix_type = nil
# Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
# table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
# for tables in a shared database. By default, the prefix is the empty string.
cattr_accessor :table_name_prefix
@@table_name_prefix = ""
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
# "people_basecamp"). By default, the suffix is the empty string.
cattr_accessor :table_name_suffix
@@table_name_suffix = ""
# Indicates whether or not table names should be the pluralized versions of the corresponding class names.
# If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
# See table_name for the full rules on table/class naming. This is true, by default.
cattr_accessor :pluralize_table_names
@@pluralize_table_names = true
# Determines whether or not to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
# make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
# may complicate matters if you use software like syslog. This is true, by default.
cattr_accessor :colorize_logging
@@colorize_logging = true
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
# This is set to :local by default.
cattr_accessor :default_timezone
@@default_timezone = :local
# Determines whether or not to use a connection for each thread, or a single shared connection for all threads.
# Defaults to true; Railties' WEBrick server sets this to false.
cattr_accessor :allow_concurrency
@@allow_concurrency = true
# Determines whether to speed up access by generating optimized reader
# methods to avoid expensive calls to method_missing when accessing
# attributes by name. You might want to set this to false in development
# mode, because the methods would be regenerated on each request.
cattr_accessor :generate_read_methods
@@generate_read_methods = true
# Specifies the format to use when dumping the database schema with Rails'
# Rakefile. If :sql, the schema is dumped as (potentially database-
# specific) SQL statements. If :ruby, the schema is dumped as an
# ActiveRecord::Schema file which can be loaded into any database that
# supports migrations. Use :ruby if you want to have different database
# adapters for, e.g., your development and test environments.
cattr_accessor :schema_format
@@schema_format = :sql
class << self # Class methods
# Find operates with three different retrieval approaches:
#
# * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
# If no record can be found for all of the listed ids, then RecordNotFound will be raised.
# * Find first: This will return the first record matched by the options used. These options can either be specific
# conditions or merely an order. If no record can matched, nil is returned.
# * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
#
# All approaches accept an option hash as their last parameter. The options are:
#
# * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
# * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
# * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
# * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
# * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
# to already defined associations. See eager loading under Associations.
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
# include the joined columns.
#
# Examples for find by id:
# Person.find(1) # returns the object for ID = 1
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
# Person.find([1]) # returns an array for objects the object with ID = 1
# Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
#
# Examples for find first:
# Person.find(:first) # returns the first object fetched by SELECT * FROM people
# Person.find(:first, :conditions => [ "user_name = ?", user_name])
# Person.find(:first, :order => "created_on DESC", :offset => 5)
#
# Examples for find all:
# Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
# Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
# Person.find(:all, :offset => 10, :limit => 10)
# Person.find(:all, :include => [ :account, :friends ])
def find(*args)
options = extract_options_from_args!(args)
case args.first
when :first
find(:all, options.merge(options[:include] ? { } : { :limit => 1 })).first
when :all
options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options))
else
return args.first if args.first.kind_of?(Array) && args.first.empty?
expects_array = args.first.kind_of?(Array)
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
ids = args.flatten.compact.uniq
case ids.size
when 0
raise RecordNotFound, "Couldn't find #{name} without an ID#{conditions}"
when 1
if result = find(:first, options.merge({ :conditions => "#{table_name}.#{primary_key} = #{sanitize(ids.first)}#{conditions}" }))
return expects_array ? [ result ] : result
else
raise RecordNotFound, "Couldn't find #{name} with ID=#{ids.first}#{conditions}"
end
else
# Find multiple ids
ids_list = ids.map { |id| sanitize(id) }.join(',')
result = find(:all, options.merge({ :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"}))
if result.size == ids.size
return result
else
raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
end
end
end
end
# Works like find(:all), but requires a complete SQL string. Examples:
# Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
# Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
def find_by_sql(sql)
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
end
# Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
# Example:
# Person.exists?(5)
def exists?(id)
!find(:first, :conditions => ["#{primary_key} = ?", id]).nil? rescue false
end
# Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
# fails under validations, the unsaved object is still returned.
def create(attributes = nil)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr) }
else
object = new(attributes)
object.save
object
end
end
# Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
# and returns it. If the save fails under validations, the unsaved object is still returned.
def update(id, attributes)
if id.is_a?(Array)
idx = -1
id.collect { |id| idx += 1; update(id, attributes[idx]) }
else
object = find(id)
object.update_attributes(attributes)
object
end
end
# Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them
# are deleted.
def delete(id)
delete_all([ "#{primary_key} IN (?)", id ])
end
# Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
# If an array of ids is provided, all of them are destroyed.
def destroy(id)
id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
end
# Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updated.
# A subset of the records can be selected by specifying +conditions+. Example:
# Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
def update_all(updates, conditions = nil)
sql = "UPDATE #{table_name} SET #{sanitize_sql(updates)} "
add_conditions!(sql, conditions)
connection.update(sql, "#{name} Update")
end
# Destroys the objects for all the records that match the +condition+ by instantiating each object and calling
# the destroy method. Example:
# Person.destroy_all "last_login < '2004-04-04'"
def destroy_all(conditions = nil)
find(:all, :conditions => conditions).each { |object| object.destroy }
end
# Deletes all the records that match the +condition+ without instantiating the objects first (and hence not
# calling the destroy method). Example:
# Post.destroy_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
def delete_all(conditions = nil)
sql = "DELETE FROM #{table_name} "
add_conditions!(sql, conditions)
connection.delete(sql, "#{name} Delete all")
end
# Returns the number of records that meet the +conditions+. Zero is returned if no records match. Example:
# Product.count "sales > 1"
def count(conditions = nil, joins = nil)
sql = "SELECT COUNT(*) FROM #{table_name} "
sql << " #{joins} " if joins
add_conditions!(sql, conditions)
count_by_sql(sql)
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
def count_by_sql(sql)
sql = sanitize_conditions(sql)
connection.select_value(sql, "#{name} Count").to_i
end
# Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
# discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
# This is used for caching aggregate values, so that they don't need to be computed every time. Especially important
# for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
# that needs to list both the number of posts and comments.
def increment_counter(counter_name, id)
update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{quote(id)}"
end
# Works like increment_counter, but decrements instead.
def decrement_counter(counter_name, id)
update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{quote(id)}"
end
# Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
# <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
# methods to do assignment. This is meant to protect sensitive attributes from being overwritten by URL/form hackers. Example:
#
# class Customer < ActiveRecord::Base
# attr_protected :credit_rating
# end
#
# customer = Customer.new("name" => David, "credit_rating" => "Excellent")
# customer.credit_rating # => nil
# customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
# customer.credit_rating # => nil
#
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
def attr_protected(*attributes)
write_inheritable_array("attr_protected", attributes - (protected_attributes || []))
end
# Returns an array of all the attributes that have been protected from mass-assignment.
def protected_attributes # :nodoc:
read_inheritable_attribute("attr_protected")
end
# If this macro is used, only those attributes named in it will be accessible for mass-assignment, such as
# <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
# protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
# attr_protected.
def attr_accessible(*attributes)
write_inheritable_array("attr_accessible", attributes - (accessible_attributes || []))
end
# Returns an array of all the attributes that have been made accessible to mass-assignment.
def accessible_attributes # :nodoc:
read_inheritable_attribute("attr_accessible")
end
# Specifies that the attribute by the name of +attr_name+ should be serialized before saving to the database and unserialized
# after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
# object must be of that class on retrieval or +SerializationTypeMismatch+ will be raised.
def serialize(attr_name, class_name = Object)
serialized_attributes[attr_name.to_s] = class_name
end
# Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
def serialized_attributes
read_inheritable_attribute("attr_serialized") or write_inheritable_attribute("attr_serialized", {})
end
# Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
# directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
# to guess the table name from even when called on Reply. The rules used to do the guess are handled by the Inflector class
# in Active Support, which knows almost all common English inflections (report a bug if your inflection isn't covered).
#
# Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
# So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
#
# You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
# "mice" table. Example:
#
# class Mouse < ActiveRecord::Base
# set_table_name "mice"
# end
def table_name
reset_table_name
end
def reset_table_name
name = "#{table_name_prefix}#{undecorated_table_name(class_name_of_active_record_descendant(self))}#{table_name_suffix}"
set_table_name name
name
end
# Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
# primary_key_prefix_type setting, though.
def primary_key
reset_primary_key
end
def reset_primary_key
key = 'id'
case primary_key_prefix_type
when :table_name
key = Inflector.foreign_key(class_name_of_active_record_descendant(self), false)
when :table_name_with_underscore
key = Inflector.foreign_key(class_name_of_active_record_descendant(self))
end
set_primary_key(key)
key
end
# Defines the column name for use with single table inheritance -- can be overridden in subclasses.
def inheritance_column
"type"
end
# Defines the sequence_name (for Oracle) -- can be overridden in subclasses.
def sequence_name
"#{table_name}_seq"
end
# Sets the table name to use to the given value, or (if the value
# is nil or false) to the value returned by the given block.
#
# Example:
#
# class Project < ActiveRecord::Base
# set_table_name "project"
# end
def set_table_name( value=nil, &block )
define_attr_method :table_name, value, &block
end
alias :table_name= :set_table_name
# Sets the name of the primary key column to use to the given value,
# or (if the value is nil or false) to the value returned by the given
# block.
#
# Example:
#
# class Project < ActiveRecord::Base
# set_primary_key "sysid"
# end
def set_primary_key( value=nil, &block )
define_attr_method :primary_key, value, &block
end
alias :primary_key= :set_primary_key
# Sets the name of the inheritance column to use to the given value,
# or (if the value # is nil or false) to the value returned by the
# given block.
#
# Example:
#
# class Project < ActiveRecord::Base
# set_inheritance_column do
# original_inheritance_column + "_id"
# end
# end
def set_inheritance_column( value=nil, &block )
define_attr_method :inheritance_column, value, &block
end
alias :inheritance_column= :set_inheritance_column
# Sets the name of the sequence to use when generating ids to the given
# value, or (if the value is nil or false) to the value returned by the
# given block. Currently useful only when using Oracle, which requires
# explicit sequences.
#
# Setting the sequence name when using other dbs will have no effect.
# If a sequence name is not explicitly set when using Oracle, it will
# default to the commonly used pattern of: #{table_name}_seq
#
# Example:
#
# class Project < ActiveRecord::Base
# set_sequence_name "projectseq" # default would have been "project_seq"
# end
def set_sequence_name( value=nil, &block )
define_attr_method :sequence_name, value, &block
end
alias :sequence_name= :set_sequence_name
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
def class_name(table_name = table_name) # :nodoc:
# remove any prefix and/or suffix from the table name
class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
class_name = class_name.singularize if pluralize_table_names
class_name
end
# Returns an array of column objects for the table associated with this class.
def columns
unless @columns
@columns = connection.columns(table_name, "#{name} Columns")
@columns.each {|column| column.primary = column.name == primary_key}
end
@columns
end
# Returns an array of column objects for the table associated with this class.
def columns_hash
@columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
end
def column_names
@column_names ||= columns.map { |column| column.name }
end
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
# and columns used for single table inheritance have been removed.
def content_columns
@content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
end
# Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
# and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
# is available.
def column_methods_hash
@dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
methods[attr.to_sym] = true
methods["#{attr}=".to_sym] = true
methods["#{attr}?".to_sym] = true
methods["#{attr}_before_type_cast".to_sym] = true
methods
end
end
# Contains the names of the generated reader methods.
def read_methods
@read_methods ||= {}
end
# Resets all the cached information about columns, which will cause them to be reloaded on the next request.
def reset_column_information
read_methods.each_key {|name| undef_method(name)}
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
end
# Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
# Person.human_attribute_name("first_name") # => "First name"
# Deprecated in favor of just calling "first_name".humanize
def human_attribute_name(attribute_key_name) #:nodoc:
attribute_key_name.humanize
end
def descends_from_active_record? # :nodoc:
superclass == Base || !columns_hash.include?(inheritance_column)
end
def quote(object) #:nodoc:
connection.quote(object)
end
# Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>.
def sanitize(object) #:nodoc:
connection.quote(object)
end
# Log and benchmark multiple statements in a single block. Example:
#
# Project.benchmark("Creating project") do
# project = Project.create("name" => "stuff")
# project.create_manager("name" => "David")
# project.milestones << Milestone.find(:all)
# end
#
# The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
# easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
# will only be conducted if the log level is low enough.
#
# The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
if logger && logger.level == log_level
result = nil
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
result
else
yield
end
end
# Silences the logger for the duration of the block.
def silence
old_logger_level, logger.level = logger.level, Logger::ERROR if logger
yield
ensure
logger.level = old_logger_level if logger
end
# Add constraints to all queries to the same model in the given block.
# Currently supported constraints are <tt>:conditions</tt> and <tt>:joins</tt>
#
# Article.constrain(:conditions => "blog_id = 1") do
# Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
# end
def constrain(options = {}, &block)
begin
self.scope_constraints = options
block.call if block_given?
ensure
self.scope_constraints = nil
end
end
# Overwrite the default class equality method to provide support for association proxies.
def ===(object)
object.is_a?(self)
end
# Deprecated
def threaded_connections
allow_concurrency
end
# Deprecated
def threaded_connections=(value)
self.allow_concurrency = value
end
private
# Finder methods must instantiate through this method to work with the single-table inheritance model
# that makes it possible to create objects of different types from the same table.
def instantiate(record)
object =
if subclass_name = record[inheritance_column]
if subclass_name.empty?
allocate
else
require_association_class(subclass_name)
begin
compute_type(subclass_name).allocate
rescue NameError
raise SubclassNotFound,
"The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
"or overwrite #{self.to_s}.inheritance_column to use another column for that information."
end
end
else
allocate
end
object.instance_variable_set("@attributes", record)
object
end
# Returns the name of the type of the record using the current module as a prefix. So descendents of
# MyApp::Business::Account would appear as "MyApp::Business::AccountSubclass".
def type_name_with_module(type_name)
self.name =~ /::/ ? self.name.scan(/(.*)::/).first.first + "::" + type_name : type_name
end
def construct_finder_sql(options)
sql = "SELECT #{options[:select] || '*'} FROM #{table_name} "
add_joins!(sql, options)
add_conditions!(sql, options[:conditions])
sql << " ORDER BY #{options[:order]} " if options[:order]
add_limit!(sql, options)
sql
end
def add_limit!(sql, options)
connection.add_limit_offset!(sql, options)
end
def add_joins!(sql, options)
join = scope_constraints[:joins] || options[:joins]
sql << " #{join} " if join
end
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
def add_conditions!(sql, conditions)
condition_segments = [scope_constraints[:conditions]]
condition_segments << sanitize_sql(conditions) unless conditions.nil?
condition_segments << type_condition unless descends_from_active_record?
condition_segments.compact!
sql << "WHERE #{condition_segments.join(" AND ")} " unless condition_segments.empty?
end
def type_condition
type_condition = subclasses.inject("#{table_name}.#{inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
condition << "OR #{table_name}.#{inheritance_column} = '#{subclass.name.demodulize}' "
end
" (#{type_condition}) "
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
def undecorated_table_name(class_name = class_name_of_active_record_descendant(self))
table_name = Inflector.underscore(Inflector.demodulize(class_name))
table_name = Inflector.pluralize(table_name) if pluralize_table_names
table_name
end
# Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
# find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
# respectively. Also works for find(:all), but using find_all_by_amount(50) that are turned into find(:all, :conditions => ["amount = ?", 50]).
#
# It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
# is actually find_all_by_amount(amount, options).
def method_missing(method_id, *arguments)
method_name = method_id.id2name
if md = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
finder = md.captures.first == 'all_by' ? :all : :first
attributes = md.captures.last.split('_and_')
attributes.each { |attr_name| super unless column_methods_hash.include?(attr_name.to_sym) }
attr_index = -1
conditions = attributes.collect { |attr_name| attr_index += 1; "#{table_name}.#{attr_name} #{attribute_condition(arguments[attr_index])} " }.join(" AND ")
if arguments[attributes.length].is_a?(Hash)
find(finder, { :conditions => [conditions, *arguments[0...attributes.length]] }.update(arguments[attributes.length]))
else
# deprecated API
send("find_#{finder}", [conditions, *arguments[0...attributes.length]], *arguments[attributes.length..-1])
end
else
super
end
end
def attribute_condition(argument)
case argument
when nil then "IS ?"
when Array then "IN (?)"
else "= ?"
end
end
# Defines an "attribute" method (like #inheritance_column or
# #table_name). A new (class) method will be created with the
# given name. If a value is specified, the new method will
# return that value (as a string). Otherwise, the given block
# will be used to compute the value of the method.
#
# The original method will be aliased, with the new name being
# prefixed with "original_". This allows the new method to
# access the original value.
#
# Example:
#
# class A < ActiveRecord::Base
# define_attr_method :primary_key, "sysid"
# define_attr_method( :inheritance_column ) do
# original_inheritance_column + "_id"
# end
# end
def define_attr_method(name, value=nil, &block)
sing = class << self; self; end
block = proc { value.to_s } if value
sing.send( :alias_method, "original_#{name}", name )
sing.send( :define_method, name, &block )
end
protected
def subclasses
@@subclasses[self] ||= []
@@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
end
def scope_constraints
if allow_concurrency
Thread.current[:constraints] ||= {}
Thread.current[:constraints][self] ||= {}
else
@scope_constraints ||= {}
end
end
# backwards compatibility
alias_method :scope_constrains, :scope_constraints
def scope_constraints=(value)
if allow_concurrency
Thread.current[:constraints] ||= {}
Thread.current[:constraints][self] = value
else
@scope_constraints = value
end
end
# backwards compatibility
alias_method :scope_constrains=, :scope_constraints=
# Returns the class type of the record using the current module as a prefix. So descendents of
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
def compute_type(type_name)
type_name_with_module(type_name).split("::").inject(Object) do |final_type, part|
final_type.const_get(part)
end
end
# Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
def class_name_of_active_record_descendant(klass)
if klass.superclass == Base
klass.name
elsif klass.superclass.nil?
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
else
class_name_of_active_record_descendant(klass.superclass)
end
end
# Accepts an array or string. The string is returned untouched, but the array has each value
# sanitized and interpolated into the sql statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
def sanitize_sql(ary)
return ary unless ary.is_a?(Array)