activegroonga 0.0.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)