-
Notifications
You must be signed in to change notification settings - Fork 21.6k
/
base.rb
2170 lines (1956 loc) · 88.2 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
begin
require 'psych'
rescue LoadError
end
require 'yaml'
require 'set'
require 'active_support/benchmarkable'
require 'active_support/dependencies'
require 'active_support/descendants_tracker'
require 'active_support/time'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/blank'
require 'arel'
require 'active_record/errors'
require 'active_record/log_subscriber'
module ActiveRecord #:nodoc:
# = Active Record
#
# 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/activerecord/README_rdoc.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 an
# 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, array, or hash 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. The hash form works much like the array form, except
# only equality and range is possible. Examples:
#
# class User < ActiveRecord::Base
# def self.authenticate_unsafely(user_name, password)
# where("user_name = '#{user_name}' AND password = '#{password}'").first
# end
#
# def self.authenticate_safely(user_name, password)
# where("user_name = ? AND password = ?", user_name, password).first
# end
#
# def self.authenticate_safely_simply(user_name, password)
# where(:user_name => user_name, :password => password).first
# 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 an HTTP request. The <tt>authenticate_safely</tt> and
# <tt>authenticate_safely_simply</tt> both 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.where(
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
# ).first
#
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
# operator. For instance:
#
# Student.where(:first_name => "Harvey", :status => 1)
# Student.where(params[:student])
#
# A range may be used in the hash to use the SQL BETWEEN operator:
#
# Student.where(:grade => 9..12)
#
# An array may be used in the hash to use the SQL IN operator:
#
# Student.where(:grade => [9,11,12])
#
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
# can be used to qualify the table name of a particular condition. For instance:
#
# Student.joins(:schools).where(:schools => { :type => 'public' })
# Student.joins(:schools).where('schools.type' => 'public' )
#
# == Overwriting default accessors
#
# All column values are automatically available through basic accessors on the Active Record
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
# the default accessors (using the same name as the attribute) and calling
# <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
# change things.
#
# class Song < ActiveRecord::Base
# # Uses an integer of seconds to hold the length of the song
#
# def length=(minutes)
# write_attribute(:length, minutes.to_i * 60)
# end
#
# def length
# read_attribute(:length) / 60
# end
# end
#
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
# instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
#
# == Attribute query methods
#
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
# Query methods allow you to test whether an attribute value is present.
#
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
# to determine whether the user has a name:
#
# user = User.new(:name => "David")
# user.name? # => true
#
# anonymous = User.new(:name => "")
# anonymous.name? # => false
#
# == 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 <tt><attribute>_before_type_cast</tt>
# accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
# you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
#
# 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 (and/or creating) objects
# by simple queries without turning to SQL. They work by appending the name of an attribute
# to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders
# like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
# <tt>Payment.find_by_transaction_id</tt>. Instead of writing
# <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
# And instead of writing <tt>Person.where(:last_name => last_name).all</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_".
#
# Person.where(:user_name => user_name, :password => password).first
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
#
# It's even possible to call these dynamic finder methods on relations and named scopes.
#
# Payment.order("created_on").find_all_by_amount(50)
# Payment.pending.find_last_by_amount(100)
#
# The same dynamic finder style can be used to create the object if it doesn't already exist.
# This dynamic finder is called with <tt>find_or_create_by_</tt> and will return the object if
# it already exists and otherwise creates it, then returns it. Protected attributes won't be set
# unless they are given in a block.
#
# # No 'Summer' tag exists
# Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
#
# # Now the 'Summer' tag does exist
# Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
#
# # Now 'Bob' exist and is an 'admin'
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
#
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
# saving it first. Protected attributes won't be set unless they are given in a block.
#
# # No 'Winter' tag exists
# winter = Tag.find_or_initialize_by_name("Winter")
# winter.persisted? # false
#
# To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
# a list of parameters.
#
# Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
#
# That will either find an existing tag named "rails", or create a new one while setting the
# user that created it.
#
# Just like <tt>find_by_*</tt>, you can also use <tt>scoped_by_*</tt> to retrieve data. The good thing about
# using this feature is that the very first time result is returned using <tt>method_missing</tt> technique
# but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit.
#
# User.scoped_by_user_name('David')
#
# == 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.
#
# 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 descendant of a class not in the hierarchy.
#
# class User < ActiveRecord::Base
# serialize :preferences, Hash
# end
#
# user = User.create(:preferences => %w( one two three ))
# User.find(user.id).preferences # raises SerializationTypeMismatch
#
# When you specify a class option, the default value for that attribute will be a new
# instance of that class.
#
# class User < ActiveRecord::Base
# serialize :preferences, OpenStruct
# end
#
# user = User.new
# user.preferences.theme_color = "red"
#
#
# == Single table inheritance
#
# Active Record allows inheritance by storing the name of the class in a column that by
# default is named "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 <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
# the companies table with type = "Firm". You can then fetch this row again using
# <tt>Company.where(:name => '37signals').first</tt> 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 an
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
# and Course and all of 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 an
# <tt>:adapter</tt> key.
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
# 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 serialized object 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. Some +find+ calls do not raise this exception to signal
# nothing was found, please check its documentation for further details.
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
# <tt>attributes=</tt> 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
# <tt>attributes=</tt> 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 <tt>Base.logger=</tt> which will then be used by all
# instances in the current object space.
class Base
##
# :singleton-method:
# 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, :instance_writer => false
##
# :singleton-method:
# Contains the database configuration - as is typically stored in config/database.yml -
# as a Hash.
#
# For example, the following database.yml...
#
# development:
# adapter: sqlite3
# database: db/development.sqlite3
#
# production:
# adapter: sqlite3
# database: db/production.sqlite3
#
# ...would result in ActiveRecord::Base.configurations to look like this:
#
# {
# 'development' => {
# 'adapter' => 'sqlite3',
# 'database' => 'db/development.sqlite3'
# },
# 'production' => {
# 'adapter' => 'sqlite3',
# 'database' => 'db/production.sqlite3'
# }
# }
cattr_accessor :configurations, :instance_writer => false
@@configurations = {}
##
# :singleton-method:
# 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, :instance_writer => false
@@primary_key_prefix_type = nil
##
# :singleton-method:
# 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.
#
# If you are organising your models within modules you can add a prefix to the models within
# a namespace by defining a singleton method in the parent module called table_name_prefix which
# returns your chosen prefix.
class_attribute :table_name_prefix, :instance_writer => false
self.table_name_prefix = ""
##
# :singleton-method:
# 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.
class_attribute :table_name_suffix, :instance_writer => false
self.table_name_suffix = ""
##
# :singleton-method:
# Indicates whether 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.
class_attribute :pluralize_table_names, :instance_writer => false
self.pluralize_table_names = true
##
# :singleton-method:
# 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, :instance_writer => false
@@default_timezone = :local
##
# :singleton-method:
# 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 , :instance_writer => false
@@schema_format = :ruby
##
# :singleton-method:
# Specify whether or not to use timestamps for migration versions
cattr_accessor :timestamped_migrations , :instance_writer => false
@@timestamped_migrations = true
# Determine whether to store the full constant name including namespace when using STI
class_attribute :store_full_sti_class
self.store_full_sti_class = true
# Stores the default scope for the class
class_attribute :default_scopes, :instance_writer => false
self.default_scopes = []
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
class_attribute :serialized_attributes
self.serialized_attributes = {}
class_attribute :_attr_readonly, :instance_writer => false
self._attr_readonly = []
class << self # Class methods
delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
# a Product object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
# table.
#
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
# no database agnostic conversions performed. This should be a last resort because using, for example,
# MySQL specific terms will lock you to using that particular database engine or require you to
# change your call if you switch engines.
#
# ==== Examples
# # A simple SQL query spanning multiple tables
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
# > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
#
# # You can use the same string replacement techniques as you can with ActiveRecord#find
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
# > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
def find_by_sql(sql, binds = [])
connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
end
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
# The resulting object is returned whether the object was saved successfully to the database or not.
#
# The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
# attributes on the objects that are to be created.
#
# +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
# in the +options+ parameter.
#
# ==== Examples
# # Create a single new object
# User.create(:first_name => 'Jamie')
#
# # Create a single new object using the :admin mass-assignment security role
# User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
#
# # Create a single new object bypassing mass-assignment security
# User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
#
# # Create an Array of new objects
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
#
# # Create a single object and pass it into a block to set other attributes.
# User.create(:first_name => 'Jamie') do |u|
# u.is_admin = false
# end
#
# # Creating an Array of new objects using a block, where the block is executed for each object:
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
# u.is_admin = false
# end
def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
object = new(attributes, options)
yield(object) if block_given?
object.save
object
end
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
# The use of this method should be restricted to complicated SQL queries that can't be executed
# using the ActiveRecord::Calculations class methods. Look into those before using this.
#
# ==== Parameters
#
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
#
# ==== Examples
#
# 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
# Attributes listed as readonly will be used to create a new record but update operations will
# ignore these fields.
def attr_readonly(*attributes)
self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
end
# Returns an array of all the attributes that have been specified as readonly.
def readonly_attributes
self._attr_readonly
end
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
# then specify the name of that attribute using this method and it will be handled automatically.
# 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.
#
# ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
# * +class_name+ - Optional, class name that the object type should be equal to.
#
# ==== Example
# # Serialize a preferences attribute
# class User < ActiveRecord::Base
# serialize :preferences
# end
def serialize(attr_name, class_name = Object)
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
class_name
else
Coders::YAMLColumn.new(class_name)
end
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
# has its own hash of own serialized attributes
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
end
# Guesses the table name (in forced lower-case) based on the name of the class in the
# inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
# looks like: Reply < Message < ActiveRecord::Base, then Message is used
# to guess the table name 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. You can add new inflections in config/initializers/inflections.rb.
#
# Nested classes are given table names prefixed by the singular form of
# the parent's table name. Enclosing modules are not considered.
#
# ==== Examples
#
# class Invoice < ActiveRecord::Base
# end
#
# file class table_name
# invoice.rb Invoice invoices
#
# class Invoice < ActiveRecord::Base
# class Lineitem < ActiveRecord::Base
# end
# end
#
# file class table_name
# invoice.rb Invoice::Lineitem invoice_lineitems
#
# module Invoice
# class Lineitem < ActiveRecord::Base
# end
# end
#
# file class table_name
# invoice/lineitem.rb Invoice::Lineitem lineitems
#
# Additionally, the class-level +table_name_prefix+ is prepended and the
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
# the table name guess for an Invoice class becomes "myapp_invoices".
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
#
# 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
# Returns a quoted version of the table name, used to construct SQL statements.
def quoted_table_name
@quoted_table_name ||= connection.quote_table_name(table_name)
end
# Computes the table name, (re)sets it internally, and returns it.
def reset_table_name #:nodoc:
self.table_name = compute_table_name
end
def full_table_name_prefix #:nodoc:
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
# Defines the column name for use with single table inheritance. Use
# <tt>set_inheritance_column</tt> to set a different value.
def inheritance_column
@inheritance_column ||= "type"
end
# Lazy-set the sequence name to the connection's default. This method
# is only ever called once since set_sequence_name overrides it.
def sequence_name #:nodoc:
reset_sequence_name
end
def reset_sequence_name #:nodoc:
default = connection.default_sequence_name(table_name, primary_key)
set_sequence_name(default)
default
end
# Sets the table name. If the value is nil or false then the value returned by the given
# block is used.
#
# class Project < ActiveRecord::Base
# set_table_name "project"
# end
def set_table_name(value = nil, &block)
@quoted_table_name = nil
define_attr_method :table_name, value, &block
@arel_table = nil
@relation = Relation.new(self, arel_table)
end
alias :table_name= :set_table_name
# 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.
#
# 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. This is required for Oracle and is useful for any
# database which relies on sequences for primary key generation.
#
# If a sequence name is not explicitly set when using Oracle or Firebird,
# it will default to the commonly used pattern of: #{table_name}_seq
#
# If a sequence name is not explicitly set when using PostgreSQL, it
# will discover the sequence corresponding to your primary key for you.
#
# 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
# Indicates whether the table associated with this class exists
def table_exists?
connection.table_exists?(table_name)
end
# Returns an array of column objects for the table associated with this class.
def columns
connection_pool.columns[table_name]
end
# Returns a hash of column objects for the table associated with this class.
def columns_hash
connection_pool.columns_hash[table_name]
end
# Returns a hash where the keys are column names and the values are
# default values when instantiating the AR object for this table.
def column_defaults
connection_pool.column_defaults[table_name]
end
# Returns an array of column names as strings.
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 #:nodoc:
@dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
attr_name = attr.to_s
methods[attr.to_sym] = attr_name
methods["#{attr}=".to_sym] = attr_name
methods["#{attr}?".to_sym] = attr_name
methods["#{attr}_before_type_cast".to_sym] = attr_name
methods
end
end
# Resets all the cached information about columns, which will cause them
# to be reloaded on the next request.
#
# The most common usage pattern for this method is probably in a migration,
# when just after creating a table you want to populate it with some default
# values, eg:
#
# class CreateJobLevels < ActiveRecord::Migration
# def self.up
# create_table :job_levels do |t|
# t.integer :id
# t.string :name
#
# t.timestamps
# end
#
# JobLevel.reset_column_information
# %w{assistant executive manager director}.each do |type|
# JobLevel.create(:name => type)
# end
# end
#
# def self.down
# drop_table :job_levels
# end
# end
def reset_column_information
connection.clear_cache!
undefine_attribute_methods
connection_pool.clear_table_cache!(table_name) if table_exists?
@column_names = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
@arel_engine = @relation = nil
end
def clear_cache! # :nodoc:
connection_pool.clear_cache!
end
def attribute_method?(attribute)
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
end
# Returns an array of column names as strings if it's not
# an abstract class and table exists.
# Otherwise it returns an empty array.
def attribute_names
@attribute_names ||= if !abstract_class? && table_exists?
column_names
else
[]
end
end
# Set the lookup ancestors for ActiveModel.
def lookup_ancestors #:nodoc:
klass = self
classes = [klass]
return classes if klass == ActiveRecord::Base
while klass != klass.base_class
classes << klass = klass.superclass
end
classes
end
# Set the i18n scope to overwrite ActiveModel.
def i18n_scope #:nodoc:
:activerecord
end
# True if this isn't a concrete subclass needing a STI type condition.
def descends_from_active_record?
if superclass.abstract_class?
superclass.descends_from_active_record?
else
superclass == Base || !columns_hash.include?(inheritance_column)
end
end
def finder_needs_type_condition? #:nodoc:
# This is like this because benchmarking justifies the strange :false stuff
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
end
# Returns a string like 'Post(id:integer, title:string, body:text)'
def inspect
if self == Base
super
elsif abstract_class?
"#{super}(abstract)"
elsif table_exists?
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
"#{super}(#{attr_list})"
else
"#{super}(Table doesn't exist)"
end
end
def quote_value(value, column = nil) #:nodoc:
connection.quote(value,column)
end
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
def sanitize(object) #:nodoc:
connection.quote(object)
end
# Overwrite the default class equality method to provide support for association proxies.
def ===(object)
object.is_a?(self)
end
def symbolized_base_class
@symbolized_base_class ||= base_class.to_s.to_sym
end
def symbolized_sti_name
@symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
end
# Returns the base AR subclass that this class descends from. If A
# extends AR::Base, A.base_class will return A. If B descends from A
# through some arbitrarily deep hierarchy, B.base_class will return A.
#
# If B < A and C < B and if A is an abstract_class then both B.base_class
# and C.base_class would return B as the answer since A is an abstract_class.
def base_class
class_of_active_record_descendant(self)
end
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
attr_accessor :abstract_class
# Returns whether this class is an abstract class or not.
def abstract_class?
defined?(@abstract_class) && @abstract_class == true
end
def respond_to?(method_id, include_private = false)
if match = DynamicFinderMatch.match(method_id)
return true if all_attributes_exists?(match.attribute_names)
elsif match = DynamicScopeMatch.match(method_id)
return true if all_attributes_exists?(match.attribute_names)
end
super
end
def sti_name
store_full_sti_class ? name : name.demodulize
end
def arel_table
@arel_table ||= Arel::Table.new(table_name, arel_engine)
end
def arel_engine
@arel_engine ||= begin
if self == ActiveRecord::Base
ActiveRecord::Base
else
connection_handler.connection_pools[name] ? self : superclass.arel_engine
end
end
end
# Returns a scope for this class without taking into account the default_scope.
#
# class Post < ActiveRecord::Base
# def self.default_scope
# where :published => true
# end
# end
#
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
# Post.unscoped.all # Fires "SELECT * FROM posts"
#
# This method also accepts a block meaning that all queries inside the block will
# not use the default_scope:
#
# Post.unscoped {
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
#
# It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
# does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
#
# Post.unscoped.published
# Post.published
def unscoped #:nodoc:
block_given? ? relation.scoping { yield } : relation
end
def before_remove_const #:nodoc:
self.current_scope = nil
end
# Specifies how the record is loaded by +Marshal+.
#
# +_load+ sets an instance variable for each key in the hash it takes as input.
# Override this method if you require more complex marshalling.
def _load(data)
record = allocate
record.init_with(Marshal.load(data))
record
end
# 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)
sti_class = find_sti_class(record[inheritance_column])
record_id = sti_class.primary_key && record[sti_class.primary_key]
if ActiveRecord::IdentityMap.enabled? && record_id
if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number?
record_id = record_id.to_i
end
if instance = IdentityMap.get(sti_class, record_id)
instance.reinit_with('attributes' => record)
else
instance = sti_class.allocate.init_with('attributes' => record)
IdentityMap.add(instance)
end
else
instance = sti_class.allocate.init_with('attributes' => record)
end
instance
end
private
def relation #:nodoc:
@relation ||= Relation.new(self, arel_table)
if finder_needs_type_condition?
@relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
else
@relation
end
end
def find_sti_class(type_name)
if type_name.blank? || !columns_hash.include?(inheritance_column)
self
else
begin
if store_full_sti_class
ActiveSupport::Dependencies.constantize(type_name)
else
compute_type(type_name)
end
rescue NameError
raise SubclassNotFound,
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +