activegroonga 0.0.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. data/README.ja.rdoc +4 -1
  2. data/README.rdoc +4 -1
  3. data/Rakefile +18 -4
  4. data/lib/active_groonga.rb +25 -52
  5. data/lib/active_groonga/base.rb +164 -1480
  6. data/lib/active_groonga/callbacks.rb +26 -7
  7. data/lib/active_groonga/database.rb +55 -0
  8. data/lib/active_groonga/{dirty.rb → error.rb} +20 -10
  9. data/lib/active_groonga/locale/en.yml +5 -0
  10. data/lib/active_groonga/migration.rb +44 -113
  11. data/lib/active_groonga/migrator.rb +172 -0
  12. data/lib/active_groonga/persistence.rb +172 -0
  13. data/lib/active_groonga/railtie.rb +66 -0
  14. data/lib/active_groonga/railties/configurable.rb +47 -0
  15. data/lib/active_groonga/railties/groonga.rake +167 -0
  16. data/lib/active_groonga/result_set.rb +89 -0
  17. data/lib/active_groonga/schema.rb +54 -188
  18. data/lib/active_groonga/validations.rb +54 -5
  19. data/lib/active_groonga/vector.rb +64 -0
  20. data/lib/active_groonga/version.rb +3 -3
  21. data/lib/{active_groonga/aggregations.rb → rails/generators/active_groonga.rb} +17 -10
  22. data/lib/rails/generators/active_groonga/migration/column.rb +101 -0
  23. data/lib/rails/generators/active_groonga/migration/migration_generator.rb +53 -0
  24. data/lib/rails/generators/active_groonga/migration/templates/migration.rb +29 -0
  25. data/lib/rails/generators/active_groonga/model/model_generator.rb +60 -0
  26. data/lib/rails/generators/active_groonga/model/templates/migration.rb +16 -0
  27. data/lib/rails/generators/active_groonga/model/templates/model.rb +2 -0
  28. data/lib/rails/generators/active_groonga/model/templates/module.rb +5 -0
  29. data/test-unit-notify/COPYING +502 -0
  30. data/test-unit-notify/Rakefile +47 -0
  31. data/test-unit-notify/lib/test/unit/notify.rb +127 -0
  32. data/test-unit/COPYING +56 -0
  33. data/test-unit/GPL +340 -0
  34. data/test-unit/PSFL +271 -0
  35. data/test-unit/Rakefile +18 -5
  36. data/test-unit/html/bar.svg +153 -0
  37. data/test-unit/html/developer.svg +469 -0
  38. data/test-unit/html/favicon.ico +0 -0
  39. data/test-unit/html/favicon.svg +82 -0
  40. data/test-unit/html/heading-mark.svg +393 -0
  41. data/test-unit/html/index.html +235 -13
  42. data/test-unit/html/index.html.ja +258 -15
  43. data/test-unit/html/install.svg +636 -0
  44. data/test-unit/html/logo.svg +483 -0
  45. data/test-unit/html/test-unit.css +339 -0
  46. data/test-unit/html/tutorial.svg +559 -0
  47. data/test-unit/lib/test/unit.rb +29 -43
  48. data/test-unit/lib/test/unit/assertionfailederror.rb +11 -0
  49. data/test-unit/lib/test/unit/assertions.rb +202 -20
  50. data/test-unit/lib/test/unit/autorunner.rb +51 -20
  51. data/test-unit/lib/test/unit/collector.rb +1 -8
  52. data/test-unit/lib/test/unit/collector/dir.rb +1 -1
  53. data/test-unit/lib/test/unit/collector/load.rb +16 -9
  54. data/test-unit/lib/test/unit/color-scheme.rb +19 -3
  55. data/test-unit/lib/test/unit/diff.rb +240 -38
  56. data/test-unit/lib/test/unit/error.rb +4 -0
  57. data/test-unit/lib/test/unit/failure.rb +31 -5
  58. data/test-unit/lib/test/unit/notification.rb +8 -4
  59. data/test-unit/lib/test/unit/omission.rb +51 -3
  60. data/test-unit/lib/test/unit/pending.rb +4 -0
  61. data/test-unit/lib/test/unit/priority.rb +2 -3
  62. data/test-unit/lib/test/unit/testcase.rb +65 -7
  63. data/test-unit/lib/test/unit/testresult.rb +34 -2
  64. data/test-unit/lib/test/unit/ui/console/testrunner.rb +197 -45
  65. data/test-unit/lib/test/unit/ui/emacs/testrunner.rb +14 -0
  66. data/test-unit/lib/test/unit/ui/tap/testrunner.rb +2 -12
  67. data/test-unit/lib/test/unit/ui/testrunner.rb +33 -0
  68. data/test-unit/lib/test/unit/util/backtracefilter.rb +1 -0
  69. data/test-unit/lib/test/unit/util/output.rb +31 -0
  70. data/test-unit/lib/test/unit/version.rb +1 -1
  71. data/test-unit/sample/{tc_adder.rb → test_adder.rb} +3 -1
  72. data/test-unit/sample/{tc_subtracter.rb → test_subtracter.rb} +3 -1
  73. data/test-unit/sample/test_user.rb +1 -0
  74. data/test-unit/test/collector/test-descendant.rb +2 -4
  75. data/test-unit/test/collector/test-load.rb +121 -8
  76. data/test-unit/test/collector/test_objectspace.rb +7 -5
  77. data/test-unit/test/run-test.rb +2 -0
  78. data/test-unit/test/test-color-scheme.rb +11 -2
  79. data/test-unit/test/test-diff.rb +48 -7
  80. data/test-unit/test/test-omission.rb +1 -1
  81. data/test-unit/test/test-testcase.rb +57 -20
  82. data/test-unit/test/test_assertions.rb +128 -13
  83. data/test-unit/test/ui/test_tap.rb +33 -0
  84. data/test-unit/test/util/test-output.rb +11 -0
  85. data/test/active-groonga-test-utils.rb +50 -36
  86. data/test/fixtures/site.rb +2 -0
  87. data/test/run-test.rb +36 -9
  88. data/test/test-associations.rb +5 -2
  89. data/test/test-base.rb +39 -31
  90. data/test/test-callbacks.rb +13 -3
  91. data/test/test-persistence.rb +53 -0
  92. data/{lib/active_groonga/observer.rb → test/test-result-set.rb} +14 -12
  93. data/test/test-schema.rb +85 -22
  94. data/{lib/active_groonga/rails_support.rb → test/test-validations.rb} +15 -13
  95. metadata +85 -52
  96. data/lib/active_groonga/associations.rb +0 -93
  97. data/lib/active_groonga/associations/belongs_to_association.rb +0 -25
  98. data/lib/active_groonga/attribute_methods.rb +0 -36
  99. data/lib/active_groonga/column.rb +0 -137
  100. data/lib/active_groonga/dynamic_record_expression_builder.rb +0 -40
  101. data/lib/active_groonga/reflection.rb +0 -30
  102. data/lib/active_groonga/schema_dumper.rb +0 -163
  103. data/lib/active_groonga/tasks.rb +0 -16
  104. data/lib/active_groonga/tasks/groonga.rake +0 -164
  105. data/lib/active_groonga/timestamp.rb +0 -30
  106. data/rails/README +0 -28
  107. data/rails/init.rb +0 -70
  108. data/rails_generators/index_table_groonga/USAGE +0 -23
  109. data/rails_generators/index_table_groonga/index_table_groonga_generator.rb +0 -44
  110. data/rails_generators/index_table_groonga/templates/migration.rb +0 -12
  111. data/rails_generators/migration_groonga/USAGE +0 -29
  112. data/rails_generators/migration_groonga/migration_groonga_generator.rb +0 -19
  113. data/rails_generators/migration_groonga/templates/migration.rb +0 -11
  114. data/rails_generators/model_groonga/USAGE +0 -28
  115. data/rails_generators/model_groonga/model_groonga_generator.rb +0 -45
  116. data/rails_generators/model_groonga/templates/fixtures.yml +0 -17
  117. data/rails_generators/model_groonga/templates/migration.rb +0 -16
  118. data/rails_generators/model_groonga/templates/model.rb +0 -2
  119. data/rails_generators/model_groonga/templates/unit_test.rb +0 -8
  120. data/test-unit/html/classic.html +0 -15
  121. data/test-unit/sample/ts_examples.rb +0 -7
  122. data/test/test-schema-dumper.rb +0 -48
data/README.ja.rdoc CHANGED
@@ -26,9 +26,12 @@ Kouhei Sutou <tt><kou@clear-code.com></tt>
26
26
 
27
27
  LGPL 2.1です。詳しくはlicense/LGPLを見てください。
28
28
 
29
+ (コントリビュートされたパッチなども含み、Kouhei Sutouがライ
30
+ センスを変更する権利を持ちます。)
31
+
29
32
  == 依存ソフトウェア
30
33
 
31
- * Ruby/groonga
34
+ * rroonga
32
35
 
33
36
  == インストール
34
37
 
data/README.rdoc CHANGED
@@ -26,9 +26,12 @@ Kouhei Sutou <tt><kou@clear-code.com></tt>
26
26
 
27
27
  LGPL 2.1. See license/LGPL for details.
28
28
 
29
+ (Kouhei Sutou has a right to change license inclidng
30
+ contributed patches.)
31
+
29
32
  == Dependencies
30
33
 
31
- * Ruby/groonga
34
+ * rroonga
32
35
 
33
36
  == Install
34
37
 
data/Rakefile CHANGED
@@ -22,7 +22,9 @@ require 'fileutils'
22
22
  require 'pathname'
23
23
  require 'erb'
24
24
  require 'rubygems'
25
- gem 'rdoc'
25
+ if RUBY_VERSION < "1.9"
26
+ gem 'rdoc'
27
+ end
26
28
  require 'hoe'
27
29
 
28
30
  ENV["NODOT"] = "yes"
@@ -39,6 +41,9 @@ $LOAD_PATH.unshift(groonga_ext_dir)
39
41
  $LOAD_PATH.unshift(groonga_lib_dir)
40
42
  ENV["RUBYLIB"] = "#{groonga_lib_dir}:#{groonga_ext_dir}:#{ENV['RUBYLIB']}"
41
43
 
44
+ active_groonga_lib_dir = File.join(base_dir, "lib")
45
+ $LOAD_PATH.unshift(active_groonga_lib_dir)
46
+
42
47
  def guess_version
43
48
  require 'active_groonga/version'
44
49
  ActiveGroonga::VERSION::STRING
@@ -82,7 +87,7 @@ def cleanup_white_space(entry)
82
87
  end
83
88
 
84
89
  ENV["VERSION"] ||= guess_version
85
- version = ENV["VERSION"]
90
+ version = ENV["VERSION"].dup
86
91
  project = nil
87
92
  Hoe.spec('activegroonga') do |_project|
88
93
  Hoe::Test::SUPPORTED_TEST_FRAMEWORKS[:testunit2] = "test/run-test.rb"
@@ -106,8 +111,8 @@ Hoe.spec('activegroonga') do |_project|
106
111
  :extra_rdoc_files => Dir.glob("*.rdoc"),
107
112
  }
108
113
  project.readme_file = "README.ja.rdoc"
109
- project.extra_deps = [["groonga", "=#{version}"],
110
- ["activerecord", "=2.3.4"]]
114
+ project.extra_deps = [["rroonga", ">= 1.0.4"],
115
+ ["activemodel", ">= 3.0.1"]]
111
116
 
112
117
  news_of_current_release = File.read("NEWS.rdoc").split(/^==\s.*$/)[1]
113
118
  project.changes = cleanup_white_space(news_of_current_release)
@@ -182,3 +187,12 @@ task :tag do
182
187
  sh("svn cp -m 'release #{version}!!!' " +
183
188
  "#{repository}/trunk #{repository}/tags/#{version}")
184
189
  end
190
+
191
+ desc "generate activegroonga.gemspec"
192
+ task :generate_gemspec do
193
+ spec = project.spec
194
+ spec_name = File.join(base_dir, project.spec.spec_name)
195
+ File.open(spec_name, "w") do |spec_file|
196
+ spec_file.puts(spec.to_ruby)
197
+ end
198
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
1
+ # Copyright (C) 2009-2010 Kouhei Sutou <kou@clear-code.com>
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -13,66 +13,39 @@
13
13
  # License along with this library; if not, write to the Free Software
14
14
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
15
 
16
- require 'rubygems'
17
- require 'active_support'
18
- require 'active_record'
16
+ require 'pathname'
19
17
 
20
- base_dir = File.dirname(__FILE__)
21
- ruby_groonga_dir = File.join(base_dir, "..", "..", "groonga")
22
- ruby_groonga_dir = File.expand_path(ruby_groonga_dir)
23
- if File.exist?(ruby_groonga_dir)
24
- $LOAD_PATH.unshift(File.join(ruby_groonga_dir, "ext"))
25
- $LOAD_PATH.unshift(File.join(ruby_groonga_dir, "lib"))
18
+ require 'active_model'
19
+
20
+ base_dir = Pathname(__FILE__).dirname
21
+ rroonga_dir = (base_dir + "../../groonga").expand_path
22
+ if rroonga_dir.exist?
23
+ $LOAD_PATH.unshift(rroonga_dir + "ext" + "groonga")
24
+ $LOAD_PATH.unshift(rroonga_dir + "lib")
26
25
  end
26
+
27
27
  require 'groonga'
28
28
 
29
29
  module ActiveGroonga
30
- def self.load_all!
31
- [Base, Column]
32
- end
30
+ extend ActiveSupport::Autoload
33
31
 
34
- autoload :VERSION, 'active_groonga/version'
32
+ eager_autoload do
33
+ autoload :VERSION
35
34
 
36
- autoload :ActiveGroongaError, 'active_groonga/base'
35
+ autoload :Error
37
36
 
38
- autoload :Aggregations, 'active_groonga/aggregations'
39
- autoload :AssociationPreload, 'active_groonga/association_preload'
40
- autoload :Associations, 'active_groonga/associations'
41
- autoload :AttributeMethods, 'active_groonga/attribute_methods'
42
- autoload :AutosaveAssociation, 'active_groonga/autosave_association'
43
- autoload :Base, 'active_groonga/base'
44
- autoload :Batches, 'active_groonga/batches'
45
- autoload :Calculations, 'active_groonga/calculations'
46
- autoload :Callbacks, 'active_groonga/callbacks'
47
- autoload :Column, 'active_groonga/column'
48
- autoload :IdColumn, 'active_groonga/column'
49
- autoload :Dirty, 'active_groonga/dirty'
50
- autoload :DynamicRecordExpressionBuilder,
51
- 'active_groonga/dynamic_record_expression_builder'
52
- autoload :Migration, 'active_groonga/migration'
53
- autoload :Migrator, 'active_groonga/migration'
54
- autoload :NamedScope, 'active_groonga/named_scope'
55
- autoload :NestedAttributes, 'active_groonga/nested_attributes'
56
- autoload :Observing, 'active_groonga/observer'
57
- autoload :QueryCache, 'active_groonga/query_cache'
58
- autoload :Reflection, 'active_groonga/reflection'
59
- autoload :Schema, 'active_groonga/schema'
60
- autoload :SchemaDumper, 'active_groonga/schema_dumper'
61
- autoload :Serialization, 'active_groonga/serialization'
62
- autoload :SessionStore, 'active_groonga/session_store'
63
- autoload :TestCase, 'active_groonga/test_case'
64
- autoload :Timestamp, 'active_groonga/timestamp'
65
- autoload :Transactions, 'active_groonga/transactions'
66
- autoload :Validations, 'active_groonga/validations'
37
+ autoload :Base
38
+ autoload :Vector
39
+ autoload :Database
40
+ autoload :ResultSet
41
+ autoload :Schema
42
+ autoload :Persistence
43
+ autoload :Validations
44
+ autoload :Callbacks
67
45
 
68
- module Locking
69
- autoload :Optimistic, 'active_groonga/locking/optimistic'
70
- autoload :Pessimistic, 'active_groonga/locking/pessimistic'
46
+ autoload :Migrator
47
+ autoload :Migration
71
48
  end
72
49
  end
73
50
 
74
- # I18n.load_path << File.dirname(__FILE__) + '/active_groonga/locale/en.yml'
75
-
76
- if defined?(Rails::Configuration)
77
- require 'active_groonga/rails_support'
78
- end
51
+ I18n.load_path << (base_dir + 'active_groonga/locale/en.yml').to_s
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
1
+ # Copyright (C) 2009-2010 Kouhei Sutou <kou@clear-code.com>
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -13,165 +13,49 @@
13
13
  # License along with this library; if not, write to the Free Software
14
14
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
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.
16
+ require 'fileutils'
39
17
 
40
- require 'active_record/base'
18
+ require 'active_support/all'
41
19
 
42
20
  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
21
  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
22
+ extend ActiveModel::Naming
23
+ include ActiveModel::Conversion
24
+ include ActiveModel::AttributeMethods
25
+ attribute_method_suffix ""
26
+ attribute_method_suffix "="
61
27
 
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
28
+ cattr_accessor :logger, :instance_writer => false
119
29
 
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
30
+ cattr_reader :database_path, :instance_reader => false
126
31
 
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
32
+ @@configurations = {}
33
+ cattr_accessor :configurations
130
34
 
131
- # Stores the default scope for the class
132
- class_inheritable_accessor :default_scoping, :instance_writer => false
133
- self.default_scoping = []
35
+ @@context = nil
36
+ @@encoding = "utf8"
37
+ cattr_reader :encoding, :instance_reader => false
134
38
 
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
39
+ class << self
40
+ def configure(configuration)
41
+ case configuration
42
+ when String, Symbol
43
+ configure(configurations[configuration.to_s])
44
+ when Hash
45
+ self.database_path = configuration["database"]
46
+ self.encoding = configuration["encoding"]
47
+ end
48
+ end
145
49
 
146
- cattr_accessor :database_directory, :instance_writer => false
147
- @@database_directory = nil
50
+ def database
51
+ @@database ||= Database.new(database_path)
52
+ end
148
53
 
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)
54
+ def create(attributes=nil, &block)
173
55
  if attributes.is_a?(Array)
174
- attributes.collect { |attr| create(attr, &block) }
56
+ attributes.collect do |nested_attributes|
57
+ create(nested_attributes, &block)
58
+ end
175
59
  else
176
60
  object = new(attributes)
177
61
  yield(object) if block_given?
@@ -180,1436 +64,236 @@ module ActiveGroonga
180
64
  end
181
65
  end
182
66
 
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}"
67
+ def find(record_id, options={})
68
+ record_id = record_id.record_id if record_id.respond_to?(:record_id)
69
+ unless table.support_key?
70
+ begin
71
+ record_id = Integer(record_id)
72
+ rescue ArgumentError
73
+ return nil
336
74
  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 ||= [IdColumn.new(table)] + 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.collect do |column|
432
- if column.id?
433
- nil
434
- else
435
- "#{column.name}: #{column.type}"
436
- end
437
- end.compact.join(', ')
438
- "#{super}(#{attr_list})"
439
- else
440
- "#{super}(Table doesn't exist)"
441
- end
442
- end
443
-
444
- # Log and benchmark multiple statements in a single block. Example:
445
- #
446
- # Project.benchmark("Creating project") do
447
- # project = Project.create("name" => "stuff")
448
- # project.create_manager("name" => "David")
449
- # project.milestones << Milestone.find(:all)
450
- # end
451
- #
452
- # The benchmark is only recorded if the current level of the logger is less than or equal to the <tt>log_level</tt>,
453
- # which makes it easy to include benchmarking statements in production software that will remain inexpensive because
454
- # the benchmark will only be conducted if the log level is low enough.
455
- #
456
- # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
457
- def benchmark(title, log_level=Logger::DEBUG, use_silence=true)
458
- if logger && logger.level <= log_level
459
- result = nil
460
- ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
461
- logger.add(log_level, '%s (%.1fms)' % [title, ms])
462
- result
463
- else
464
- yield
75
+ return nil unless table.exist?(record_id)
465
76
  end
77
+ record = table[record_id]
78
+ return nil if record.nil?
79
+ instantiate(record)
466
80
  end
467
81
 
468
- # Overwrite the default class equality method to provide support for association proxies.
469
- def ===(object)
470
- object.is_a?(self)
471
- end
472
-
473
- # Returns the base AR subclass that this class descends from. If A
474
- # extends AR::Base, A.base_class will return A. If B descends from A
475
- # through some arbitrarily deep hierarchy, B.base_class will return A.
476
- def base_class
477
- class_of_active_groonga_descendant(self)
478
- end
479
-
480
- # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
481
- attr_accessor :abstract_class
482
-
483
- # Returns whether this class is a base AR class. If A is a base class and
484
- # B descends from A, then B.base_class will return B.
485
- def abstract_class?
486
- defined?(@abstract_class) && @abstract_class == true
487
- end
488
-
489
- def find(*args, &block)
490
- options = args.extract_options!
491
- options = options.merge(:expression => block) if block
492
- validate_find_options(options)
493
- set_readonly_option!(options)
494
-
495
- case args.first
496
- when :first
497
- find_initial(options)
498
- when :last
499
- find_last(options)
500
- when :all
501
- find_every(options)
502
- else
503
- find_from_ids(args, options)
504
- end
505
- end
506
-
507
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
508
- # same arguments to this method as you can to <tt>find(:first)</tt>.
509
- def first(*args)
510
- find(:first, *args)
511
- end
512
-
513
- # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
514
- # same arguments to this method as you can to <tt>find(:last)</tt>.
515
- def last(*args)
516
- find(:last, *args)
517
- end
518
-
519
- # This is an alias for find(:all). You can pass in all the same arguments to this method as you can
520
- # to find(:all)
521
- def all(*args)
522
- find(:all, *args)
523
- end
524
-
525
- def context
526
- Groonga::Context.default
527
- end
528
-
529
- def database
530
- context.database
531
- end
532
-
533
- def table
534
- context[groonga_table_name]
535
- end
536
-
537
- def groonga_table_name(name=nil)
538
- (name || table_name).to_s
539
- end
540
-
541
- def groonga_metadata_table_name(name)
542
- "meta-#{name}"
543
- end
544
-
545
- # Defines an "attribute" method (like +inheritance_column+ or
546
- # +table_name+). A new (class) method will be created with the
547
- # given name. If a value is specified, the new method will
548
- # return that value (as a string). Otherwise, the given block
549
- # will be used to compute the value of the method.
550
- #
551
- # The original method will be aliased, with the new name being
552
- # prefixed with "original_". This allows the new method to
553
- # access the original value.
554
- #
555
- # Example:
556
- #
557
- # class A < ActiveRecord::Base
558
- # define_attr_method :primary_key, "sysid"
559
- # define_attr_method( :inheritance_column ) do
560
- # original_inheritance_column + "_id"
561
- # end
562
- # end
563
- def define_attr_method(name, value=nil, &block)
564
- sing = class << self; self; end
565
- sing.send :alias_method, "original_#{name}", name
82
+ def select(options={})
566
83
  if block_given?
567
- sing.send :define_method, name, &block
568
- else
569
- # use eval instead of a block to work around a memory leak in dev
570
- # mode in fcgi
571
- sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
572
- end
573
- end
574
-
575
- def setup_database(spec=nil)
576
- case spec
577
- when nil
578
- raise DatabaseNotSpecified unless defined? RAILS_ENV
579
- setup_database(RAILS_ENV)
580
- when Symbol, String
581
- if configuration = configurations[spec.to_s]
582
- setup_database(configuration)
583
- else
584
- raise DatabaseNotSpecified, "#{spec} database is not configured"
84
+ records = table.select do |record|
85
+ yield(record)
585
86
  end
87
+ ResultSet.new(records, self, :expression => records.expression)
586
88
  else
587
- spec = spec.symbolize_keys
588
- unless spec.key?(:database)
589
- raise DatabaseNotSpecified, "groonga configuration does not specify database"
590
- end
591
- database_directory = spec[:database]
592
-
593
- Groonga::Context.default = nil
594
- Groonga::Context.default_options = {:encoding => spec[:encoding]}
595
- unless File.exist?(database_directory)
596
- FileUtils.mkdir_p(database_directory)
597
- end
598
- database_directory = File.expand_path(database_directory)
599
- database_file = File.join(database_directory, "database.groonga")
600
- if File.exist?(database_file)
601
- Groonga::Database.new(database_file)
602
- else
603
- Groonga::Database.create(:path => database_file)
604
- end
605
- self.database_directory = database_directory
89
+ ResultSet.new(table, self)
606
90
  end
607
91
  end
608
92
 
609
- def tables_directory
610
- directory = File.join(database_directory, "tables")
611
- FileUtils.mkdir_p(directory) unless File.exist?(directory)
612
- directory
93
+ def count
94
+ table.size
613
95
  end
614
96
 
615
- def columns_directory(table_name)
616
- directory = File.join(tables_directory, table_name.to_s, "columns")
617
- FileUtils.mkdir_p(directory) unless File.exist?(directory)
618
- directory
619
- end
620
-
621
- def index_columns_directory(table_name, target_table_name)
622
- directory = File.join(columns_directory(table_name), target_table_name)
623
- FileUtils.mkdir_p(directory) unless File.exist?(directory)
624
- directory
625
- end
626
-
627
- def metadata_directory
628
- directory = File.join(database_directory, "metadata")
629
- FileUtils.mkdir_p(directory) unless File.exist?(directory)
630
- directory
631
- end
632
-
633
- def count(expression=nil)
634
- if expression
635
- table.select do |record|
636
- expression.call(DynamicRecordExpressionBuilder.new(record))
637
- end.size
638
- else
639
- table.size
640
- end
641
- end
642
-
643
- private
644
- def find_initial(options)
645
- options.update(:limit => 1)
646
- find_every(options).first
647
- end
648
-
649
- def find_every(options)
650
- expression = options[:expression]
651
- include_associations = merge_includes(scope(:find, :include), options[:include])
652
-
653
- if include_associations.any? && references_eager_loaded_tables?(options)
654
- records = find_with_associations(options)
655
- else
656
- if expression
657
- records = table.select do |record|
658
- expression.call(DynamicRecordExpressionBuilder.new(record))
659
- end
660
- else
661
- records = table.select
662
- end
663
- sort_options = {}
664
- limit = options[:limit]
665
- offset = options[:offset]
666
- offset = Integer(offset) unless offset.nil?
667
- if limit and offset.nil?
668
- sort_options[:limit] = limit
669
- end
670
- records = records.sort([:key => ".:score", :order => :descending],
671
- sort_options)
672
- if offset
673
- in_target = false
674
- _records, records = records, []
675
- _records.each_with_index do |record, i|
676
- break if limit and limit <= records.size
677
- in_target = i >= offset unless in_target
678
- records << record if in_target
679
- end
680
- end
681
- records = records.collect do |record|
682
- instantiate(record, record.key.id, record.table.domain)
683
- end
684
- if include_associations.any?
685
- preload_associations(records, include_associations)
686
- end
687
- end
688
-
689
- records.each {|record| record.readonly!} if options[:readonly]
690
-
691
- records
97
+ def context
98
+ Groonga::Context.default
692
99
  end
693
100
 
694
- def find_from_ids(ids, options)
695
- expects_array = ids.first.kind_of?(Array)
696
- return ids.first if expects_array && ids.first.empty?
697
-
698
- ids = ids.flatten.compact.uniq
699
-
700
- case ids.size
701
- when 0
702
- raise RecordNotFound, "Couldn't find #{name} without an ID"
703
- when 1
704
- result = find_one(ids.first, options)
705
- expects_array ? [result] : result
706
- else
707
- find_some(ids, options)
708
- end
101
+ def encoding=(new_encoding)
102
+ return if @@encoding == new_encoding
103
+ @@encoding = new_encoding
104
+ database_opened = !context.database.nil?
105
+ Groonga::Context.default = nil
106
+ Groonga::Context.default_options = {:encoding => @@encoding}
107
+ database.reopen if database_opened
709
108
  end
710
109
 
711
- def find_one(id, options)
712
- if id.is_a?(Groonga::Record)
713
- record = id
110
+ def table_name(name=nil)
111
+ if name.nil?
112
+ @table_name ||= model_name.plural
714
113
  else
715
- if id.is_a?(ActiveGroonga::Base)
716
- id = id.id
717
- else
718
- id = Integer(id)
719
- end
720
- record = Groonga::Record.new(table, id)
721
- end
722
- result = instantiate(record)
723
- if result.nil?
724
- raise RecordNotFound, "Couldn't find #{name} with ID=#{id}"
114
+ self.table_name = name
725
115
  end
726
- result
727
116
  end
728
117
 
729
- def find_some(ids, options)
730
- result = ids.collect do |id|
731
- context[id]
732
- end
733
- n_not_found_ids = result.count(nil)
734
- if n_not_found_ids.zero?
735
- result
736
- else
737
- raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids}) (found #{result.compact.size} results, but was looking for #{ids.size})"
738
- end
118
+ def table_name=(name)
119
+ @table_name = name
739
120
  end
740
121
 
741
- def merge_includes(first, second)
742
- (safe_to_array(first) + safe_to_array(second)).uniq
122
+ def table
123
+ @table ||= context[table_name]
743
124
  end
744
125
 
745
- # ugly. derived from Active Record. FIXME: remove it.
746
- def safe_to_array(o)
747
- case o
748
- when NilClass
749
- []
750
- when Array
751
- o
752
- else
753
- [o]
126
+ def define_column_accessors
127
+ attribute_names = table.columns.collect do |column|
128
+ column.local_name
754
129
  end
130
+ define_attribute_methods(attribute_names)
755
131
  end
756
132
 
757
- VALID_FIND_OPTIONS = [:expression, :readonly, :limit, :offset]
758
- def validate_find_options(options)
759
- options.assert_valid_keys(VALID_FIND_OPTIONS)
760
- end
761
-
762
- def set_readonly_option!(options) #:nodoc:
763
- # Inherit :readonly from finder scope if set. Otherwise,
764
- # if :joins is not blank then :readonly defaults to true.
765
- unless options.has_key?(:readonly)
766
- if scoped_readonly = scope(:find, :readonly)
767
- options[:readonly] = scoped_readonly
768
- elsif !options[:joins].blank? && !options[:select]
769
- options[:readonly] = true
770
- end
133
+ def inspect
134
+ return super if table.nil?
135
+ columns_info = table.columns.collect do |column|
136
+ "#{column.local_name}: #{column.range.name}"
771
137
  end
138
+ "#{name}(#{columns_info.join(', ')})"
772
139
  end
773
140
 
774
- # Guesses the table name, but does not decorate it with prefix and suffix information.
775
- def undecorated_table_name(class_name = base_class.name)
776
- table_name = class_name.to_s.demodulize.underscore
777
- table_name = table_name.pluralize if pluralize_table_names
778
- table_name
779
- end
780
-
781
- # Finder methods must instantiate through this method to work with the
782
- # single-table inheritance model that makes it possible to create
783
- # objects of different types from the same table.
784
- def instantiate(record, id=nil, table=nil)
785
- id ||= record.id
786
- table ||= record.table
787
-
788
- subclass_name = nil
789
- if record.have_column?(inheritance_column)
790
- subclass_name = record[inheritance_column]
791
- end
792
-
793
- if subclass_name.blank? or !columns_hash.include?(inheritance_column)
794
- object = allocate
795
- else
796
- begin
797
- object = compute_type(subclass_name).allocate
798
- rescue NameError
799
- raise SubclassNotFound,
800
- "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
801
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
802
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
803
- "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
804
- end
805
- end
806
-
807
- object.instance_variable_set("@id", id)
808
- object.instance_variable_set("@score", record.score)
809
- attributes = {}
810
- table.columns.each do |column|
811
- column_name = column.local_name
812
- attributes[column_name] = record[".#{column_name}"]
813
- end
814
- object.instance_variable_set("@attributes", attributes)
815
- object.instance_variable_set("@attributes_cache", Hash.new)
816
-
817
- if object.respond_to_without_attributes?(:after_find)
818
- object.send(:callback, :after_find)
141
+ def instantiate(record)
142
+ object = new(record)
143
+ object.instance_variable_set("@id", record.id)
144
+ if record.support_key?
145
+ object.instance_variable_set("@key", record.key)
819
146
  end
820
-
821
- if object.respond_to_without_attributes?(:after_initialize)
822
- object.send(:callback, :after_initialize)
823
- end
824
-
147
+ object.instance_variable_set("@new_record", false)
825
148
  object
826
149
  end
827
150
 
828
- # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
829
- # that are turned into <tt>find(:first) {|record| record["user_name"] == user_name}</tt> and
830
- # <tt>find(:first) {|record| (record["user_name"] ==
831
- # user_name) & (record["password"] == password)}</tt> respectively. Also works for
832
- # <tt>find(:all)</tt> by using
833
- # <tt>find_all_by_amount(50)</tt> that is turned into
834
- # <tt>find(:all) {|record| record["amount"] == 50}</tt>.
835
- #
836
- # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
837
- # is actually <tt>find_all_by_amount(amount, options)</tt>.
838
- #
839
- # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
840
- # are turned into scoped(:expression => Proc.new {|record| record["user_name"] == user_name}) and scoped(:expression => Proc.new {|record| (record["user_name"] == user_name) & (record["password"] == password)})
841
- # respectively.
842
- #
843
- # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
844
- # attempts to use it do not run through method_missing.
845
- def method_missing(method_id, *arguments, &block)
846
- if match = ActiveRecord::DynamicFinderMatch.match(method_id)
847
- attribute_names = match.attribute_names
848
- super unless all_attributes_exists?(attribute_names)
849
- if match.finder?
850
- finder = match.finder
851
- bang = match.bang?
852
- # def self.find_by_login_and_activated(*args)
853
- # options = args.extract_options!
854
- # expression = construct_expression_from_arguments(
855
- # [:login,:activated],
856
- # args
857
- # )
858
- # finder_options = { :expression => expression }
859
- # validate_find_options(options)
860
- # set_readonly_option!(options)
861
- #
862
- # if options[:expression]
863
- # with_scope(:find => finder_options) do
864
- # find(:first, options)
865
- # end
866
- # else
867
- # find(:first, options.merge(finder_options))
868
- # end
869
- # end
870
- self.class_eval <<-EOC, __FILE__, __LINE__
871
- def self.#{method_id}(*args)
872
- options = args.extract_options!
873
- expression = construct_expression_from_arguments(
874
- [:#{attribute_names.join(',:')}],
875
- args
876
- )
877
- finder_options = {:expression => expression}
878
- validate_find_options(options)
879
- set_readonly_option!(options)
880
-
881
- #{'result = ' if bang}if options[:expression]
882
- with_scope(:find => finder_options) do
883
- find(:#{finder}, options)
884
- end
885
- else
886
- find(:#{finder}, options.merge(finder_options))
887
- end
888
- #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
889
- end
890
- EOC
891
- send(method_id, *arguments)
892
- elsif match.instantiator?
893
- instantiator = match.instantiator
894
- # def self.find_or_create_by_user_id(*args)
895
- # guard_protected_attributes = false
896
- #
897
- # if args[0].is_a?(Hash)
898
- # guard_protected_attributes = true
899
- # attributes = args[0].with_indifferent_access
900
- # find_expression = attributes.slice(*[:user_id])
901
- # else
902
- # attributes = construct_attributes_from_arguments([:user_id], args)
903
- # find_expression = construct_expression_from_arguments([:user_id], args)
904
- # end
905
- #
906
- # options = { :expression => find_expression }
907
- # set_readonly_option!(options)
908
- #
909
- # record = find(:first, options)
910
- #
911
- # if record.nil?
912
- # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
913
- # yield(record) if block_given?
914
- # record.save
915
- # record
916
- # else
917
- # record
918
- # end
919
- # end
920
- self.class_eval <<-EOC, __FILE__, __LINE__
921
- def self.#{method_id}(*args)
922
- guard_protected_attributes = false
923
-
924
- if args[0].is_a?(Hash)
925
- guard_protected_attributes = true
926
- attributes = args[0].with_indifferent_access
927
- find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
928
- else
929
- find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
930
- end
931
-
932
- find_expression = construct_expression_from_attributes(find_attributes)
933
- options = { :expression => find_expression }
934
- set_readonly_option!(options)
935
-
936
- record = find(:first, options)
937
-
938
- if record.nil?
939
- record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
940
- #{'yield(record) if block_given?'}
941
- #{'record.save' if instantiator == :create}
942
- record
943
- else
944
- record
945
- end
946
- end
947
- EOC
948
- send(method_id, *arguments, &block)
151
+ def define_method_attribute(name)
152
+ generated_attribute_methods.module_eval do
153
+ define_method(name) do
154
+ read_attribute(name)
949
155
  end
950
- elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
951
- attribute_names = match.attribute_names
952
- super unless all_attributes_exists?(attribute_names)
953
- if match.scope?
954
- self.class_eval <<-EOC, __FILE__, __LINE__
955
- def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
956
- options = args.extract_options! # options = args.extract_options!
957
- expression = construct_expression_from_arguments( # expression = construct_expression_from_arguments(
958
- [:#{attribute_names.join(',:')}], args # [:user_name, :password], args
959
- ) # )
960
- #
961
- scoped(:expression => expression) # scoped(:expression => expression)
962
- end # end
963
- EOC
964
- send(method_id, *arguments)
965
- end
966
- else
967
- super
968
156
  end
969
157
  end
970
158
 
971
- def construct_attributes_from_arguments(attribute_names, arguments)
972
- attributes = {}
973
- attribute_names.each_with_index do |name, i|
974
- attributes[name] = arguments[i]
975
- end
976
- attributes
977
- end
978
-
979
- def construct_expression_from_attributes(attributes)
980
- Proc.new do |record|
981
- builder = nil
982
- attributes.each do |name, value|
983
- expression = (record[name] == value)
984
- if builder
985
- builder = builder & expression
986
- else
987
- builder = expression
988
- end
989
- end
990
- builder
991
- end
992
- end
993
-
994
- def construct_expression_from_arguments(attribute_names, arguments)
995
- Proc.new do |record|
996
- builder = nil
997
- attribute_names.each_with_index do |name, i|
998
- expression = (record[name] == arguments[i])
999
- if builder
1000
- builder = builder & expression
1001
- else
1002
- builder = expression
1003
- end
1004
- end
1005
- builder
1006
- end
1007
- end
1008
-
1009
- # Similar in purpose to +expand_hash_expression_for_aggregates+.
1010
- def expand_attribute_names_for_aggregates(attribute_names)
1011
- expanded_attribute_names = []
1012
- attribute_names.each do |attribute_name|
1013
- unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
1014
- aggregate_mapping(aggregation).each do |field_attr, aggregate_attr|
1015
- expanded_attribute_names << field_attr
1016
- end
1017
- else
1018
- expanded_attribute_names << attribute_name
159
+ def define_method_attribute=(name)
160
+ generated_attribute_methods.module_eval do
161
+ define_method("#{name}=") do |new_value|
162
+ write_attribute(name, new_value)
1019
163
  end
1020
164
  end
1021
- expanded_attribute_names
1022
- end
1023
-
1024
- def all_attributes_exists?(attribute_names)
1025
- attribute_names = expand_attribute_names_for_aggregates(attribute_names)
1026
- attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
1027
165
  end
1028
166
 
1029
- # Nest the type name in the same module as this class.
1030
- # Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
1031
- def type_name_with_module(type_name)
1032
- if store_full_sti_class
1033
- type_name
1034
- else
1035
- (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
1036
- end
167
+ def database_path=(path)
168
+ path = Pathname(path) if path.is_a?(String)
169
+ @@database_path = path
170
+ @@database = nil
1037
171
  end
1038
172
 
1039
- # Test whether the given method and optional key are scoped.
1040
- def scoped?(method, key = nil) #:nodoc:
1041
- if current_scoped_methods && (scope = current_scoped_methods[method])
1042
- !key || !scope[key].nil?
1043
- end
173
+ def reference_class(column_name, klass)
174
+ @reference_mapping ||= {}
175
+ column_name = column_name.to_s
176
+ @reference_mapping[column_name] = klass
1044
177
  end
1045
178
 
1046
- # Retrieve the scope for the given method and optional key.
1047
- def scope(method, key = nil) #:nodoc:
1048
- if current_scoped_methods && (scope = current_scoped_methods[method])
1049
- key ? scope[key] : scope
1050
- end
179
+ def custom_reference_class(column_name)
180
+ @reference_mapping ||= {}
181
+ column_name = column_name.to_s
182
+ @reference_mapping[column_name]
1051
183
  end
1052
184
 
1053
- def scoped_methods #:nodoc:
1054
- Thread.current[:"#{self}_scoped_methods"] ||= default_scoping.dup
185
+ def i18n_scope
186
+ :activegroonga
1055
187
  end
1056
188
 
1057
- def current_scoped_methods #:nodoc:
1058
- scoped_methods.last
1059
- end
1060
-
1061
- # Returns the class type of the record using the current module as a prefix. So descendants of
1062
- # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
1063
- def compute_type(type_name)
1064
- modularized_name = type_name_with_module(type_name)
1065
- silence_warnings do
1066
- begin
1067
- class_eval(modularized_name, __FILE__, __LINE__)
1068
- rescue NameError
1069
- class_eval(type_name, __FILE__, __LINE__)
1070
- end
1071
- end
1072
- end
1073
-
1074
- # Returns the class descending directly from ActiveGroonga::Base or an
1075
- # abstract class, if any, in the inheritance hierarchy.
1076
- def class_of_active_groonga_descendant(klass)
1077
- if klass.superclass == Base || klass.superclass.abstract_class?
1078
- klass
1079
- elsif klass.superclass.nil?
1080
- raise ActiveGroongaError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
1081
- else
1082
- class_of_active_record_descendant(klass.superclass)
1083
- end
189
+ protected
190
+ def instance_method_already_implemented?(method_name)
191
+ super(method_name)
1084
192
  end
1085
193
  end
1086
194
 
1087
- def initialize(attributes=nil)
195
+ def initialize(record_or_attributes=nil)
196
+ self.class.define_column_accessors
1088
197
  @id = nil
1089
- @score = nil
1090
- @attributes = attributes_from_column_definition
1091
- @attributes_cache = {}
198
+ @key = nil
1092
199
  @new_record = true
1093
- ensure_proper_type
1094
- self.attributes = attributes unless attributes.nil?
1095
- self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
1096
- result = yield self if block_given?
1097
- callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
1098
- result
1099
- end
1100
-
1101
- # A model instance's primary key is always available as model.id
1102
- # whether you name it the default 'id' or set it to something else.
1103
- def id
1104
- @id
1105
- end
1106
-
1107
- def score
1108
- @score
1109
- end
1110
-
1111
- # Returns a String, which Action Pack uses for constructing an URL to this
1112
- # object. The default implementation returns this record's id as a String,
1113
- # or nil if this record's unsaved.
1114
- #
1115
- # For example, suppose that you have a User model, and that you have a
1116
- # <tt>map.resources :users</tt> route. Normally, +user_path+ will
1117
- # construct a path with the user object's 'id' in it:
1118
- #
1119
- # user = User.find_by_name('Phusion')
1120
- # user_path(user) # => "/users/1"
1121
- #
1122
- # You can override +to_param+ in your model to make +user_path+ construct
1123
- # a path using the user's name instead of the user's id:
1124
- #
1125
- # class User < ActiveRecord::Base
1126
- # def to_param # overridden
1127
- # name
1128
- # end
1129
- # end
1130
- #
1131
- # user = User.find_by_name('Phusion')
1132
- # user_path(user) # => "/users/Phusion"
1133
- def to_param
1134
- # We can't use alias_method here, because method 'id' optimizes itself on the fly.
1135
- (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
1136
- end
1137
-
1138
- # Sets the primary ID.
1139
- def id=(value)
1140
- @id = value
1141
- end
1142
-
1143
- # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
1144
- def new_record?
1145
- @new_record || false
1146
- end
1147
-
1148
- # :call-seq:
1149
- # save(perform_validation = true)
1150
- #
1151
- # Saves the model.
1152
- #
1153
- # If the model is new a record gets created in the database, otherwise
1154
- # the existing record gets updated.
1155
- #
1156
- # If +perform_validation+ is true validations run. If any of them fail
1157
- # the action is cancelled and +save+ returns +false+. If the flag is
1158
- # false validations are bypassed altogether. See
1159
- # ActiveRecord::Validations for more information.
1160
- #
1161
- # There's a series of callbacks associated with +save+. If any of the
1162
- # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
1163
- # +save+ returns +false+. See ActiveRecord::Callbacks for further
1164
- # details.
1165
- def save
1166
- create_or_update
1167
- end
1168
-
1169
- # Saves the model.
1170
- #
1171
- # If the model is new a record gets created in the database, otherwise
1172
- # the existing record gets updated.
1173
- #
1174
- # With <tt>save!</tt> validations always run. If any of them fail
1175
- # ActiveGroonga::RecordInvalid gets raised. See ActiveRecord::Validations
1176
- # for more information.
1177
- #
1178
- # There's a series of callbacks associated with <tt>save!</tt>. If any of
1179
- # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
1180
- # and <tt>save!</tt> raises ActiveGroonga::RecordNotSaved. See
1181
- # ActiveRecord::Callbacks for further details.
1182
- def save!
1183
- create_or_update || raise(RecordNotSaved)
1184
- end
1185
-
1186
- # Deletes the record in the database and freezes this instance to
1187
- # reflect that no changes should be made (since they can't be
1188
- # persisted). Returns the frozen instance.
1189
- #
1190
- # The row is simply removed with a SQL +DELETE+ statement on the
1191
- # record's primary key, and no callbacks are executed.
1192
- #
1193
- # To enforce the object's +before_destroy+ and +after_destroy+
1194
- # callbacks, Observer methods, or any <tt>:dependent</tt> association
1195
- # options, use <tt>#destroy</tt>.
1196
- def delete
1197
- self.class.delete(id) unless new_record?
1198
- freeze
1199
- end
1200
-
1201
- # Deletes the record in the database and freezes this instance to reflect that no changes should
1202
- # be made (since they can't be persisted).
1203
- def destroy
1204
- self.class.table.delete(id) unless new_record?
1205
- freeze
1206
- end
1207
-
1208
- # Updates a single attribute and saves the record without going through the normal validation procedure.
1209
- # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
1210
- # in Base is replaced with this when the validations module is mixed in, which it is by default.
1211
- def update_attribute(name, value)
1212
- send(name.to_s + '=', value)
1213
- save(false)
1214
- end
1215
-
1216
- # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
1217
- # fail and false will be returned.
1218
- def update_attributes(attributes)
1219
- self.attributes = attributes
1220
- save
1221
- end
1222
-
1223
- # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
1224
- def update_attributes!(attributes)
1225
- self.attributes = attributes
1226
- save!
1227
- end
1228
-
1229
- # Reloads the attributes of this object from the database.
1230
- # The optional options argument is passed to find when reloading so you
1231
- # may do e.g. record.reload(:lock => true) to reload the same record with
1232
- # an exclusive row lock.
1233
- def reload(options = nil)
1234
- clear_aggregation_cache
1235
- clear_association_cache
1236
- @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
200
+ @destroyed = false
201
+ @attributes = initial_attributes
1237
202
  @attributes_cache = {}
1238
- self
1239
- end
1240
-
1241
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
1242
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
1243
- # (Alias for the protected read_attribute method).
1244
- def [](attr_name)
1245
- read_attribute(attr_name)
203
+ if record_or_attributes.is_a?(Groonga::Record)
204
+ reload_attributes(record_or_attributes)
205
+ else
206
+ reload_attributes
207
+ self.attributes = (record_or_attributes || {})
208
+ end
1246
209
  end
1247
210
 
1248
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
1249
- # (Alias for the protected write_attribute method).
1250
- def []=(attr_name, value)
1251
- write_attribute(attr_name, value)
211
+ def have_column?(name)
212
+ table.have_column?(name)
1252
213
  end
1253
214
 
1254
- # Allows you to set all the attributes at once by passing in a hash with keys
1255
- # matching the attribute names (which again matches the column names).
1256
- #
1257
- # If +guard_protected_attributes+ is true (the default), then sensitive
1258
- # attributes can be protected from this form of mass-assignment by using
1259
- # the +attr_protected+ macro. Or you can alternatively specify which
1260
- # attributes *can* be accessed with the +attr_accessible+ macro. Then all the
1261
- # attributes not included in that won't be allowed to be mass-assigned.
1262
- #
1263
- # class User < ActiveGroonga::Base
1264
- # attr_protected :is_admin
1265
- # end
1266
- #
1267
- # user = User.new
1268
- # user.attributes = { :username => 'Phusion', :is_admin => true }
1269
- # user.username # => "Phusion"
1270
- # user.is_admin? # => false
1271
- #
1272
- # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
1273
- # user.is_admin? # => true
1274
- def attributes=(new_attributes, guard_protected_attributes = true)
1275
- return if new_attributes.nil?
1276
- attributes = new_attributes.dup
1277
- attributes.stringify_keys!
1278
-
1279
- multi_parameter_attributes = []
1280
- attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
1281
-
1282
- attributes.each do |k, v|
1283
- if k.include?("(")
1284
- multi_parameter_attributes << [ k, v ]
1285
- else
1286
- respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
1287
- end
1288
- end
1289
-
1290
- assign_multiparameter_attributes(multi_parameter_attributes)
215
+ def id
216
+ @id
1291
217
  end
1292
218
 
1293
- # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
1294
- def attributes
1295
- self.attribute_names.inject({}) do |attrs, name|
1296
- attrs[name] = read_attribute(name)
1297
- attrs
1298
- end
219
+ def key
220
+ @key
1299
221
  end
1300
222
 
1301
- # Returns a hash of attributes before typecasting and deserialization.
1302
- def attributes_before_type_cast
1303
- self.attribute_names.inject({}) do |attrs, name|
1304
- attrs[name] = read_attribute_before_type_cast(name)
1305
- attrs
1306
- end
223
+ def key=(key)
224
+ raise NoKeyTableError.new(table) unless table.support_key?
225
+ raise KeyOverrideError.new(table, key) unless new_record?
226
+ @key = key
1307
227
  end
1308
228
 
1309
- # Returns an <tt>#inspect</tt>-like string for the value of the
1310
- # attribute +attr_name+. String attributes are elided after 50
1311
- # characters, and Date and Time attributes are returned in the
1312
- # <tt>:db</tt> format. Other attributes return the value of
1313
- # <tt>#inspect</tt> without modification.
1314
- #
1315
- # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
1316
- #
1317
- # person.attribute_for_inspect(:name)
1318
- # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
1319
- #
1320
- # person.attribute_for_inspect(:created_at)
1321
- # # => '"2009-01-12 04:48:57"'
1322
- def attribute_for_inspect(attr_name)
1323
- value = read_attribute(attr_name)
1324
-
1325
- if value.is_a?(String) && value.length > 50
1326
- "#{value[0..50]}...".inspect
1327
- elsif value.is_a?(Date) || value.is_a?(Time)
1328
- %("#{value.to_s(:db)}")
229
+ def record_id
230
+ if table.support_key?
231
+ key
1329
232
  else
1330
- value.inspect
233
+ id
1331
234
  end
1332
235
  end
1333
236
 
1334
- # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
1335
- # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
1336
- def attribute_present?(attribute)
1337
- value = read_attribute(attribute)
1338
- !value.blank?
1339
- end
1340
-
1341
- # Returns true if the given attribute is in the attributes hash
1342
- def has_attribute?(attr_name)
1343
- @attributes.has_key?(attr_name.to_s)
1344
- end
1345
-
1346
- # Returns an array of names for the attributes available on this object sorted alphabetically.
1347
- def attribute_names
1348
- @attributes.keys.sort
237
+ def record_raw_id
238
+ id
1349
239
  end
1350
240
 
1351
- # Returns the column object for the named attribute.
1352
- def column_for_attribute(name)
1353
- self.class.columns_hash[name.to_s]
241
+ def attributes
242
+ @attributes
1354
243
  end
1355
244
 
1356
- # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
1357
- def ==(comparison_object)
1358
- comparison_object.equal?(self) ||
1359
- (comparison_object.instance_of?(self.class) &&
1360
- comparison_object.id == id &&
1361
- !comparison_object.new_record?)
245
+ def attributes=(attributes)
246
+ attributes.each do |key, value|
247
+ send("#{key}=", value)
248
+ end
1362
249
  end
1363
250
 
1364
- # Delegates to ==
1365
- def eql?(comparison_object)
1366
- self == (comparison_object)
251
+ def ==(other)
252
+ other.is_a?(self.class) and other.id == id
1367
253
  end
1368
254
 
1369
- # Delegates to id in order to allow two records of the same type and id to work with something like:
1370
- # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
1371
255
  def hash
1372
256
  id.hash
1373
257
  end
1374
258
 
1375
- # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
1376
- def freeze
1377
- @attributes.freeze; self
259
+ def read_attribute(name)
260
+ @attributes[name]
1378
261
  end
1379
262
 
1380
- # Returns +true+ if the attributes hash has been frozen.
1381
- def frozen?
1382
- @attributes.frozen?
263
+ def write_attribute(name, value)
264
+ @attributes[name] = value
1383
265
  end
1384
266
 
1385
- # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
1386
- # attributes will be marked as read only since they cannot be saved.
1387
- def readonly?
1388
- defined?(@readonly) && @readonly == true
1389
- end
1390
-
1391
- # Marks this record as read only.
1392
- def readonly!
1393
- @readonly = true
1394
- end
1395
-
1396
- # Returns the contents of the record as a nicely formatted string.
1397
267
  def inspect
1398
- attributes_as_nice_string = self.class.column_names.collect { |name|
1399
- if has_attribute?(name) || new_record?
1400
- "#{name}: #{attribute_for_inspect(name)}"
1401
- end
1402
- }.compact.join(", ")
1403
- "#<#{self.class} #{attributes_as_nice_string}>"
1404
- end
1405
-
1406
- private
1407
- def create_or_update
1408
- raise ReadOnlyRecord if readonly?
1409
- result = new_record? ? create : update
1410
- result != false
1411
- end
1412
-
1413
- # Updates the associated record with values matching those of the instance attributes.
1414
- # Returns the number of affected rows.
1415
- def update(attribute_names=@attributes.keys)
1416
- attribute_names = remove_readonly_attributes(attribute_names)
1417
- table = self.class.table
1418
- quoted_attributes = attributes_with_quotes(false, attribute_names)
1419
- quoted_attributes.each do |name, value|
1420
- column = table.column(name)
1421
- next if column.nil?
1422
- column[id] = value
1423
- end
1424
- end
1425
-
1426
- # Creates a record with values matching those of the instance attributes
1427
- # and returns its id.
1428
- def create
1429
- table = self.class.table
1430
- record = table.add
1431
- quoted_attributes = attributes_with_quotes
1432
- quoted_attributes.each do |name, value|
1433
- record[name] = value
1434
- end
1435
- self.id = record.id
1436
- @new_record = false
1437
- id
1438
- end
1439
-
1440
- # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
1441
- # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
1442
- # set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
1443
- # Message class in that example.
1444
- def ensure_proper_type
1445
- unless self.class.descends_from_active_groonga?
1446
- write_attribute(self.class.inheritance_column, self.class.sti_name)
1447
- end
1448
- end
1449
-
1450
- def convert_number_column_value(value)
1451
- if value == false
1452
- 0
1453
- elsif value == true
1454
- 1
1455
- elsif value.is_a?(String) && value.blank?
1456
- nil
268
+ inspected_attributes = []
269
+ if table.support_key?
270
+ inspected_attributes << "key: #{key}"
1457
271
  else
1458
- value
272
+ inspected_attributes << "id: #{id}"
1459
273
  end
1460
- end
1461
-
1462
- def remove_attributes_protected_from_mass_assignment(attributes)
1463
- safe_attributes =
1464
- if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
1465
- attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1466
- elsif self.class.protected_attributes.nil?
1467
- attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1468
- elsif self.class.accessible_attributes.nil?
1469
- attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1470
- else
1471
- raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
1472
- end
1473
-
1474
- removed_attributes = attributes.keys - safe_attributes.keys
1475
-
1476
- if removed_attributes.any?
1477
- log_protected_attribute_removal(removed_attributes)
1478
- end
1479
-
1480
- safe_attributes
1481
- end
1482
-
1483
- # Removes attributes which have been marked as readonly.
1484
- def remove_readonly_attributes(attributes)
1485
- unless self.class.readonly_attributes.nil?
1486
- attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) }
1487
- else
1488
- attributes
1489
- end
1490
- end
1491
-
1492
- def log_protected_attribute_removal(*attributes)
1493
- logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
1494
- end
1495
-
1496
- # The primary key and inheritance column can never be set by mass-assignment for security reasons.
1497
- def attributes_protected_by_default
1498
- default = [ self.class.primary_key, self.class.inheritance_column ]
1499
- default << 'id' unless self.class.primary_key.eql? 'id'
1500
- default
1501
- end
1502
-
1503
- # Initializes the attributes array with keys matching the columns from the linked table and
1504
- # the values matching the corresponding default value of that column, so
1505
- # that a new instance, or one populated from a passed-in Hash, still has all the attributes
1506
- # that instances loaded from the database would.
1507
- def attributes_from_column_definition
1508
- self.class.columns.inject({}) do |attributes, column|
1509
- attributes[column.name] = column.default
1510
- attributes
1511
- end
1512
- end
1513
-
1514
- # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
1515
- # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
1516
- # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
1517
- # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
1518
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
1519
- # s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.
1520
- def assign_multiparameter_attributes(pairs)
1521
- execute_callstack_for_multiparameter_attributes(
1522
- extract_callstack_for_multiparameter_attributes(pairs)
1523
- )
1524
- end
1525
-
1526
- def execute_callstack_for_multiparameter_attributes(callstack)
1527
- errors = []
1528
- callstack.each do |name, values|
1529
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
1530
- if values.empty?
1531
- send(name + "=", nil)
1532
- else
1533
- begin
1534
- value = if Time == klass
1535
- instantiate_time_object(name, values)
1536
- elsif Date == klass
1537
- begin
1538
- Date.new(*values)
1539
- rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
1540
- 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
1541
- end
1542
- else
1543
- klass.new(*values)
1544
- end
1545
-
1546
- send(name + "=", value)
1547
- rescue => ex
1548
- errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
1549
- end
1550
- end
1551
- end
1552
- unless errors.empty?
1553
- raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
1554
- end
1555
- end
1556
-
1557
- def extract_callstack_for_multiparameter_attributes(pairs)
1558
- attributes = { }
1559
-
1560
- for pair in pairs
1561
- multiparameter_name, value = pair
1562
- attribute_name = multiparameter_name.split("(").first
1563
- attributes[attribute_name] = [] unless attributes.include?(attribute_name)
1564
-
1565
- unless value.empty?
1566
- attributes[attribute_name] <<
1567
- [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
1568
- end
274
+ @attributes.each do |key, value|
275
+ inspected_attributes << "#{key}: #{value.inspect}"
1569
276
  end
1570
-
1571
- attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
277
+ "\#<#{self.class.name} #{inspected_attributes.join(', ')}>"
1572
278
  end
1573
279
 
1574
- # Returns a copy of the attributes hash where all the values have been safely quoted for use in
1575
- # an SQL statement.
1576
- def attributes_with_quotes(include_readonly_attributes=true, attribute_names=@attributes.keys)
1577
- quoted = {}
1578
- attribute_names.each do |name|
1579
- column = column_for_attribute(name)
1580
- next if column.nil? or column.id?
1581
-
1582
- value = read_attribute(name)
1583
- # We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML.
1584
- if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))
1585
- value = value.to_yaml
1586
- end
1587
- quoted[name] = column.quote(value)
1588
- end
1589
- include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
280
+ def table
281
+ @table ||= self.class.table
1590
282
  end
1591
283
 
1592
- # Quote strings appropriately for SQL statements.
1593
- def quote_value(value, column=nil)
1594
- if column
1595
- column.quote(value)
1596
- else
1597
- value
1598
- end
284
+ private
285
+ def attribute(name)
286
+ read_attribute(name)
1599
287
  end
1600
288
 
1601
- def clone_attribute_value(reader_method, attribute_name)
1602
- value = send(reader_method, attribute_name)
1603
- value.duplicable? ? value.clone : value
1604
- rescue TypeError, NoMethodError
1605
- value
289
+ def attribute=(name, value)
290
+ write_attribute(name, value)
1606
291
  end
1607
292
 
293
+ include Persistence
1608
294
  include Validations
1609
- include AttributeMethods
1610
- include Dirty
1611
- include Callbacks, Observing, Timestamp
1612
- include Associations
1613
- include Aggregations, Reflection
295
+ include Callbacks
1614
296
  end
1615
297
  end
298
+
299
+ ActiveSupport.run_load_hooks(:active_groonga, ActiveGroonga::Base)