activerecord-postgresql-extensions 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +4 -0
  3. data/Gemfile +1 -0
  4. data/Guardfile +3 -3
  5. data/MIT-LICENSE +1 -1
  6. data/README.rdoc +10 -3
  7. data/lib/active_record/postgresql_extensions/adapter_extensions.rb +100 -60
  8. data/lib/active_record/postgresql_extensions/constraints.rb +13 -17
  9. data/lib/active_record/postgresql_extensions/event_triggers.rb +129 -0
  10. data/lib/active_record/postgresql_extensions/extensions.rb +14 -15
  11. data/lib/active_record/postgresql_extensions/features.rb +80 -41
  12. data/lib/active_record/postgresql_extensions/functions.rb +1 -1
  13. data/lib/active_record/postgresql_extensions/geometry.rb +6 -8
  14. data/lib/active_record/postgresql_extensions/indexes.rb +19 -11
  15. data/lib/active_record/postgresql_extensions/languages.rb +1 -1
  16. data/lib/active_record/postgresql_extensions/materialized_views.rb +272 -0
  17. data/lib/active_record/postgresql_extensions/permissions.rb +60 -22
  18. data/lib/active_record/postgresql_extensions/roles.rb +18 -7
  19. data/lib/active_record/postgresql_extensions/rules.rb +5 -0
  20. data/lib/active_record/postgresql_extensions/schemas.rb +39 -3
  21. data/lib/active_record/postgresql_extensions/sequences.rb +6 -3
  22. data/lib/active_record/postgresql_extensions/tables.rb +47 -19
  23. data/lib/active_record/postgresql_extensions/tablespaces.rb +1 -1
  24. data/lib/active_record/postgresql_extensions/text_search.rb +3 -3
  25. data/lib/active_record/postgresql_extensions/triggers.rb +3 -3
  26. data/lib/active_record/postgresql_extensions/types.rb +104 -1
  27. data/lib/active_record/postgresql_extensions/utils.rb +35 -13
  28. data/lib/active_record/postgresql_extensions/vacuum.rb +1 -1
  29. data/lib/active_record/postgresql_extensions/version.rb +1 -1
  30. data/lib/active_record/postgresql_extensions/views.rb +137 -6
  31. data/lib/activerecord-postgresql-extensions.rb +13 -11
  32. data/test/{adapter_tests.rb → adapter_extensions_tests.rb} +96 -3
  33. data/test/constraints_tests.rb +216 -104
  34. data/test/event_triggers_tests.rb +109 -0
  35. data/test/extensions_tests.rb +47 -39
  36. data/test/functions_tests.rb +47 -38
  37. data/test/geometry_tests.rb +268 -135
  38. data/test/{index_tests.rb → indexes_tests.rb} +16 -16
  39. data/test/languages_tests.rb +26 -9
  40. data/test/materialized_views_tests.rb +174 -0
  41. data/test/permissions_tests.rb +159 -45
  42. data/test/roles_tests.rb +17 -7
  43. data/test/rules_tests.rb +14 -6
  44. data/test/schemas_tests.rb +35 -9
  45. data/test/sequences_tests.rb +9 -11
  46. data/test/tables_tests.rb +132 -42
  47. data/test/tablespace_tests.rb +21 -15
  48. data/test/test_helper.rb +56 -10
  49. data/test/text_search_tests.rb +42 -44
  50. data/test/trigger_tests.rb +1 -3
  51. data/test/types_tests.rb +95 -0
  52. data/test/vacuum_tests.rb +1 -3
  53. data/test/views_tests.rb +203 -0
  54. metadata +22 -16
@@ -0,0 +1,129 @@
1
+
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ module ActiveRecord
5
+ class InvalidEventTriggerEventType < ActiveRecordError #:nodoc:
6
+ def initialize(events)
7
+ super("Invalid trigger event(s) - #{events.inspect}")
8
+ end
9
+ end
10
+
11
+ module ConnectionAdapters
12
+ class PostgreSQLAdapter
13
+ # Creates a PostgreSQL event trigger. Available in PostgreSQL 9.3+.
14
+ #
15
+ # +event+ is one of the valid event trigger event names. See the
16
+ # PostgreSQL documentation for details.
17
+ #
18
+ # ==== Options
19
+ #
20
+ # ==== Example
21
+ #
22
+ def create_event_trigger(name, event, function, options = {})
23
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:event_triggers)
24
+
25
+ execute PostgreSQLEventTriggerDefinition.new(self, name, event, function, options).to_s
26
+ end
27
+
28
+ # Drops an event trigger.
29
+ #
30
+ # ==== Options
31
+ #
32
+ # * <tt>:if_exists</tt> - adds IF EXISTS.
33
+ # * <tt>:cascade</tt> - cascades changes down to objects referring
34
+ # to the trigger.
35
+ def drop_event_trigger(name, options = {})
36
+ sql = 'DROP EVENT TRIGGER '
37
+ sql << 'IF EXISTS ' if options[:if_exists]
38
+ sql << quote_generic(name)
39
+ sql << ' CASCADE' if options[:cascade]
40
+ execute("#{sql};")
41
+ end
42
+
43
+ # Renames an event trigger.
44
+ def rename_event_trigger(name, new_name)
45
+ execute "ALTER EVENT TRIGGER #{quote_generic(name)} RENAME TO #{quote_generic(new_name)};"
46
+ end
47
+
48
+ # Reassigns ownership of an event trigger.
49
+ def alter_event_trigger_owner(name, role)
50
+ execute "ALTER EVENT TRIGGER #{quote_generic(name)} OWNER TO #{quote_generic(role)};"
51
+ end
52
+
53
+ # Enables an event trigger.
54
+ #
55
+ # ==== Options
56
+ #
57
+ # * <tt>:replica
58
+ def enable_event_trigger(name, options = {})
59
+ if options[:always] && options[:replica]
60
+ raise ArgumentError.new("Cannot use :replica and :always together when enabling an event trigger.")
61
+ end
62
+
63
+ sql = "ALTER EVENT TRIGGER #{quote_generic(name)} ENABLE"
64
+
65
+ if options[:always]
66
+ sql << ' ALWAYS'
67
+ elsif options[:replica]
68
+ sql << ' REPLICA'
69
+ end
70
+
71
+ execute "#{sql};"
72
+ end
73
+
74
+ # Disables an event trigger.
75
+ def disable_event_trigger(name)
76
+ execute "ALTER EVENT TRIGGER #{quote_generic(name)} DISABLE;"
77
+ end
78
+ end
79
+
80
+ # Creates a PostgreSQL event trigger definition. This class isn't really
81
+ # meant to be used directly. You'd be better off sticking to
82
+ # PostgreSQLAdapter#create_event_trigger. Honestly.
83
+ class PostgreSQLEventTriggerDefinition
84
+ attr_accessor :base, :name, :event, :function, :options
85
+
86
+ def initialize(base, name, event, function, options = {}) #:nodoc:
87
+ assert_valid_event_name(event)
88
+
89
+ @base, @name, @event, @function, @options =
90
+ base, name, event, function, options
91
+ end
92
+
93
+ def to_sql #:nodoc:
94
+ sql = "CREATE EVENT TRIGGER #{base.quote_generic(name)} ON #{base.quote_generic(event)}"
95
+
96
+ if options[:when].present?
97
+ sql << "\n WHEN "
98
+
99
+ sql << options[:when].inject([]) { |memo, (k, v)|
100
+ memo.tap {
101
+ values = Array.wrap(v).collect { |value|
102
+ base.quote(value)
103
+ }.join(', ')
104
+
105
+ memo << "#{base.quote_generic(k)} IN (#{values})"
106
+ }
107
+ }.join("\n AND ")
108
+
109
+ sql << "\n "
110
+ end
111
+
112
+ sql << " EXECUTE PROCEDURE #{base.quote_function(function)}()"
113
+
114
+ "#{sql};"
115
+ end
116
+ alias :to_s :to_sql
117
+
118
+
119
+ private
120
+ EVENT_NAMES = %w{ ddl_command_start ddl_command_end sql_drop }.freeze
121
+
122
+ def assert_valid_event_name(event) #:nodoc:
123
+ if !EVENT_NAMES.include?(event.to_s.downcase)
124
+ raise ActiveRecord::InvalidEventTriggerEventType.new(event)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -8,8 +8,7 @@ module ActiveRecord
8
8
  # either a parser_name or a source_config option as per the PostgreSQL
9
9
  # text search docs.
10
10
  def create_extension(name, options = {})
11
- raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new('extensions') if
12
- !ActiveRecord::PostgreSQLExtensions::Features.extensions?
11
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:extensions)
13
12
 
14
13
  sql = "CREATE EXTENSION "
15
14
  sql << "IF NOT EXISTS " if options[:if_not_exists]
@@ -26,22 +25,20 @@ module ActiveRecord
26
25
  # * <tt>if_exists</tt> - adds IF EXISTS.
27
26
  # * <tt>cascade</tt> - adds CASCADE.
28
27
  def drop_extension(*args)
29
- raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new('extensions') if
30
- !ActiveRecord::PostgreSQLExtensions::Features.extensions?
28
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:extensions)
31
29
 
32
30
  options = args.extract_options!
33
31
 
34
32
  sql = 'DROP EXTENSION '
35
33
  sql << 'IF EXISTS ' if options[:if_exists]
36
- sql << Array(args).collect { |name| quote_generic(name) }.join(', ')
34
+ sql << Array.wrap(args).collect { |name| quote_generic(name) }.join(', ')
37
35
  sql << ' CASCADE' if options[:cascade]
38
36
 
39
37
  execute("#{sql};")
40
38
  end
41
39
 
42
40
  def update_extension(name, new_version = nil)
43
- raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new('extensions') if
44
- !ActiveRecord::PostgreSQLExtensions::Features.extensions?
41
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:extensions)
45
42
 
46
43
  sql = "ALTER EXTENSION #{quote_generic(name)} UPDATE"
47
44
  sql << " TO #{quote_generic(new_version)}" if new_version;
@@ -49,8 +46,7 @@ module ActiveRecord
49
46
  end
50
47
 
51
48
  def alter_extension_schema(name, schema)
52
- raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new('extensions') if
53
- !ActiveRecord::PostgreSQLExtensions::Features.extensions?
49
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:extensions)
54
50
 
55
51
  execute "ALTER EXTENSION #{quote_generic(name)} SET SCHEMA #{quote_schema(schema)};"
56
52
  end
@@ -124,8 +120,7 @@ module ActiveRecord
124
120
  # * <tt>:operator_class</tt> and <tt>:operator_family</tt> - <tt>:name</tt>
125
121
  # and <tt>:indexing_method</tt>.
126
122
  def alter_extension(name, options = {})
127
- raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new('extensions') if
128
- !ActiveRecord::PostgreSQLExtensions::Features.extensions?
123
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:extensions)
129
124
 
130
125
  alterer = PostgreSQLExtensionAlterer.new(self, name, options)
131
126
 
@@ -139,8 +134,7 @@ module ActiveRecord
139
134
 
140
135
  class PostgreSQLExtensionAlterer
141
136
  def initialize(base, name, options = {}) #:nodoc:
142
- raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new('extensions') if
143
- !ActiveRecord::PostgreSQLExtensions::Features.extensions?
137
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:extensions)
144
138
 
145
139
  @base, @name, @options = base, name, options
146
140
  @sql = options.collect { |k, v| build_statement(k, v) }
@@ -187,6 +181,8 @@ module ActiveRecord
187
181
  action = :add
188
182
  end
189
183
 
184
+ assert_valid_action(action)
185
+
190
186
  sql = "ALTER EXTENSION #{@base.quote_generic(@name || name)} #{action.to_s.upcase} "
191
187
  sql << case option
192
188
  when 'aggregate'
@@ -199,7 +195,7 @@ module ActiveRecord
199
195
 
200
196
  "AGGREGATE %s (%s)" % [
201
197
  @base.quote_generic(name),
202
- Array(types).collect { |t|
198
+ Array.wrap(types).collect { |t|
203
199
  @base.quote_generic(t)
204
200
  }.join(', ')
205
201
  ]
@@ -223,7 +219,7 @@ module ActiveRecord
223
219
  [ args.shift, *args ]
224
220
  end
225
221
 
226
- "FUNCTION #{@base.quote_function(name)}(#{Array(arguments).join(', ')})"
222
+ "FUNCTION #{@base.quote_function(name)}(#{Array.wrap(arguments).join(', ')})"
227
223
  when 'operator'
228
224
  name, left_type, right_type =
229
225
  extract_hash_or_array_options(args, :name, :left_type, :right_type)
@@ -234,6 +230,9 @@ module ActiveRecord
234
230
  extract_hash_or_array_options(args, :name, :indexing_method)
235
231
 
236
232
  "#{option.upcase.gsub('_', ' ')} #{@base.quote_generic(object_name)} USING #{@base.quote_generic(indexing_method)})"
233
+
234
+ else
235
+ raise ArgumentError.new("Unknown operation #{option}")
237
236
  end
238
237
  sql
239
238
  end
@@ -1,46 +1,85 @@
1
1
 
2
2
  module ActiveRecord
3
3
  module PostgreSQLExtensions
4
- class FeatureNotSupportedError < Exception
5
- def initialize(feature)
6
- super(%{The feature "#{feature}" is not supported by server. (Server version #{ActiveRecord::PostgreSQLExtensions.SERVER_VERSION}.)"})
7
- end
8
- end
9
-
10
- module Features
11
- class << self
12
- def extensions?
13
- if defined?(@has_extensions)
14
- @has_extensions
15
- else
16
- @has_extensions = ActiveRecord::PostgreSQLExtensions.SERVER_VERSION >= '9.1'
17
- end
18
- end
19
-
20
- def foreign_tables?
21
- if defined?(@has_foreign_tables)
22
- @has_foreign_tables
23
- else
24
- @has_foreign_tables = ActiveRecord::PostgreSQLExtensions.SERVER_VERSION >= '9.1'
25
- end
26
- end
27
-
28
- def modify_mass_privileges?
29
- if defined?(@has_modify_mass_privileges)
30
- @has_modify_mass_privileges
31
- else
32
- @has_modify_mass_privileges = ActiveRecord::PostgreSQLExtensions.SERVER_VERSION >= '9.0'
33
- end
34
- end
35
-
36
- def postgis?
37
- if defined?(@has_postgis)
38
- @has_postgis
39
- else
40
- @has_postgis = !!ActiveRecord::PostgreSQLExtensions::PostGIS.VERSION
41
- end
42
- end
43
- end
44
- end
4
+ class FeatureNotSupportedError < Exception
5
+ def initialize(feature)
6
+ super(%{The feature "#{feature}" is not supported by server. (Server version #{ActiveRecord::PostgreSQLExtensions.SERVER_VERSION}.)"})
7
+ end
8
+ end
9
+
10
+ module Features
11
+ class << self
12
+ %w{
13
+ copy_from_encoding
14
+ copy_from_freeze
15
+ copy_from_program
16
+ create_schema_if_not_exists
17
+ create_table_if_not_exists
18
+ create_table_unlogged
19
+ event_triggers
20
+ extensions
21
+ foreign_tables
22
+ materialized_views
23
+ modify_mass_privileges
24
+ postgis
25
+ rename_rule
26
+ type_if_not_exists
27
+ view_if_exists
28
+ view_recursive
29
+ view_set_options
30
+ }.each do |feature|
31
+ self.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
32
+ def #{feature}?
33
+ sniff_features unless sniffed?
34
+ !!@has_#{feature}
35
+ end
36
+ RUBY
37
+ end
38
+
39
+ def check_feature(feature)
40
+ if !self.send("#{feature}?")
41
+ raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new(feature)
42
+ end
43
+ end
44
+
45
+ private
46
+ def sniffed?
47
+ @sniffed
48
+ end
49
+
50
+ def sniff_features
51
+ @sniffed = true
52
+
53
+ if ActiveRecord::PostgreSQLExtensions.SERVER_VERSION >= '9.3'
54
+ @has_copy_from_freeze = true
55
+ @has_copy_from_program = true
56
+ @has_create_schema_if_not_exists = true
57
+ @has_event_triggers = true
58
+ @has_materialized_views = true
59
+ @has_rename_rule = true
60
+ @has_type_if_not_exists = true
61
+ @has_view_recursive = true
62
+ end
63
+
64
+ if ActiveRecord::PostgreSQLExtensions.SERVER_VERSION >= '9.1'
65
+ @has_copy_from_encoding = true
66
+ @has_create_table_if_not_exists = true
67
+ @has_create_table_unlogged = true
68
+ @has_extensions = true
69
+ @has_foreign_tables = true
70
+ @has_view_if_exists = true
71
+ @has_view_set_options = true
72
+ end
73
+
74
+ if ActiveRecord::PostgreSQLExtensions.SERVER_VERSION >= '9.0'
75
+ @has_modify_mass_privileges = true
76
+ end
77
+
78
+ if !!ActiveRecord::PostgreSQLExtensions::PostGIS.VERSION
79
+ @has_postgis = true
80
+ end
81
+ end
82
+ end
83
+ end
45
84
  end
46
85
  end
@@ -228,7 +228,7 @@ module ActiveRecord
228
228
  sql << set_me
229
229
  end
230
230
  else
231
- sql << Array(opts).collect do |s|
231
+ sql << Array.wrap(opts).collect do |s|
232
232
  "SET #{s.to_s}"
233
233
  end
234
234
  end
@@ -134,20 +134,20 @@ module ActiveRecord
134
134
  ActiveRecord::PostgreSQLExtensions::PostGIS.VERSION[:lib] < '2.0' ||
135
135
  opts[:force_constraints]
136
136
  )
137
- @table_constraints << PostgreSQLCheckConstraint.new(
137
+ table_constraints << PostgreSQLCheckConstraint.new(
138
138
  base,
139
139
  "ST_srid(#{base.quote_column_name(column_name)}) = (#{opts[:srid].to_i})",
140
140
  :name => "enforce_srid_#{column_name}"
141
141
  )
142
142
 
143
- @table_constraints << PostgreSQLCheckConstraint.new(
143
+ table_constraints << PostgreSQLCheckConstraint.new(
144
144
  base,
145
145
  "ST_ndims(#{base.quote_column_name(column_name)}) = #{opts[:ndims].to_i}",
146
146
  :name => "enforce_dims_#{column_name}"
147
147
  )
148
148
 
149
149
  if opts[:geometry_type].to_s.upcase != 'GEOMETRY'
150
- @table_constraints << PostgreSQLCheckConstraint.new(
150
+ table_constraints << PostgreSQLCheckConstraint.new(
151
151
  base,
152
152
  "geometrytype(#{base.quote_column_name(column_name)}) = '#{opts[:geometry_type].to_s.upcase}'::text OR #{base.quote_column_name(column_name)} IS NULL",
153
153
  :name => "enforce_geotype_#{column_name}"
@@ -167,13 +167,11 @@ module ActiveRecord
167
167
  [ schema || 'public', table_name ]
168
168
  end
169
169
 
170
- @post_processing ||= Array.new
171
-
172
170
  if opts[:add_geometry_columns_entry] &&
173
171
  opts[:spatial_column_type].to_s != 'geography' &&
174
172
  ActiveRecord::PostgreSQLExtensions::PostGIS.VERSION[:lib] < '2.0'
175
173
 
176
- @post_processing << sprintf(
174
+ self.post_processing << sprintf(
177
175
  "DELETE FROM \"geometry_columns\" WHERE f_table_catalog = '' AND " +
178
176
  "f_table_schema = %s AND " +
179
177
  "f_table_name = %s AND " +
@@ -183,7 +181,7 @@ module ActiveRecord
183
181
  base.quote(column_name.to_s)
184
182
  )
185
183
 
186
- @post_processing << sprintf(
184
+ self.post_processing << sprintf(
187
185
  "INSERT INTO \"geometry_columns\" VALUES ('', %s, %s, %s, %d, %d, %s);",
188
186
  base.quote(current_scoped_schema.to_s),
189
187
  base.quote(current_table_name.to_s),
@@ -201,7 +199,7 @@ module ActiveRecord
201
199
  "#{current_table_name}_#{column_name}_gist_index"
202
200
  end
203
201
 
204
- @post_processing << PostgreSQLIndexDefinition.new(
202
+ self.post_processing << PostgreSQLIndexDefinition.new(
205
203
  base,
206
204
  index_name,
207
205
  { current_scoped_schema => current_table_name },
@@ -18,7 +18,8 @@ module ActiveRecord
18
18
  class PostgreSQLAdapter
19
19
  # Creates an index. This method is an alternative to the standard
20
20
  # ActiveRecord add_index method and includes PostgreSQL-specific
21
- # options.
21
+ # options. Indexes can be created on tables as well as materialized views
22
+ # starting with PostgreSQL 9.3.
22
23
  #
23
24
  # === Differences to add_index
24
25
  #
@@ -62,6 +63,9 @@ module ActiveRecord
62
63
  # * <tt>:conditions</tt> - adds an optional WHERE clause to the
63
64
  # index. (You can alternatively use the option <tt>:where</tt>
64
65
  # instead.)
66
+ # * <tt>:index_parameters</tt> - a simple String or Hash used to
67
+ # assign index storage parameters. See the PostgreSQL docs for
68
+ # details on the various storage parameters available.
65
69
  #
66
70
  # ==== Column Options
67
71
  #
@@ -106,8 +110,8 @@ module ActiveRecord
106
110
  # # additional options
107
111
  # create_index('search_idx', :foo, :tsvector, :using => :gin)
108
112
  # # => CREATE INDEX "search_idx" ON "foo" USING "gin"("tsvector");
109
- def create_index(name, table, columns, options = {})
110
- execute PostgreSQLIndexDefinition.new(self, name, table, columns, options).to_s
113
+ def create_index(name, object, columns, options = {})
114
+ execute PostgreSQLIndexDefinition.new(self, name, object, columns, options).to_s
111
115
  end
112
116
 
113
117
  # PostgreSQL-specific version of the standard ActiveRecord
@@ -130,17 +134,20 @@ module ActiveRecord
130
134
  # the INDEX. When using the :concurrently option, only one INDEX can
131
135
  # specified and the :cascade option cannot be used. See the PostgreSQL
132
136
  # documentation for details.
133
- def drop_index(name, options = {})
137
+ def drop_index(*args)
138
+ options = args.extract_options!
139
+ args.flatten!
140
+
134
141
  if options[:concurrently] && options[:cascade]
135
142
  raise ArgumentError.new("The :concurrently and :cascade options cannot be used together.")
136
- elsif options[:concurrently] && name.is_a?(Array) && name.length > 1
143
+ elsif options[:concurrently] && args.length > 1
137
144
  raise ArgumentError.new("The :concurrently option can only be used on a single INDEX.")
138
145
  end
139
146
 
140
147
  sql = 'DROP INDEX '
141
148
  sql << 'CONCURRENTLY ' if options[:concurrently]
142
149
  sql << 'IF EXISTS ' if options[:if_exists]
143
- sql << Array(name).collect { |i| quote_generic(i) }.join(', ')
150
+ sql << Array.wrap(args).collect { |i| quote_generic(i) }.join(', ')
144
151
  sql << ' CASCADE' if options[:cascade]
145
152
  execute("#{sql};")
146
153
  end
@@ -160,13 +167,13 @@ module ActiveRecord
160
167
  # to be used directly. Instead, see PostgreSQLAdapter#create_index
161
168
  # for usage.
162
169
  class PostgreSQLIndexDefinition
163
- attr_accessor :base, :name, :table, :columns, :options
170
+ attr_accessor :base, :name, :object, :columns, :options
164
171
 
165
- def initialize(base, name, table, columns, options = {}) #:nodoc:
172
+ def initialize(base, name, object, columns, options = {}) #:nodoc:
166
173
  assert_valid_columns(columns)
167
174
  assert_valid_fill_factor(options[:fill_factor])
168
175
 
169
- @base, @name, @table, @columns, @options = base, name, table, columns, options
176
+ @base, @name, @object, @columns, @options = base, name, object, columns, options
170
177
  end
171
178
 
172
179
  def to_sql #:nodoc:
@@ -174,7 +181,7 @@ module ActiveRecord
174
181
  sql << 'UNIQUE ' if options[:unique]
175
182
  sql << 'INDEX '
176
183
  sql << 'CONCURRENTLY ' if options[:concurrently]
177
- sql << "#{base.quote_generic(name)} ON #{base.quote_table_name(table)}"
184
+ sql << "#{base.quote_generic(name)} ON #{base.quote_table_name(object)}"
178
185
  sql << " USING #{base.quote_generic(options[:using])}" if options[:using]
179
186
  sql << '('
180
187
  sql << [ columns ].flatten.collect do |column|
@@ -196,6 +203,7 @@ module ActiveRecord
196
203
  end.join(', ')
197
204
  sql << ')'
198
205
  sql << " WITH (FILLFACTOR = #{options[:fill_factor].to_i})" if options[:fill_factor]
206
+ sql << " WITH (#{ActiveRecord::PostgreSQLExtensions::Utils.options_from_hash_or_string(options[:index_parameters], base)})" if options[:index_parameters].present?
199
207
  sql << " TABLESPACE #{base.quote_tablespace(options[:tablespace])}" if options[:tablespace]
200
208
  sql << " WHERE (#{options[:conditions] || options[:where]})" if options[:conditions] || options[:where]
201
209
  "#{sql};"
@@ -204,7 +212,7 @@ module ActiveRecord
204
212
 
205
213
  private
206
214
  def assert_valid_columns(columns) #:nodoc:
207
- Array(columns).each do |column|
215
+ Array.wrap(columns).each do |column|
208
216
  if column.is_a?(Hash)
209
217
  if column.has_key?(:column) && column.has_key?(:expression)
210
218
  raise ActiveRecord::InvalidIndexColumnDefinition.new("You can't specify both :column and :expression in a column definition", column)