activegroonga 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/AUTHORS +1 -0
  2. data/NEWS.ja.rdoc +5 -0
  3. data/NEWS.rdoc +5 -0
  4. data/README.ja.rdoc +49 -0
  5. data/README.rdoc +49 -0
  6. data/Rakefile +175 -0
  7. data/lib/active_groonga.rb +75 -0
  8. data/lib/active_groonga/aggregations.rb +30 -0
  9. data/lib/active_groonga/associations.rb +93 -0
  10. data/lib/active_groonga/associations/belongs_to_association.rb +25 -0
  11. data/lib/active_groonga/attribute_methods.rb +36 -0
  12. data/lib/active_groonga/base.rb +1579 -0
  13. data/lib/active_groonga/column.rb +107 -0
  14. data/lib/active_groonga/dirty.rb +30 -0
  15. data/lib/active_groonga/fixtures.rb +92 -0
  16. data/lib/active_groonga/migration.rb +150 -0
  17. data/lib/active_groonga/rails_support.rb +31 -0
  18. data/lib/active_groonga/reflection.rb +30 -0
  19. data/lib/active_groonga/schema.rb +314 -0
  20. data/lib/active_groonga/schema_dumper.rb +147 -0
  21. data/lib/active_groonga/tasks.rb +16 -0
  22. data/lib/active_groonga/tasks/groonga.rake +162 -0
  23. data/lib/active_groonga/test_case.rb +21 -0
  24. data/lib/active_groonga/test_help.rb +21 -0
  25. data/lib/active_groonga/timestamp.rb +30 -0
  26. data/lib/active_groonga/validations.rb +26 -0
  27. data/lib/active_groonga/version.rb +24 -0
  28. data/license/LGPL +504 -0
  29. data/rails/README +28 -0
  30. data/rails/init.rb +70 -0
  31. data/rails_generators/model_groonga/USAGE +28 -0
  32. data/rails_generators/model_groonga/model_groonga_generator.rb +45 -0
  33. data/rails_generators/model_groonga/templates/fixtures.yml +17 -0
  34. data/rails_generators/model_groonga/templates/migration.rb +16 -0
  35. data/rails_generators/model_groonga/templates/model.rb +2 -0
  36. data/rails_generators/model_groonga/templates/unit_test.rb +8 -0
  37. data/test-unit/Rakefile +35 -0
  38. data/test-unit/TODO +5 -0
  39. data/test-unit/bin/testrb +5 -0
  40. data/test-unit/html/classic.html +15 -0
  41. data/test-unit/html/index.html +25 -0
  42. data/test-unit/html/index.html.ja +27 -0
  43. data/test-unit/lib/test/unit.rb +342 -0
  44. data/test-unit/lib/test/unit/assertionfailederror.rb +14 -0
  45. data/test-unit/lib/test/unit/assertions.rb +1149 -0
  46. data/test-unit/lib/test/unit/attribute.rb +125 -0
  47. data/test-unit/lib/test/unit/autorunner.rb +306 -0
  48. data/test-unit/lib/test/unit/collector.rb +43 -0
  49. data/test-unit/lib/test/unit/collector/descendant.rb +23 -0
  50. data/test-unit/lib/test/unit/collector/dir.rb +108 -0
  51. data/test-unit/lib/test/unit/collector/load.rb +135 -0
  52. data/test-unit/lib/test/unit/collector/objectspace.rb +34 -0
  53. data/test-unit/lib/test/unit/color-scheme.rb +86 -0
  54. data/test-unit/lib/test/unit/color.rb +96 -0
  55. data/test-unit/lib/test/unit/diff.rb +538 -0
  56. data/test-unit/lib/test/unit/error.rb +124 -0
  57. data/test-unit/lib/test/unit/exceptionhandler.rb +39 -0
  58. data/test-unit/lib/test/unit/failure.rb +110 -0
  59. data/test-unit/lib/test/unit/fixture.rb +176 -0
  60. data/test-unit/lib/test/unit/notification.rb +125 -0
  61. data/test-unit/lib/test/unit/omission.rb +143 -0
  62. data/test-unit/lib/test/unit/pending.rb +146 -0
  63. data/test-unit/lib/test/unit/priority.rb +161 -0
  64. data/test-unit/lib/test/unit/runner/console.rb +52 -0
  65. data/test-unit/lib/test/unit/runner/emacs.rb +8 -0
  66. data/test-unit/lib/test/unit/testcase.rb +360 -0
  67. data/test-unit/lib/test/unit/testresult.rb +89 -0
  68. data/test-unit/lib/test/unit/testsuite.rb +110 -0
  69. data/test-unit/lib/test/unit/ui/console/outputlevel.rb +14 -0
  70. data/test-unit/lib/test/unit/ui/console/testrunner.rb +220 -0
  71. data/test-unit/lib/test/unit/ui/emacs/testrunner.rb +49 -0
  72. data/test-unit/lib/test/unit/ui/testrunner.rb +20 -0
  73. data/test-unit/lib/test/unit/ui/testrunnermediator.rb +77 -0
  74. data/test-unit/lib/test/unit/ui/testrunnerutilities.rb +41 -0
  75. data/test-unit/lib/test/unit/util/backtracefilter.rb +41 -0
  76. data/test-unit/lib/test/unit/util/method-owner-finder.rb +28 -0
  77. data/test-unit/lib/test/unit/util/observable.rb +90 -0
  78. data/test-unit/lib/test/unit/util/procwrapper.rb +48 -0
  79. data/test-unit/lib/test/unit/version.rb +7 -0
  80. data/test-unit/sample/adder.rb +13 -0
  81. data/test-unit/sample/subtracter.rb +12 -0
  82. data/test-unit/sample/tc_adder.rb +18 -0
  83. data/test-unit/sample/tc_subtracter.rb +18 -0
  84. data/test-unit/sample/test_user.rb +22 -0
  85. data/test-unit/sample/ts_examples.rb +7 -0
  86. data/test-unit/test/collector/test-descendant.rb +135 -0
  87. data/test-unit/test/collector/test-load.rb +333 -0
  88. data/test-unit/test/collector/test_dir.rb +406 -0
  89. data/test-unit/test/collector/test_objectspace.rb +98 -0
  90. data/test-unit/test/run-test.rb +13 -0
  91. data/test-unit/test/test-attribute.rb +86 -0
  92. data/test-unit/test/test-color-scheme.rb +56 -0
  93. data/test-unit/test/test-color.rb +47 -0
  94. data/test-unit/test/test-diff.rb +477 -0
  95. data/test-unit/test/test-emacs-runner.rb +60 -0
  96. data/test-unit/test/test-fixture.rb +287 -0
  97. data/test-unit/test/test-notification.rb +33 -0
  98. data/test-unit/test/test-omission.rb +81 -0
  99. data/test-unit/test/test-pending.rb +70 -0
  100. data/test-unit/test/test-priority.rb +119 -0
  101. data/test-unit/test/test_assertions.rb +1082 -0
  102. data/test-unit/test/test_error.rb +26 -0
  103. data/test-unit/test/test_failure.rb +33 -0
  104. data/test-unit/test/test_testcase.rb +478 -0
  105. data/test-unit/test/test_testresult.rb +113 -0
  106. data/test-unit/test/test_testsuite.rb +129 -0
  107. data/test-unit/test/testunit-test-util.rb +14 -0
  108. data/test-unit/test/ui/test_testrunmediator.rb +20 -0
  109. data/test-unit/test/util/test-method-owner-finder.rb +38 -0
  110. data/test-unit/test/util/test_backtracefilter.rb +41 -0
  111. data/test-unit/test/util/test_observable.rb +102 -0
  112. data/test-unit/test/util/test_procwrapper.rb +36 -0
  113. data/test/active-groonga-test-utils.rb +234 -0
  114. data/test/fixtures/bookmark.rb +2 -0
  115. data/test/fixtures/task.rb +2 -0
  116. data/test/fixtures/user.rb +2 -0
  117. data/test/run-test.rb +51 -0
  118. data/test/test-associations.rb +24 -0
  119. data/test/test-base.rb +194 -0
  120. data/test/test-schema.rb +49 -0
  121. metadata +192 -0
@@ -0,0 +1,25 @@
1
+ # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ module ActiveGroonga
17
+ module Associations
18
+ class BelongsToAssociation < ActiveRecord::Associations::BelongsToAssociation
19
+ def find_target
20
+ @reflection.klass.find(@owner.id,
21
+ :readonly => @reflection.options[:readonly])
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ module ActiveGroonga
17
+ module AttributeMethods
18
+ def self.included(base)
19
+ base.module_eval do
20
+ include ActiveRecord::AttributeMethods
21
+ extend AttributeMethods::ClassMethods
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def instance_method_already_implemented?(method_name)
27
+ method_name = method_name.to_s
28
+ return true if method_name =~ /^id(=$|\?$|$)/
29
+ @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveGroonga::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map(&:to_s).to_set
30
+ @@_defined_activegroonga_methods ||= (ActiveGroonga::Base.public_instance_methods(false) | ActiveGroonga::Base.private_instance_methods(false) | ActiveGroonga::Base.protected_instance_methods(false)).map(&:to_s).to_set
31
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveGroonga" if @@_defined_activegroonga_methods.include?(method_name)
32
+ @_defined_class_methods.include?(method_name)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,1579 @@
1
+ # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ # This library includes ActiveRecord based codes temporary.
17
+ # Here is their copyright and license:
18
+ #
19
+ # Copyright (c) 2004-2009 David Heinemeier Hansson
20
+ #
21
+ # Permission is hereby granted, free of charge, to any person obtaining
22
+ # a copy of this software and associated documentation files (the
23
+ # "Software"), to deal in the Software without restriction, including
24
+ # without limitation the rights to use, copy, modify, merge, publish,
25
+ # distribute, sublicense, and/or sell copies of the Software, and to
26
+ # permit persons to whom the Software is furnished to do so, subject to
27
+ # the following conditions:
28
+ #
29
+ # The above copyright notice and this permission notice shall be
30
+ # included in all copies or substantial portions of the Software.
31
+ #
32
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
33
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
35
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
36
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
37
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
38
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39
+
40
+ require 'active_record/base'
41
+
42
+ module ActiveGroonga
43
+ # Generic ActiveGroonga exception class.
44
+ class ActiveGroongaError < StandardError
45
+ end
46
+
47
+ # Raised when ActiveGroonga cannot find record by given id or set of ids.
48
+ class RecordNotFound < ActiveGroongaError
49
+ end
50
+
51
+ # Raised when database not specified (or configuration file <tt>config/groonga.yml</tt> misses database field).
52
+ class DatabaseNotSpecified < ActiveGroongaError
53
+ end
54
+
55
+ class Base
56
+ ##
57
+ # :singleton-method:
58
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
59
+ # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
60
+ cattr_accessor :logger, :instance_writer => false
61
+
62
+ ##
63
+ # :singleton-method:
64
+ # Contains the groonga configuration - as is typically stored in config/groonga.yml -
65
+ # as a Hash.
66
+ #
67
+ # For example, the following groonga.yml...
68
+ #
69
+ # development:
70
+ # database: db/development.groonga
71
+ #
72
+ # production:
73
+ # adapter: groonga
74
+ # database: db/production.groonga
75
+ #
76
+ # ...would result in ActiveGroonga::Base.configurations to look like this:
77
+ #
78
+ # {
79
+ # 'development' => {
80
+ # 'database' => 'db/development.groonga'
81
+ # },
82
+ # 'production' => {
83
+ # 'database' => 'db/production.groonga'
84
+ # }
85
+ # }
86
+ cattr_accessor :configurations, :instance_writer => false
87
+ @@configurations = {}
88
+
89
+ ##
90
+ # :singleton-method:
91
+ # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
92
+ # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
93
+ # for tables in a shared database. By default, the prefix is the empty string.
94
+ cattr_accessor :table_name_prefix, :instance_writer => false
95
+ @@table_name_prefix = ""
96
+
97
+ ##
98
+ # :singleton-method:
99
+ # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
100
+ # "people_basecamp"). By default, the suffix is the empty string.
101
+ cattr_accessor :table_name_suffix, :instance_writer => false
102
+ @@table_name_suffix = ""
103
+
104
+ ##
105
+ # :singleton-method:
106
+ # Indicates whether table names should be the pluralized versions of the corresponding class names.
107
+ # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
108
+ # See table_name for the full rules on table/class naming. This is true, by default.
109
+ cattr_accessor :pluralize_table_names, :instance_writer => false
110
+ @@pluralize_table_names = true
111
+
112
+ ##
113
+ # :singleton-method:
114
+ # Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
115
+ # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
116
+ # may complicate matters if you use software like syslog. This is true, by default.
117
+ cattr_accessor :colorize_logging, :instance_writer => false
118
+ @@colorize_logging = true
119
+
120
+ ##
121
+ # :singleton-method:
122
+ # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
123
+ # This is set to :local by default.
124
+ cattr_accessor :default_timezone, :instance_writer => false
125
+ @@default_timezone = :local
126
+
127
+ # Determine whether to store the full constant name including namespace when using STI
128
+ superclass_delegating_accessor :store_full_sti_class
129
+ self.store_full_sti_class = false
130
+
131
+ # Stores the default scope for the class
132
+ class_inheritable_accessor :default_scoping, :instance_writer => false
133
+ self.default_scoping = []
134
+
135
+ ##
136
+ # :singleton-method:
137
+ # Specifies the format to use when dumping the database schema with Rails'
138
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
139
+ # specific) SQL statements. If :ruby, the schema is dumped as an
140
+ # ActiveRecord::Schema file which can be loaded into any database that
141
+ # supports migrations. Use :ruby if you want to have different database
142
+ # adapters for, e.g., your development and test environments.
143
+ cattr_accessor :schema_format , :instance_writer => false
144
+ @@schema_format = :ruby
145
+
146
+ cattr_accessor :database_directory, :instance_writer => false
147
+ @@database_directory = nil
148
+
149
+ class << self
150
+ # Creates an object (or multiple objects) and saves it to the database, if validations pass.
151
+ # The resulting object is returned whether the object was saved successfully to the database or not.
152
+ #
153
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
154
+ # attributes on the objects that are to be created.
155
+ #
156
+ # ==== Examples
157
+ # # Create a single new object
158
+ # User.create(:first_name => 'Jamie')
159
+ #
160
+ # # Create an Array of new objects
161
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
162
+ #
163
+ # # Create a single object and pass it into a block to set other attributes.
164
+ # User.create(:first_name => 'Jamie') do |u|
165
+ # u.is_admin = false
166
+ # end
167
+ #
168
+ # # Creating an Array of new objects using a block, where the block is executed for each object:
169
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
170
+ # u.is_admin = false
171
+ # end
172
+ def create(attributes = nil, &block)
173
+ if attributes.is_a?(Array)
174
+ attributes.collect { |attr| create(attr, &block) }
175
+ else
176
+ object = new(attributes)
177
+ yield(object) if block_given?
178
+ object.save
179
+ object
180
+ end
181
+ end
182
+
183
+ # Attributes named in this macro are protected from mass-assignment,
184
+ # such as <tt>new(attributes)</tt>,
185
+ # <tt>update_attributes(attributes)</tt>, or
186
+ # <tt>attributes=(attributes)</tt>.
187
+ #
188
+ # Mass-assignment to these attributes will simply be ignored, to assign
189
+ # to them you can use direct writer methods. This is meant to protect
190
+ # sensitive attributes from being overwritten by malicious users
191
+ # tampering with URLs or forms.
192
+ #
193
+ # class Customer < ActiveRecord::Base
194
+ # attr_protected :credit_rating
195
+ # end
196
+ #
197
+ # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
198
+ # customer.credit_rating # => nil
199
+ # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
200
+ # customer.credit_rating # => nil
201
+ #
202
+ # customer.credit_rating = "Average"
203
+ # customer.credit_rating # => "Average"
204
+ #
205
+ # To start from an all-closed default and enable attributes as needed,
206
+ # have a look at +attr_accessible+.
207
+ def attr_protected(*attributes)
208
+ write_inheritable_attribute(:attr_protected, Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
209
+ end
210
+
211
+ # Returns an array of all the attributes that have been protected from mass-assignment.
212
+ def protected_attributes # :nodoc:
213
+ read_inheritable_attribute(:attr_protected)
214
+ end
215
+
216
+ # Specifies a white list of model attributes that can be set via
217
+ # mass-assignment, such as <tt>new(attributes)</tt>,
218
+ # <tt>update_attributes(attributes)</tt>, or
219
+ # <tt>attributes=(attributes)</tt>
220
+ #
221
+ # This is the opposite of the +attr_protected+ macro: Mass-assignment
222
+ # will only set attributes in this list, to assign to the rest of
223
+ # attributes you can use direct writer methods. This is meant to protect
224
+ # sensitive attributes from being overwritten by malicious users
225
+ # tampering with URLs or forms. If you'd rather start from an all-open
226
+ # default and restrict attributes as needed, have a look at
227
+ # +attr_protected+.
228
+ #
229
+ # class Customer < ActiveRecord::Base
230
+ # attr_accessible :name, :nickname
231
+ # end
232
+ #
233
+ # customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
234
+ # customer.credit_rating # => nil
235
+ # customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
236
+ # customer.credit_rating # => nil
237
+ #
238
+ # customer.credit_rating = "Average"
239
+ # customer.credit_rating # => "Average"
240
+ def attr_accessible(*attributes)
241
+ write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
242
+ end
243
+
244
+ # Returns an array of all the attributes that have been made accessible to mass-assignment.
245
+ def accessible_attributes # :nodoc:
246
+ read_inheritable_attribute(:attr_accessible)
247
+ end
248
+
249
+ # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
250
+ def attr_readonly(*attributes)
251
+ write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
252
+ end
253
+
254
+ # Returns an array of all the attributes that have been specified as readonly.
255
+ def readonly_attributes
256
+ read_inheritable_attribute(:attr_readonly)
257
+ end
258
+
259
+
260
+ # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
261
+ # then specify the name of that attribute using this method and it will be handled automatically.
262
+ # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
263
+ # class on retrieval or SerializationTypeMismatch will be raised.
264
+ #
265
+ # ==== Parameters
266
+ #
267
+ # * +attr_name+ - The field name that should be serialized.
268
+ # * +class_name+ - Optional, class name that the object type should be equal to.
269
+ #
270
+ # ==== Example
271
+ # # Serialize a preferences attribute
272
+ # class User
273
+ # serialize :preferences
274
+ # end
275
+ def serialize(attr_name, class_name = Object)
276
+ serialized_attributes[attr_name.to_s] = class_name
277
+ end
278
+
279
+ # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
280
+ def serialized_attributes
281
+ read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
282
+ end
283
+
284
+ # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
285
+ # directly from ActiveGroonga::Base. So if the hierarchy looks like: Reply < Message < ActiveGroonga::Base, then Message is used
286
+ # to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
287
+ # in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.
288
+ #
289
+ # Nested classes are given table names prefixed by the singular form of
290
+ # the parent's table name. Enclosing modules are not considered.
291
+ #
292
+ # ==== Examples
293
+ #
294
+ # class Invoice < ActiveGroonga::Base; end;
295
+ # file class table_name
296
+ # invoice.rb Invoice invoices
297
+ #
298
+ # class Invoice < ActiveGroonga::Base; class Lineitem < ActiveGroonga::Base; end; end;
299
+ # file class table_name
300
+ # invoice.rb Invoice::Lineitem invoice_lineitems
301
+ #
302
+ # module Invoice; class Lineitem < ActiveGroonga::Base; end; end;
303
+ # file class table_name
304
+ # invoice/lineitem.rb Invoice::Lineitem lineitems
305
+ #
306
+ # Additionally, the class-level +table_name_prefix+ is prepended and the
307
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
308
+ # the table name guess for an Invoice class becomes "myapp_invoices".
309
+ # Invoice::Lineitem becomes "myapp_invoice_lineitems".
310
+ #
311
+ # You can also overwrite this class method to allow for unguessable
312
+ # links, such as a Mouse class with a link to a "mice" table. Example:
313
+ #
314
+ # class Mouse < ActiveGroonga::Base
315
+ # set_table_name "mice"
316
+ # end
317
+ def table_name
318
+ reset_table_name
319
+ end
320
+
321
+ def reset_table_name #:nodoc:
322
+ base = base_class
323
+
324
+ name =
325
+ # STI subclasses always use their superclass' table.
326
+ unless self == base
327
+ base.table_name
328
+ else
329
+ # Nested classes are prefixed with singular parent table name.
330
+ if parent < ActiveGroonga::Base && !parent.abstract_class?
331
+ contained = parent.table_name
332
+ contained = contained.singularize if parent.pluralize_table_names
333
+ contained << '_'
334
+ end
335
+ name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
336
+ end
337
+
338
+ set_table_name(name)
339
+ name
340
+ end
341
+
342
+ # Defines the column name for use with single table inheritance
343
+ # -- can be set in subclasses like so: self.inheritance_column = "type_id"
344
+ def inheritance_column
345
+ @inheritance_column ||= "type".freeze
346
+ end
347
+
348
+ # Sets the table name to use to the given value, or (if the value
349
+ # is nil or false) to the value returned by the given block.
350
+ #
351
+ # class Project < ActiveGroonga::Base
352
+ # set_table_name "project"
353
+ # end
354
+ def set_table_name(value = nil, &block)
355
+ define_attr_method :table_name, value, &block
356
+ end
357
+ alias :table_name= :set_table_name
358
+
359
+ # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
360
+ def class_name(table_name = table_name) # :nodoc:
361
+ # remove any prefix and/or suffix from the table name
362
+ class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
363
+ class_name = class_name.singularize if pluralize_table_names
364
+ class_name
365
+ end
366
+
367
+ # Indicates whether the table associated with this class exists
368
+ def table_exists?
369
+ not table.nil?
370
+ end
371
+
372
+ def primary_key
373
+ "id"
374
+ end
375
+
376
+ # Returns an array of column objects for the table associated with this class.
377
+ def columns
378
+ @columns ||= table.columns.collect do |column|
379
+ Column.new(column)
380
+ end
381
+ end
382
+
383
+ # Returns a hash of column objects for the table associated with this class.
384
+ def columns_hash
385
+ @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
386
+ end
387
+
388
+ # Returns an array of column names as strings.
389
+ def column_names
390
+ @column_names ||= columns.map { |column| column.name }
391
+ end
392
+
393
+ # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
394
+ # and columns used for single table inheritance have been removed.
395
+ def content_columns
396
+ @content_columns ||= columns.reject do |c|
397
+ c.primary || c.type == :references || c.name == inheritance_column
398
+ end
399
+ end
400
+
401
+ # 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
402
+ # 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
403
+ # is available.
404
+ def column_methods_hash #:nodoc:
405
+ @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
406
+ attr_name = attr.to_s
407
+ methods[attr.to_sym] = attr_name
408
+ methods["#{attr}=".to_sym] = attr_name
409
+ methods["#{attr}?".to_sym] = attr_name
410
+ methods["#{attr}_before_type_cast".to_sym] = attr_name
411
+ methods
412
+ end
413
+ end
414
+
415
+ # True if this isn't a concrete subclass needing a STI type condition.
416
+ def descends_from_active_groonga?
417
+ if superclass.abstract_class?
418
+ superclass.descends_from_active_groonga?
419
+ else
420
+ superclass == Base || !columns_hash.include?(inheritance_column)
421
+ end
422
+ end
423
+
424
+ # Returns a string like 'Post id:integer, title:string, body:text'
425
+ def inspect
426
+ if self == Base
427
+ super
428
+ elsif abstract_class?
429
+ "#{super}(abstract)"
430
+ elsif table_exists?
431
+ attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
432
+ "#{super}(#{attr_list})"
433
+ else
434
+ "#{super}(Table doesn't exist)"
435
+ end
436
+ end
437
+
438
+ # Log and benchmark multiple statements in a single block. Example:
439
+ #
440
+ # Project.benchmark("Creating project") do
441
+ # project = Project.create("name" => "stuff")
442
+ # project.create_manager("name" => "David")
443
+ # project.milestones << Milestone.find(:all)
444
+ # end
445
+ #
446
+ # The benchmark is only recorded if the current level of the logger is less than or equal to the <tt>log_level</tt>,
447
+ # which makes it easy to include benchmarking statements in production software that will remain inexpensive because
448
+ # the benchmark will only be conducted if the log level is low enough.
449
+ #
450
+ # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
451
+ def benchmark(title, log_level=Logger::DEBUG, use_silence=true)
452
+ if logger && logger.level <= log_level
453
+ result = nil
454
+ ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
455
+ logger.add(log_level, '%s (%.1fms)' % [title, ms])
456
+ result
457
+ else
458
+ yield
459
+ end
460
+ end
461
+
462
+ # Overwrite the default class equality method to provide support for association proxies.
463
+ def ===(object)
464
+ object.is_a?(self)
465
+ end
466
+
467
+ # Returns the base AR subclass that this class descends from. If A
468
+ # extends AR::Base, A.base_class will return A. If B descends from A
469
+ # through some arbitrarily deep hierarchy, B.base_class will return A.
470
+ def base_class
471
+ class_of_active_groonga_descendant(self)
472
+ end
473
+
474
+ # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
475
+ attr_accessor :abstract_class
476
+
477
+ # Returns whether this class is a base AR class. If A is a base class and
478
+ # B descends from A, then B.base_class will return B.
479
+ def abstract_class?
480
+ defined?(@abstract_class) && @abstract_class == true
481
+ end
482
+
483
+ def find(*args)
484
+ options = args.extract_options!
485
+ validate_find_options(options)
486
+ set_readonly_option!(options)
487
+
488
+ case args.first
489
+ when :first
490
+ find_initial(options)
491
+ when :last
492
+ find_last(options)
493
+ when :all
494
+ find_every(options)
495
+ else
496
+ find_from_ids(args, options)
497
+ end
498
+ end
499
+
500
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
501
+ # same arguments to this method as you can to <tt>find(:first)</tt>.
502
+ def first(*args)
503
+ find(:first, *args)
504
+ end
505
+
506
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
507
+ # same arguments to this method as you can to <tt>find(:last)</tt>.
508
+ def last(*args)
509
+ find(:last, *args)
510
+ end
511
+
512
+ # This is an alias for find(:all). You can pass in all the same arguments to this method as you can
513
+ # to find(:all)
514
+ def all(*args)
515
+ find(:all, *args)
516
+ end
517
+
518
+ def context
519
+ Groonga::Context.default
520
+ end
521
+
522
+ def database
523
+ context.database
524
+ end
525
+
526
+ def table
527
+ context[groonga_table_name]
528
+ end
529
+
530
+ def groonga_table_name(name=nil)
531
+ "<table:#{name || table_name}>"
532
+ end
533
+
534
+ def groonga_metadata_table_name(name)
535
+ "<metadata:#{name}>"
536
+ end
537
+
538
+ # Defines an "attribute" method (like +inheritance_column+ or
539
+ # +table_name+). A new (class) method will be created with the
540
+ # given name. If a value is specified, the new method will
541
+ # return that value (as a string). Otherwise, the given block
542
+ # will be used to compute the value of the method.
543
+ #
544
+ # The original method will be aliased, with the new name being
545
+ # prefixed with "original_". This allows the new method to
546
+ # access the original value.
547
+ #
548
+ # Example:
549
+ #
550
+ # class A < ActiveRecord::Base
551
+ # define_attr_method :primary_key, "sysid"
552
+ # define_attr_method( :inheritance_column ) do
553
+ # original_inheritance_column + "_id"
554
+ # end
555
+ # end
556
+ def define_attr_method(name, value=nil, &block)
557
+ sing = class << self; self; end
558
+ sing.send :alias_method, "original_#{name}", name
559
+ if block_given?
560
+ sing.send :define_method, name, &block
561
+ else
562
+ # use eval instead of a block to work around a memory leak in dev
563
+ # mode in fcgi
564
+ sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
565
+ end
566
+ end
567
+
568
+ def setup_database(spec=nil)
569
+ case spec
570
+ when nil
571
+ raise DatabaseNotSpecified unless defined? RAILS_ENV
572
+ setup_database(RAILS_ENV)
573
+ when Symbol, String
574
+ if configuration = configurations[spec.to_s]
575
+ setup_database(configuration)
576
+ else
577
+ raise DatabaseNotSpecified, "#{spec} database is not configured"
578
+ end
579
+ else
580
+ spec = spec.symbolize_keys
581
+ unless spec.key?(:database)
582
+ raise DatabaseNotSpecified, "groonga configuration does not specify database"
583
+ end
584
+ database_directory = spec[:database]
585
+
586
+ Groonga::Context.default = nil
587
+ Groonga::Context.default_options = {:encoding => spec[:encoding]}
588
+ unless File.exist?(database_directory)
589
+ FileUtils.mkdir_p(database_directory)
590
+ end
591
+ database_file = File.join(database_directory, "database.groonga")
592
+ if File.exist?(database_file)
593
+ Groonga::Database.new(database_file)
594
+ else
595
+ Groonga::Database.create(:path => database_file)
596
+ end
597
+ self.database_directory = database_directory
598
+ end
599
+ end
600
+
601
+ def tables_directory
602
+ directory = File.join(database_directory, "tables")
603
+ FileUtils.mkdir_p(directory) unless File.exist?(directory)
604
+ directory
605
+ end
606
+
607
+ def columns_directory(table_name)
608
+ directory = File.join(tables_directory, table_name.to_s, "columns")
609
+ FileUtils.mkdir_p(directory) unless File.exist?(directory)
610
+ directory
611
+ end
612
+
613
+ def metadata_directory
614
+ directory = File.join(database_directory, "metadata")
615
+ FileUtils.mkdir_p(directory) unless File.exist?(directory)
616
+ directory
617
+ end
618
+
619
+ def count
620
+ table.size
621
+ end
622
+
623
+ private
624
+ def find_initial(options)
625
+ options.update(:limit => 1)
626
+ find_every(options).first
627
+ end
628
+
629
+ def find_every(options)
630
+ limit = options[:limit] ||= 0
631
+ conditions = (options[:conditions] || {}).stringify_keys
632
+ include_associations = merge_includes(scope(:find, :include), options[:include])
633
+
634
+ if include_associations.any? && references_eager_loaded_tables?(options)
635
+ records = find_with_associations(options)
636
+ else
637
+ records = []
638
+ target_records = []
639
+ original_table = table
640
+ index_records = nil
641
+ Schema.indexes(table_name).each do |index_definition|
642
+ if conditions.has_key?(index_definition.column)
643
+ index_column_name =
644
+ "#{index_definition.table}/#{index_definition.column}"
645
+ index = Schema.index_table.column(index_column_name)
646
+ key = conditions.delete(index_definition.column)
647
+ index_records = index.search(key, :result => index_records)
648
+ end
649
+ end
650
+ if index_records
651
+ sorted_records = index_records.sort([".:score"], :limit => limit)
652
+ limit = sorted_records.size
653
+ target_records = sorted_records.records(:order => :ascending).collect do |record|
654
+ index_record_id = record.value.unpack("i")[0]
655
+ index_record = Groonga::Record.new(index_records, index_record_id)
656
+ target_record = index_record.key
657
+ target_record.instance_variable_set("@score", index_record.score)
658
+ def target_record.score
659
+ @score
660
+ end
661
+ target_record
662
+ end
663
+ else
664
+ target_records = original_table.records
665
+ limit = target_records.size if limit.zero?
666
+ end
667
+ target_records.each_with_index do |record, i|
668
+ break if records.size >= limit
669
+ unless conditions.all? do |name, value|
670
+ record[name] == value or
671
+ (record.reference_column?(name) and record[name].id == value)
672
+ end
673
+ next
674
+ end
675
+ records << instantiate(record)
676
+ end
677
+ if include_associations.any?
678
+ preload_associations(records, include_associations)
679
+ end
680
+ end
681
+
682
+ records.each {|record| record.readonly!} if options[:readonly]
683
+
684
+ records
685
+ end
686
+
687
+ def find_from_ids(ids, options)
688
+ expects_array = ids.first.kind_of?(Array)
689
+ return ids.first if expects_array && ids.first.empty?
690
+
691
+ ids = ids.flatten.compact.uniq
692
+
693
+ case ids.size
694
+ when 0
695
+ raise RecordNotFound, "Couldn't find #{name} without an ID"
696
+ when 1
697
+ result = find_one(ids.first, options)
698
+ expects_array ? [result] : result
699
+ else
700
+ find_some(ids, options)
701
+ end
702
+ end
703
+
704
+ def find_one(id, options)
705
+ if id.is_a?(Groonga::Record)
706
+ record = id
707
+ else
708
+ if id.is_a?(ActiveGroonga::Base)
709
+ id = id.id
710
+ else
711
+ id = Integer(id)
712
+ end
713
+ record = Groonga::Record.new(table, id)
714
+ end
715
+ result = instantiate(record)
716
+ if result.nil?
717
+ raise RecordNotFound, "Couldn't find #{name} with ID=#{id}"
718
+ end
719
+ result
720
+ end
721
+
722
+ def find_some(ids, options)
723
+ result = ids.collect do |id|
724
+ context[id]
725
+ end
726
+ n_not_found_ids = result.count(nil)
727
+ if n_not_found_ids.zero?
728
+ result
729
+ else
730
+ raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids}) (found #{result.compact.size} results, but was looking for #{ids.size})"
731
+ end
732
+ end
733
+
734
+ def merge_includes(first, second)
735
+ (safe_to_array(first) + safe_to_array(second)).uniq
736
+ end
737
+
738
+ # ugly. derived from Active Record. FIXME: remove it.
739
+ def safe_to_array(o)
740
+ case o
741
+ when NilClass
742
+ []
743
+ when Array
744
+ o
745
+ else
746
+ [o]
747
+ end
748
+ end
749
+
750
+ VALID_FIND_OPTIONS = [:conditions, :readonly, :limit]
751
+ def validate_find_options(options)
752
+ options.assert_valid_keys(VALID_FIND_OPTIONS)
753
+ end
754
+
755
+ def set_readonly_option!(options) #:nodoc:
756
+ # Inherit :readonly from finder scope if set. Otherwise,
757
+ # if :joins is not blank then :readonly defaults to true.
758
+ unless options.has_key?(:readonly)
759
+ if scoped_readonly = scope(:find, :readonly)
760
+ options[:readonly] = scoped_readonly
761
+ elsif !options[:joins].blank? && !options[:select]
762
+ options[:readonly] = true
763
+ end
764
+ end
765
+ end
766
+
767
+ # Guesses the table name, but does not decorate it with prefix and suffix information.
768
+ def undecorated_table_name(class_name = base_class.name)
769
+ table_name = class_name.to_s.demodulize.underscore
770
+ table_name = table_name.pluralize if pluralize_table_names
771
+ table_name
772
+ end
773
+
774
+ # Finder methods must instantiate through this method to work with the
775
+ # single-table inheritance model that makes it possible to create
776
+ # objects of different types from the same table.
777
+ def instantiate(record)
778
+ object =
779
+ if subclass_name = record[inheritance_column]
780
+ # No type given.
781
+ if subclass_name.empty?
782
+ allocate
783
+
784
+ else
785
+ # Ignore type if no column is present since it was probably
786
+ # pulled in from a sloppy join.
787
+ unless columns_hash.include?(inheritance_column)
788
+ allocate
789
+
790
+ else
791
+ begin
792
+ compute_type(subclass_name).allocate
793
+ rescue NameError
794
+ raise SubclassNotFound,
795
+ "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
796
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
797
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
798
+ "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
799
+ end
800
+ end
801
+ end
802
+ else
803
+ allocate
804
+ end
805
+
806
+ object.instance_variable_set("@id", record.id)
807
+ object.instance_variable_set("@score", record.score)
808
+ attributes = {}
809
+ record.table.columns.each do |column|
810
+ _, column_name = column.name.split(/\A#{record.table.name}\./, 2)
811
+ attributes[column_name] = column[record.id]
812
+ end
813
+ object.instance_variable_set("@attributes", attributes)
814
+ object.instance_variable_set("@attributes_cache", Hash.new)
815
+
816
+ if object.respond_to_without_attributes?(:after_find)
817
+ object.send(:callback, :after_find)
818
+ end
819
+
820
+ if object.respond_to_without_attributes?(:after_initialize)
821
+ object.send(:callback, :after_initialize)
822
+ end
823
+
824
+ object
825
+ end
826
+
827
+ # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
828
+ # that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and
829
+ # <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for
830
+ # <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>.
831
+ #
832
+ # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
833
+ # is actually <tt>find_all_by_amount(amount, options)</tt>.
834
+ #
835
+ # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
836
+ # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
837
+ # respectively.
838
+ #
839
+ # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
840
+ # attempts to use it do not run through method_missing.
841
+ def method_missing(method_id, *arguments, &block)
842
+ if match = ActiveRecord::DynamicFinderMatch.match(method_id)
843
+ attribute_names = match.attribute_names
844
+ super unless all_attributes_exists?(attribute_names)
845
+ if match.finder?
846
+ finder = match.finder
847
+ bang = match.bang?
848
+ # def self.find_by_login_and_activated(*args)
849
+ # options = args.extract_options!
850
+ # attributes = construct_attributes_from_arguments(
851
+ # [:login,:activated],
852
+ # args
853
+ # )
854
+ # finder_options = { :conditions => attributes }
855
+ # validate_find_options(options)
856
+ # set_readonly_option!(options)
857
+ #
858
+ # if options[:conditions]
859
+ # with_scope(:find => finder_options) do
860
+ # find(:first, options)
861
+ # end
862
+ # else
863
+ # find(:first, options.merge(finder_options))
864
+ # end
865
+ # end
866
+ self.class_eval <<-EOC, __FILE__, __LINE__
867
+ def self.#{method_id}(*args)
868
+ options = args.extract_options!
869
+ attributes = construct_attributes_from_arguments(
870
+ [:#{attribute_names.join(',:')}],
871
+ args
872
+ )
873
+ finder_options = { :conditions => attributes }
874
+ validate_find_options(options)
875
+ set_readonly_option!(options)
876
+
877
+ #{'result = ' if bang}if options[:conditions]
878
+ with_scope(:find => finder_options) do
879
+ find(:#{finder}, options)
880
+ end
881
+ else
882
+ find(:#{finder}, options.merge(finder_options))
883
+ end
884
+ #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
885
+ end
886
+ EOC
887
+ send(method_id, *arguments)
888
+ elsif match.instantiator?
889
+ instantiator = match.instantiator
890
+ # def self.find_or_create_by_user_id(*args)
891
+ # guard_protected_attributes = false
892
+ #
893
+ # if args[0].is_a?(Hash)
894
+ # guard_protected_attributes = true
895
+ # attributes = args[0].with_indifferent_access
896
+ # find_attributes = attributes.slice(*[:user_id])
897
+ # else
898
+ # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
899
+ # end
900
+ #
901
+ # options = { :conditions => find_attributes }
902
+ # set_readonly_option!(options)
903
+ #
904
+ # record = find(:first, options)
905
+ #
906
+ # if record.nil?
907
+ # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
908
+ # yield(record) if block_given?
909
+ # record.save
910
+ # record
911
+ # else
912
+ # record
913
+ # end
914
+ # end
915
+ self.class_eval <<-EOC, __FILE__, __LINE__
916
+ def self.#{method_id}(*args)
917
+ guard_protected_attributes = false
918
+
919
+ if args[0].is_a?(Hash)
920
+ guard_protected_attributes = true
921
+ attributes = args[0].with_indifferent_access
922
+ find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
923
+ else
924
+ find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
925
+ end
926
+
927
+ options = { :conditions => find_attributes }
928
+ set_readonly_option!(options)
929
+
930
+ record = find(:first, options)
931
+
932
+ if record.nil?
933
+ record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
934
+ #{'yield(record) if block_given?'}
935
+ #{'record.save' if instantiator == :create}
936
+ record
937
+ else
938
+ record
939
+ end
940
+ end
941
+ EOC
942
+ send(method_id, *arguments, &block)
943
+ end
944
+ elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
945
+ attribute_names = match.attribute_names
946
+ super unless all_attributes_exists?(attribute_names)
947
+ if match.scope?
948
+ self.class_eval <<-EOC, __FILE__, __LINE__
949
+ def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
950
+ options = args.extract_options! # options = args.extract_options!
951
+ attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
952
+ [:#{attribute_names.join(',:')}], args # [:user_name, :password], args
953
+ ) # )
954
+ #
955
+ scoped(:conditions => attributes) # scoped(:conditions => attributes)
956
+ end # end
957
+ EOC
958
+ send(method_id, *arguments)
959
+ end
960
+ else
961
+ super
962
+ end
963
+ end
964
+
965
+ def construct_attributes_from_arguments(attribute_names, arguments)
966
+ attributes = {}
967
+ attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
968
+ attributes
969
+ end
970
+
971
+ # Similar in purpose to +expand_hash_conditions_for_aggregates+.
972
+ def expand_attribute_names_for_aggregates(attribute_names)
973
+ expanded_attribute_names = []
974
+ attribute_names.each do |attribute_name|
975
+ unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
976
+ aggregate_mapping(aggregation).each do |field_attr, aggregate_attr|
977
+ expanded_attribute_names << field_attr
978
+ end
979
+ else
980
+ expanded_attribute_names << attribute_name
981
+ end
982
+ end
983
+ expanded_attribute_names
984
+ end
985
+
986
+ def all_attributes_exists?(attribute_names)
987
+ attribute_names = expand_attribute_names_for_aggregates(attribute_names)
988
+ attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
989
+ end
990
+
991
+ # Nest the type name in the same module as this class.
992
+ # Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
993
+ def type_name_with_module(type_name)
994
+ if store_full_sti_class
995
+ type_name
996
+ else
997
+ (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
998
+ end
999
+ end
1000
+
1001
+ # Test whether the given method and optional key are scoped.
1002
+ def scoped?(method, key = nil) #:nodoc:
1003
+ if current_scoped_methods && (scope = current_scoped_methods[method])
1004
+ !key || !scope[key].nil?
1005
+ end
1006
+ end
1007
+
1008
+ # Retrieve the scope for the given method and optional key.
1009
+ def scope(method, key = nil) #:nodoc:
1010
+ if current_scoped_methods && (scope = current_scoped_methods[method])
1011
+ key ? scope[key] : scope
1012
+ end
1013
+ end
1014
+
1015
+ def scoped_methods #:nodoc:
1016
+ Thread.current[:"#{self}_scoped_methods"] ||= default_scoping.dup
1017
+ end
1018
+
1019
+ def current_scoped_methods #:nodoc:
1020
+ scoped_methods.last
1021
+ end
1022
+
1023
+ # Returns the class type of the record using the current module as a prefix. So descendants of
1024
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
1025
+ def compute_type(type_name)
1026
+ modularized_name = type_name_with_module(type_name)
1027
+ silence_warnings do
1028
+ begin
1029
+ class_eval(modularized_name, __FILE__, __LINE__)
1030
+ rescue NameError
1031
+ class_eval(type_name, __FILE__, __LINE__)
1032
+ end
1033
+ end
1034
+ end
1035
+
1036
+ # Returns the class descending directly from ActiveGroonga::Base or an
1037
+ # abstract class, if any, in the inheritance hierarchy.
1038
+ def class_of_active_groonga_descendant(klass)
1039
+ if klass.superclass == Base || klass.superclass.abstract_class?
1040
+ klass
1041
+ elsif klass.superclass.nil?
1042
+ raise ActiveGroongaError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
1043
+ else
1044
+ class_of_active_record_descendant(klass.superclass)
1045
+ end
1046
+ end
1047
+ end
1048
+
1049
+ def initialize(attributes=nil)
1050
+ @id = nil
1051
+ @score = nil
1052
+ @attributes = attributes_from_column_definition
1053
+ @attributes_cache = {}
1054
+ @new_record = true
1055
+ ensure_proper_type
1056
+ self.attributes = attributes unless attributes.nil?
1057
+ self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
1058
+ result = yield self if block_given?
1059
+ callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
1060
+ result
1061
+ end
1062
+
1063
+ # A model instance's primary key is always available as model.id
1064
+ # whether you name it the default 'id' or set it to something else.
1065
+ def id
1066
+ @id
1067
+ end
1068
+
1069
+ def score
1070
+ @score
1071
+ end
1072
+
1073
+ # Returns a String, which Action Pack uses for constructing an URL to this
1074
+ # object. The default implementation returns this record's id as a String,
1075
+ # or nil if this record's unsaved.
1076
+ #
1077
+ # For example, suppose that you have a User model, and that you have a
1078
+ # <tt>map.resources :users</tt> route. Normally, +user_path+ will
1079
+ # construct a path with the user object's 'id' in it:
1080
+ #
1081
+ # user = User.find_by_name('Phusion')
1082
+ # user_path(user) # => "/users/1"
1083
+ #
1084
+ # You can override +to_param+ in your model to make +user_path+ construct
1085
+ # a path using the user's name instead of the user's id:
1086
+ #
1087
+ # class User < ActiveRecord::Base
1088
+ # def to_param # overridden
1089
+ # name
1090
+ # end
1091
+ # end
1092
+ #
1093
+ # user = User.find_by_name('Phusion')
1094
+ # user_path(user) # => "/users/Phusion"
1095
+ def to_param
1096
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
1097
+ (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
1098
+ end
1099
+
1100
+ # Sets the primary ID.
1101
+ def id=(value)
1102
+ @id = value
1103
+ end
1104
+
1105
+ # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
1106
+ def new_record?
1107
+ @new_record || false
1108
+ end
1109
+
1110
+ # :call-seq:
1111
+ # save(perform_validation = true)
1112
+ #
1113
+ # Saves the model.
1114
+ #
1115
+ # If the model is new a record gets created in the database, otherwise
1116
+ # the existing record gets updated.
1117
+ #
1118
+ # If +perform_validation+ is true validations run. If any of them fail
1119
+ # the action is cancelled and +save+ returns +false+. If the flag is
1120
+ # false validations are bypassed altogether. See
1121
+ # ActiveRecord::Validations for more information.
1122
+ #
1123
+ # There's a series of callbacks associated with +save+. If any of the
1124
+ # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
1125
+ # +save+ returns +false+. See ActiveRecord::Callbacks for further
1126
+ # details.
1127
+ def save
1128
+ create_or_update
1129
+ end
1130
+
1131
+ # Saves the model.
1132
+ #
1133
+ # If the model is new a record gets created in the database, otherwise
1134
+ # the existing record gets updated.
1135
+ #
1136
+ # With <tt>save!</tt> validations always run. If any of them fail
1137
+ # ActiveGroonga::RecordInvalid gets raised. See ActiveRecord::Validations
1138
+ # for more information.
1139
+ #
1140
+ # There's a series of callbacks associated with <tt>save!</tt>. If any of
1141
+ # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
1142
+ # and <tt>save!</tt> raises ActiveGroonga::RecordNotSaved. See
1143
+ # ActiveRecord::Callbacks for further details.
1144
+ def save!
1145
+ create_or_update || raise(RecordNotSaved)
1146
+ end
1147
+
1148
+ # Deletes the record in the database and freezes this instance to
1149
+ # reflect that no changes should be made (since they can't be
1150
+ # persisted). Returns the frozen instance.
1151
+ #
1152
+ # The row is simply removed with a SQL +DELETE+ statement on the
1153
+ # record's primary key, and no callbacks are executed.
1154
+ #
1155
+ # To enforce the object's +before_destroy+ and +after_destroy+
1156
+ # callbacks, Observer methods, or any <tt>:dependent</tt> association
1157
+ # options, use <tt>#destroy</tt>.
1158
+ def delete
1159
+ self.class.delete(id) unless new_record?
1160
+ freeze
1161
+ end
1162
+
1163
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
1164
+ # be made (since they can't be persisted).
1165
+ def destroy
1166
+ self.class.table.delete(id) unless new_record?
1167
+ freeze
1168
+ end
1169
+
1170
+ # Updates a single attribute and saves the record without going through the normal validation procedure.
1171
+ # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
1172
+ # in Base is replaced with this when the validations module is mixed in, which it is by default.
1173
+ def update_attribute(name, value)
1174
+ send(name.to_s + '=', value)
1175
+ save(false)
1176
+ end
1177
+
1178
+ # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
1179
+ # fail and false will be returned.
1180
+ def update_attributes(attributes)
1181
+ self.attributes = attributes
1182
+ save
1183
+ end
1184
+
1185
+ # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
1186
+ def update_attributes!(attributes)
1187
+ self.attributes = attributes
1188
+ save!
1189
+ end
1190
+
1191
+ # Reloads the attributes of this object from the database.
1192
+ # The optional options argument is passed to find when reloading so you
1193
+ # may do e.g. record.reload(:lock => true) to reload the same record with
1194
+ # an exclusive row lock.
1195
+ def reload(options = nil)
1196
+ clear_aggregation_cache
1197
+ clear_association_cache
1198
+ @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
1199
+ @attributes_cache = {}
1200
+ self
1201
+ end
1202
+
1203
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
1204
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
1205
+ # (Alias for the protected read_attribute method).
1206
+ def [](attr_name)
1207
+ read_attribute(attr_name)
1208
+ end
1209
+
1210
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
1211
+ # (Alias for the protected write_attribute method).
1212
+ def []=(attr_name, value)
1213
+ write_attribute(attr_name, value)
1214
+ end
1215
+
1216
+ # Allows you to set all the attributes at once by passing in a hash with keys
1217
+ # matching the attribute names (which again matches the column names).
1218
+ #
1219
+ # If +guard_protected_attributes+ is true (the default), then sensitive
1220
+ # attributes can be protected from this form of mass-assignment by using
1221
+ # the +attr_protected+ macro. Or you can alternatively specify which
1222
+ # attributes *can* be accessed with the +attr_accessible+ macro. Then all the
1223
+ # attributes not included in that won't be allowed to be mass-assigned.
1224
+ #
1225
+ # class User < ActiveGroonga::Base
1226
+ # attr_protected :is_admin
1227
+ # end
1228
+ #
1229
+ # user = User.new
1230
+ # user.attributes = { :username => 'Phusion', :is_admin => true }
1231
+ # user.username # => "Phusion"
1232
+ # user.is_admin? # => false
1233
+ #
1234
+ # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
1235
+ # user.is_admin? # => true
1236
+ def attributes=(new_attributes, guard_protected_attributes = true)
1237
+ return if new_attributes.nil?
1238
+ attributes = new_attributes.dup
1239
+ attributes.stringify_keys!
1240
+
1241
+ multi_parameter_attributes = []
1242
+ attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
1243
+
1244
+ attributes.each do |k, v|
1245
+ if k.include?("(")
1246
+ multi_parameter_attributes << [ k, v ]
1247
+ else
1248
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
1249
+ end
1250
+ end
1251
+
1252
+ assign_multiparameter_attributes(multi_parameter_attributes)
1253
+ end
1254
+
1255
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
1256
+ def attributes
1257
+ self.attribute_names.inject({}) do |attrs, name|
1258
+ attrs[name] = read_attribute(name)
1259
+ attrs
1260
+ end
1261
+ end
1262
+
1263
+ # Returns a hash of attributes before typecasting and deserialization.
1264
+ def attributes_before_type_cast
1265
+ self.attribute_names.inject({}) do |attrs, name|
1266
+ attrs[name] = read_attribute_before_type_cast(name)
1267
+ attrs
1268
+ end
1269
+ end
1270
+
1271
+ # Returns an <tt>#inspect</tt>-like string for the value of the
1272
+ # attribute +attr_name+. String attributes are elided after 50
1273
+ # characters, and Date and Time attributes are returned in the
1274
+ # <tt>:db</tt> format. Other attributes return the value of
1275
+ # <tt>#inspect</tt> without modification.
1276
+ #
1277
+ # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
1278
+ #
1279
+ # person.attribute_for_inspect(:name)
1280
+ # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
1281
+ #
1282
+ # person.attribute_for_inspect(:created_at)
1283
+ # # => '"2009-01-12 04:48:57"'
1284
+ def attribute_for_inspect(attr_name)
1285
+ value = read_attribute(attr_name)
1286
+
1287
+ if value.is_a?(String) && value.length > 50
1288
+ "#{value[0..50]}...".inspect
1289
+ elsif value.is_a?(Date) || value.is_a?(Time)
1290
+ %("#{value.to_s(:db)}")
1291
+ else
1292
+ value.inspect
1293
+ end
1294
+ end
1295
+
1296
+ # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
1297
+ # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
1298
+ def attribute_present?(attribute)
1299
+ value = read_attribute(attribute)
1300
+ !value.blank?
1301
+ end
1302
+
1303
+ # Returns true if the given attribute is in the attributes hash
1304
+ def has_attribute?(attr_name)
1305
+ @attributes.has_key?(attr_name.to_s)
1306
+ end
1307
+
1308
+ # Returns an array of names for the attributes available on this object sorted alphabetically.
1309
+ def attribute_names
1310
+ @attributes.keys.sort
1311
+ end
1312
+
1313
+ # Returns the column object for the named attribute.
1314
+ def column_for_attribute(name)
1315
+ self.class.columns_hash[name.to_s]
1316
+ end
1317
+
1318
+ # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
1319
+ def ==(comparison_object)
1320
+ comparison_object.equal?(self) ||
1321
+ (comparison_object.instance_of?(self.class) &&
1322
+ comparison_object.id == id &&
1323
+ !comparison_object.new_record?)
1324
+ end
1325
+
1326
+ # Delegates to ==
1327
+ def eql?(comparison_object)
1328
+ self == (comparison_object)
1329
+ end
1330
+
1331
+ # Delegates to id in order to allow two records of the same type and id to work with something like:
1332
+ # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
1333
+ def hash
1334
+ id.hash
1335
+ end
1336
+
1337
+ # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
1338
+ def freeze
1339
+ @attributes.freeze; self
1340
+ end
1341
+
1342
+ # Returns +true+ if the attributes hash has been frozen.
1343
+ def frozen?
1344
+ @attributes.frozen?
1345
+ end
1346
+
1347
+ # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
1348
+ # attributes will be marked as read only since they cannot be saved.
1349
+ def readonly?
1350
+ defined?(@readonly) && @readonly == true
1351
+ end
1352
+
1353
+ # Marks this record as read only.
1354
+ def readonly!
1355
+ @readonly = true
1356
+ end
1357
+
1358
+ # Returns the contents of the record as a nicely formatted string.
1359
+ def inspect
1360
+ attributes_as_nice_string = self.class.column_names.collect { |name|
1361
+ if has_attribute?(name) || new_record?
1362
+ "#{name}: #{attribute_for_inspect(name)}"
1363
+ end
1364
+ }.compact.join(", ")
1365
+ "#<#{self.class} #{attributes_as_nice_string}>"
1366
+ end
1367
+
1368
+ private
1369
+ def create_or_update
1370
+ raise ReadOnlyRecord if readonly?
1371
+ result = new_record? ? create : update
1372
+ result != false
1373
+ end
1374
+
1375
+ # Updates the associated record with values matching those of the instance attributes.
1376
+ # Returns the number of affected rows.
1377
+ def update(attribute_names=@attributes.keys)
1378
+ attribute_names = remove_readonly_attributes(attribute_names)
1379
+ table = self.class.table
1380
+ indexes = Schema.indexes(table)
1381
+ quoted_attributes = attributes_with_quotes(false, attribute_names)
1382
+ quoted_attributes.each do |name, value|
1383
+ column = table.column(name)
1384
+ next if column.nil?
1385
+ column[id] = value
1386
+ end
1387
+ end
1388
+
1389
+ # Creates a record with values matching those of the instance attributes
1390
+ # and returns its id.
1391
+ def create
1392
+ table = self.class.table
1393
+ record = table.add
1394
+ indexes = Schema.indexes(table)
1395
+ quoted_attributes = attributes_with_quotes
1396
+ quoted_attributes.each do |name, value|
1397
+ record[name] = value
1398
+ end
1399
+ self.id = record.id
1400
+ @new_record = false
1401
+ id
1402
+ end
1403
+
1404
+ # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
1405
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
1406
+ # set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
1407
+ # Message class in that example.
1408
+ def ensure_proper_type
1409
+ unless self.class.descends_from_active_groonga?
1410
+ write_attribute(self.class.inheritance_column, self.class.sti_name)
1411
+ end
1412
+ end
1413
+
1414
+ def convert_number_column_value(value)
1415
+ if value == false
1416
+ 0
1417
+ elsif value == true
1418
+ 1
1419
+ elsif value.is_a?(String) && value.blank?
1420
+ nil
1421
+ else
1422
+ value
1423
+ end
1424
+ end
1425
+
1426
+ def remove_attributes_protected_from_mass_assignment(attributes)
1427
+ safe_attributes =
1428
+ if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
1429
+ attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1430
+ elsif self.class.protected_attributes.nil?
1431
+ attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1432
+ elsif self.class.accessible_attributes.nil?
1433
+ attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1434
+ else
1435
+ raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
1436
+ end
1437
+
1438
+ removed_attributes = attributes.keys - safe_attributes.keys
1439
+
1440
+ if removed_attributes.any?
1441
+ log_protected_attribute_removal(removed_attributes)
1442
+ end
1443
+
1444
+ safe_attributes
1445
+ end
1446
+
1447
+ # Removes attributes which have been marked as readonly.
1448
+ def remove_readonly_attributes(attributes)
1449
+ unless self.class.readonly_attributes.nil?
1450
+ attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) }
1451
+ else
1452
+ attributes
1453
+ end
1454
+ end
1455
+
1456
+ def log_protected_attribute_removal(*attributes)
1457
+ logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
1458
+ end
1459
+
1460
+ # The primary key and inheritance column can never be set by mass-assignment for security reasons.
1461
+ def attributes_protected_by_default
1462
+ default = [ self.class.primary_key, self.class.inheritance_column ]
1463
+ default << 'id' unless self.class.primary_key.eql? 'id'
1464
+ default
1465
+ end
1466
+
1467
+ # Initializes the attributes array with keys matching the columns from the linked table and
1468
+ # the values matching the corresponding default value of that column, so
1469
+ # that a new instance, or one populated from a passed-in Hash, still has all the attributes
1470
+ # that instances loaded from the database would.
1471
+ def attributes_from_column_definition
1472
+ self.class.columns.inject({}) do |attributes, column|
1473
+ attributes[column.name] = column.default
1474
+ attributes
1475
+ end
1476
+ end
1477
+
1478
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
1479
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
1480
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
1481
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
1482
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
1483
+ # s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.
1484
+ def assign_multiparameter_attributes(pairs)
1485
+ execute_callstack_for_multiparameter_attributes(
1486
+ extract_callstack_for_multiparameter_attributes(pairs)
1487
+ )
1488
+ end
1489
+
1490
+ def execute_callstack_for_multiparameter_attributes(callstack)
1491
+ errors = []
1492
+ callstack.each do |name, values|
1493
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
1494
+ if values.empty?
1495
+ send(name + "=", nil)
1496
+ else
1497
+ begin
1498
+ value = if Time == klass
1499
+ instantiate_time_object(name, values)
1500
+ elsif Date == klass
1501
+ begin
1502
+ Date.new(*values)
1503
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
1504
+ instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
1505
+ end
1506
+ else
1507
+ klass.new(*values)
1508
+ end
1509
+
1510
+ send(name + "=", value)
1511
+ rescue => ex
1512
+ errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
1513
+ end
1514
+ end
1515
+ end
1516
+ unless errors.empty?
1517
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
1518
+ end
1519
+ end
1520
+
1521
+ def extract_callstack_for_multiparameter_attributes(pairs)
1522
+ attributes = { }
1523
+
1524
+ for pair in pairs
1525
+ multiparameter_name, value = pair
1526
+ attribute_name = multiparameter_name.split("(").first
1527
+ attributes[attribute_name] = [] unless attributes.include?(attribute_name)
1528
+
1529
+ unless value.empty?
1530
+ attributes[attribute_name] <<
1531
+ [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
1532
+ end
1533
+ end
1534
+
1535
+ attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
1536
+ end
1537
+
1538
+ # Returns a copy of the attributes hash where all the values have been safely quoted for use in
1539
+ # an SQL statement.
1540
+ def attributes_with_quotes(include_readonly_attributes=true, attribute_names=@attributes.keys)
1541
+ quoted = {}
1542
+ attribute_names.each do |name|
1543
+ column = column_for_attribute(name)
1544
+ next if column.nil?
1545
+
1546
+ value = read_attribute(name)
1547
+ # We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML.
1548
+ if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))
1549
+ value = value.to_yaml
1550
+ end
1551
+ quoted[name] = column.quote(value)
1552
+ end
1553
+ include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
1554
+ end
1555
+
1556
+ # Quote strings appropriately for SQL statements.
1557
+ def quote_value(value, column=nil)
1558
+ if column
1559
+ column.quote(value)
1560
+ else
1561
+ value
1562
+ end
1563
+ end
1564
+
1565
+ def clone_attribute_value(reader_method, attribute_name)
1566
+ value = send(reader_method, attribute_name)
1567
+ value.duplicable? ? value.clone : value
1568
+ rescue TypeError, NoMethodError
1569
+ value
1570
+ end
1571
+
1572
+ include Validations
1573
+ include AttributeMethods
1574
+ include Dirty
1575
+ include Timestamp
1576
+ include Associations
1577
+ include Aggregations, Reflection
1578
+ end
1579
+ end