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
@@ -66,7 +66,7 @@ module ActiveRecord
66
66
 
67
67
  # Returns an Array of available languages.
68
68
  def languages(name = nil)
69
- query(<<-SQL, name).map { |row| row[0] }
69
+ query(PostgreSQLExtensions::Utils.strip_heredoc(<<-SQL), name).map { |row| row[0] }
70
70
  SELECT lanname
71
71
  FROM pg_language;
72
72
  SQL
@@ -0,0 +1,272 @@
1
+
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ class PostgreSQLAdapter
7
+ # Creates a new PostgreSQL materialized view.
8
+ #
9
+ # +name+ is the name of the view. View quoting works the same as
10
+ # table quoting, so you can use PostgreSQLAdapter#with_schema and
11
+ # friends. See PostgreSQLAdapter#with_schema and
12
+ # PostgreSQLAdapter#quote_table_name for details.
13
+ #
14
+ # +query+ is the SELECT query to use for the view. This is just
15
+ # a straight-up String, so quoting rules will not apply.
16
+ #
17
+ # Note that you can grant privileges on views using the
18
+ # grant_view_privileges method and revoke them using
19
+ # revoke_view_privileges.
20
+ #
21
+ # ==== Options
22
+ #
23
+ # * <tt>:columns</tt> - you can rename the output columns as
24
+ # necessary. Note that this can be an Array and that it must be
25
+ # the same length as the number of output columns created by
26
+ # +query+.
27
+ # * <tt>:tablespace</tt> - allows you to set the tablespace of a
28
+ # materialized view.
29
+ # * <tt>:with_data</tt> - whether to populate the materialized view
30
+ # upon creation. The default is true.
31
+ #
32
+ # ==== Examples
33
+ #
34
+ # create_materialized_view(:foo_view, 'SELECT * FROM bar')
35
+ # # => CREATE MATERIALIZED VIEW "foo_view" AS SELECT * FROM bar;
36
+ #
37
+ # create_view(
38
+ # { :geospatial => :foo_view },
39
+ # 'SELECT * FROM bar',
40
+ # :columns => [ :id, :name, :the_geom ],
41
+ # :with_data => false
42
+ # )
43
+ # # => CREATE MATERIALIZED VIEW "geospatial"."foo_view" ("id", "name", "the_geom") AS SELECT * FROM bar WITH NO DATA;
44
+ def create_materialized_view(name, query, options = {})
45
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:materialized_views)
46
+
47
+ execute PostgreSQLMaterializedViewDefinition.new(self, name, query, options).to_s
48
+ end
49
+
50
+ # Drops a materialized view.
51
+ #
52
+ # ==== Options
53
+ #
54
+ # * <tt>:if_exists</tt> - adds IF EXISTS.
55
+ # * <tt>:cascade</tt> - adds CASCADE.
56
+ def drop_materialized_view(*args)
57
+ options = args.extract_options!
58
+ args.flatten!
59
+
60
+ sql = 'DROP MATERIALIZED VIEW '
61
+ sql << 'IF EXISTS ' if options[:if_exists]
62
+ sql << Array.wrap(args).collect { |v| quote_view_name(v) }.join(', ')
63
+ sql << ' CASCADE' if options[:cascade]
64
+ execute("#{sql};")
65
+ end
66
+
67
+ # Renames a materialized view.
68
+ def rename_materialized_view(name, new_name, options = {})
69
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
70
+ :rename_to => new_name
71
+ }, options).to_sql
72
+ end
73
+
74
+ # Change the default of a materialized view column. The default value can
75
+ # be either a straight-up value or a Hash containing an expression
76
+ # in the form <tt>:expression => value</tt> which will be passed
77
+ # through unescaped. This allows you to set expressions and use
78
+ # functions and the like.
79
+ def alter_materialized_view_set_column_default(name, column, default, options = {})
80
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
81
+ :column => column,
82
+ :set_default => default
83
+ }, options).to_sql
84
+ end
85
+
86
+ # Drop the default value on a materialized view column
87
+ def alter_materialized_view_drop_column_default(name, column, options = {})
88
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
89
+ :drop_default => column
90
+ }, options).to_sql
91
+ end
92
+
93
+ # Change the ownership of a materialized view.
94
+ def alter_materialized_view_owner(name, role, options = {})
95
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
96
+ :owner_to => role
97
+ }, options).to_sql
98
+ end
99
+
100
+ # Alter a materialized view's schema.
101
+ def alter_materialized_view_schema(name, schema, options = {})
102
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
103
+ :set_schema => schema
104
+ }, options).to_sql
105
+ end
106
+
107
+ # Sets a materialized view's options using a Hash.
108
+ def alter_materialized_view_set_options(name, set_options, options = {})
109
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
110
+ :set_options => set_options
111
+ }, options).to_sql
112
+ end
113
+
114
+ # Resets a materialized view's options.
115
+ def alter_materialized_view_reset_options(name, *args)
116
+ options = args.extract_options!
117
+
118
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
119
+ :reset_options => args
120
+ }, options).to_sql
121
+ end
122
+
123
+ # Cluster a materialized view on an index.
124
+ def cluster_materialized_view(name, index_name)
125
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
126
+ :cluster_on => index_name
127
+ }).to_sql
128
+ end
129
+
130
+ # Remove a cluster from materialized view.
131
+ def remove_cluster_from_materialized_view(name)
132
+ execute PostgreSQLMaterializedViewAlterer.new(self, name, {
133
+ :remove_cluster => true
134
+ }).to_sql
135
+ end
136
+
137
+ # Refreshes the data in a materialized view.
138
+ #
139
+ # ==== Options
140
+ #
141
+ # * <tt>:with_data</tt> - whether to populate the materialized view with
142
+ # data. The default is true.
143
+ def refresh_materialized_view(name, options = {})
144
+ options = {
145
+ :with_data => true
146
+ }.merge(options)
147
+
148
+ sql = "REFRESH MATERIALIZED VIEW #{quote_view_name(name)}"
149
+ sql << " WITH NO DATA" unless options[:with_data]
150
+
151
+ execute "#{sql};"
152
+ end
153
+ end
154
+
155
+ # Creates a PostgreSQL materialized view definition. This class isn't
156
+ # really meant to be used directly. Instead, see
157
+ # PostgreSQLAdapter#create_materialized_view for usage.
158
+ class PostgreSQLMaterializedViewDefinition
159
+ include ActiveRecord::PostgreSQLExtensions::Utils
160
+
161
+ attr_accessor :base, :name, :query, :options
162
+
163
+ def initialize(base, name, query, options = {}) #:nodoc:
164
+ @base, @name, @query, @options = base, name, query, options
165
+ end
166
+
167
+ def to_sql #:nodoc:
168
+ sql = "CREATE MATERIALIZED VIEW #{base.quote_view_name(name)} "
169
+
170
+ if options[:columns]
171
+ sql << '(' << Array.wrap(options[:columns]).collect do |c|
172
+ base.quote_column_name(c)
173
+ end.join(', ') << ') '
174
+ end
175
+
176
+ sql << "WITH (#{options_from_hash_or_string(options[:with_options])}) " if options[:with_options].present?
177
+
178
+ sql << "TABLESPACE #{base.quote_tablespace(options[:tablespace])} " if options[:tablespace]
179
+ sql << "AS #{query}"
180
+ sql << " WITH NO DATA" if options.key?(:with_data) && !options[:with_data]
181
+ "#{sql};"
182
+ end
183
+ alias :to_s :to_sql
184
+ end
185
+
186
+ # Alters a PostgreSQL materialized view definition. This class isn't
187
+ # really meant to be used directly. Instead, see the various
188
+ # PostgreSQLAdapter materialied views methods for usage.
189
+ class PostgreSQLMaterializedViewAlterer
190
+ include ActiveRecord::PostgreSQLExtensions::Utils
191
+
192
+ attr_accessor :base, :name, :actions, :options
193
+
194
+ VALID_OPTIONS = %w{
195
+ set_default
196
+ drop_default
197
+ owner_to
198
+ rename_to
199
+ set_schema
200
+ set_options
201
+ reset_options
202
+ cluster_on
203
+ remove_cluster
204
+ }.freeze
205
+
206
+ def initialize(base, name, actions, options = {}) #:nodoc:
207
+ @base, @name, @actions, @options = base, name, actions, options
208
+ end
209
+
210
+ def to_sql #:nodoc:
211
+ all_sql = []
212
+
213
+ VALID_OPTIONS.each do |key|
214
+ key = key.to_sym
215
+
216
+ if actions.key?(key)
217
+ sql = "ALTER MATERIALIZED VIEW "
218
+ sql << "IF EXISTS " if options[:if_exists]
219
+ sql << "#{base.quote_view_name(name)} "
220
+
221
+ sql << case key
222
+ when :set_default
223
+ expression = if actions[:set_default].is_a?(Hash) && actions[:set_default].key?(:expression)
224
+ actions[:set_default][:expression]
225
+ else
226
+ base.quote(actions[:set_default])
227
+ end
228
+
229
+ "ALTER COLUMN #{base.quote_column_name(actions[:column])} SET DEFAULT #{expression}"
230
+
231
+ when :drop_default
232
+ "ALTER COLUMN #{base.quote_column_name(actions[:drop_default])} DROP DEFAULT"
233
+
234
+ when :owner_to
235
+ "OWNER TO #{base.quote_role(actions[:owner_to])}"
236
+
237
+ when :rename_to
238
+ "RENAME TO #{base.quote_generic_ignore_scoped_schema(actions[:rename_to])}"
239
+
240
+ when :set_schema
241
+ "SET SCHEMA #{base.quote_schema(actions[:set_schema])}"
242
+
243
+ when :set_options
244
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:view_set_options)
245
+
246
+ "SET (#{options_from_hash_or_string(actions[:set_options])})" if actions[:set_options].present?
247
+
248
+ when :reset_options
249
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:view_set_options)
250
+
251
+ 'RESET (' << Array.wrap(actions[:reset_options]).collect { |value|
252
+ base.quote_generic(value)
253
+ }.join(", ") << ')'
254
+
255
+ when :cluster_on
256
+ "CLUSTER ON #{base.quote_generic(actions[:cluster_on])}"
257
+
258
+ when :remove_cluster
259
+ next unless actions[:remove_cluster]
260
+
261
+ "SET WITHOUT CLUSTER"
262
+ end
263
+
264
+ all_sql << "#{sql};"
265
+ end
266
+ end
267
+
268
+ all_sql.join("\n")
269
+ end
270
+ end
271
+ end
272
+ end
@@ -66,6 +66,24 @@ module ActiveRecord
66
66
  execute PostgreSQLGrantPrivilege.new(self, :tablespace, tablespaces, privileges, roles, options).to_sql
67
67
  end
68
68
 
69
+ # Grants privileges on views. You can specify multiple views,
70
+ # roles and privileges all at once using Arrays for each of the
71
+ # desired parameters. See PostgreSQLGrantPrivilege for
72
+ # usage.
73
+ def grant_view_privileges(views, privileges, roles, options = {})
74
+ execute PostgreSQLGrantPrivilege.new(self, :view, views, privileges, roles, options, :named_object_type => false).to_sql
75
+ end
76
+
77
+ # Grants privileges on views. You can specify multiple
78
+ # materialized views, roles and privileges all at once using Arrays for
79
+ # each of the desired parameters. See PostgreSQLGrantPrivilege for
80
+ # usage.
81
+ def grant_materialized_view_privileges(materialized_views, privileges, roles, options = {})
82
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:materialized_views)
83
+
84
+ execute PostgreSQLGrantPrivilege.new(self, :materialized_view, materialized_views, privileges, roles, options, :named_object_type => false).to_sql
85
+ end
86
+
69
87
  # Grants role membership to another role. You can specify multiple
70
88
  # roles for both the roles and the role_names parameters using
71
89
  # Arrays.
@@ -76,9 +94,9 @@ module ActiveRecord
76
94
  # clause to the command.
77
95
  def grant_role_membership(roles, role_names, options = {})
78
96
  sql = "GRANT "
79
- sql << Array(roles).collect { |r| quote_role(r) }.join(', ')
97
+ sql << Array.wrap(roles).collect { |r| quote_role(r) }.join(', ')
80
98
  sql << ' TO '
81
- sql << Array(role_names).collect { |r| quote_role(r) }.join(', ')
99
+ sql << Array.wrap(role_names).collect { |r| quote_role(r) }.join(', ')
82
100
  sql << ' WITH ADMIN OPTION' if options[:with_admin_option]
83
101
  execute("#{sql};")
84
102
  end
@@ -139,6 +157,24 @@ module ActiveRecord
139
157
  execute PostgreSQLRevokePrivilege.new(self, :tablespace, tablespaces, privileges, roles, options).to_sql
140
158
  end
141
159
 
160
+ # Revokes materialized view privileges. You can specify multiple
161
+ # materialized views, roles and privileges all at once using Arrays for
162
+ # each of the desired parameters. See PostgreSQLRevokePrivilege for
163
+ # usage.
164
+ def revoke_materialized_view_privileges(materialized_views, privileges, roles, options = {})
165
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:materialized_views)
166
+
167
+ execute PostgreSQLRevokePrivilege.new(self, :materialized_view, materialized_views, privileges, roles, options, :named_object_type => false).to_sql
168
+ end
169
+
170
+ # Revokes view privileges. You can specify multiple views,
171
+ # roles and privileges all at once using Arrays for each of the
172
+ # desired parameters. See PostgreSQLRevokePrivilege for
173
+ # usage.
174
+ def revoke_view_privileges(views, privileges, roles, options = {})
175
+ execute PostgreSQLRevokePrivilege.new(self, :view, views, privileges, roles, options, :named_object_type => false).to_sql
176
+ end
177
+
142
178
  # Revokes role membership. You can specify multiple
143
179
  # roles for both the roles and the role_names parameters using
144
180
  # Arrays.
@@ -151,9 +187,9 @@ module ActiveRecord
151
187
  def revoke_role_membership(roles, role_names, options = {})
152
188
  sql = 'REVOKE '
153
189
  sql << 'ADMIN_OPTION_FOR ' if options[:admin_option_for]
154
- sql << Array(roles).collect { |r| quote_role(r) }.join(', ')
190
+ sql << Array.wrap(roles).collect { |r| quote_role(r) }.join(', ')
155
191
  sql << ' FROM '
156
- sql << Array(role_names).collect { |r| quote_role(r) }.join(', ')
192
+ sql << Array.wrap(role_names).collect { |r| quote_role(r) }.join(', ')
157
193
  sql << ' CASCADE' if options[:cascade]
158
194
  execute("#{sql};")
159
195
  end
@@ -178,11 +214,13 @@ module ActiveRecord
178
214
  :function => [ 'execute', 'all' ],
179
215
  :language => [ 'usage', 'all' ],
180
216
  :schema => [ 'create', 'usage', 'all' ],
181
- :tablespace => [ 'create', 'all' ]
217
+ :tablespace => [ 'create', 'all' ],
218
+ :view => [ 'select', 'insert', 'update', 'delete', 'references', 'trigger', 'all' ],
219
+ :materialized_view => [ 'select', 'insert', 'update', 'delete', 'references', 'trigger' ]
182
220
  }.freeze
183
221
 
184
222
  def assert_valid_privileges type, privileges
185
- check_privileges = Array(privileges).collect(&:to_s) - PRIVILEGE_TYPES[type]
223
+ check_privileges = Array.wrap(privileges).collect(&:to_s) - PRIVILEGE_TYPES[type]
186
224
  if !check_privileges.empty?
187
225
  raise ActiveRecord::InvalidPrivilegeTypes.new(type, check_privileges)
188
226
  end
@@ -221,20 +259,20 @@ module ActiveRecord
221
259
  class PostgreSQLGrantPrivilege < PostgreSQLPrivilege
222
260
  def to_sql #:nodoc:
223
261
  my_query_options = {
224
- :quote_objects => true
262
+ :quote_objects => true,
263
+ :named_object_type => true
225
264
  }.merge query_options
226
265
 
227
- sql = "GRANT #{Array(privileges).collect(&:to_s).collect(&:upcase).join(', ')} ON "
266
+ sql = "GRANT #{Array.wrap(privileges).collect(&:to_s).collect(&:upcase).join(', ')} ON "
228
267
 
229
268
  if options[:all]
230
- if !ActiveRecord::PostgreSQLExtensions::Features.modify_mass_privileges?
231
- raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new('modify mass privileges')
232
- end
269
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:modify_mass_privileges)
233
270
 
234
271
  sql << "ALL #{type.to_s.upcase}S IN SCHEMA #{base.quote_schema(objects)}"
235
272
  else
236
- sql << "#{type.to_s.upcase} "
237
- sql << Array(objects).collect do |t|
273
+ sql << "#{type.to_s.gsub('_', ' ').upcase} " if my_query_options[:named_object_type]
274
+
275
+ sql << Array.wrap(objects).collect do |t|
238
276
  if my_query_options[:quote_objects]
239
277
  if my_query_options[:ignore_schema]
240
278
  base.quote_generic_ignore_scoped_schema(t)
@@ -247,7 +285,7 @@ module ActiveRecord
247
285
  end.join(', ')
248
286
  end
249
287
 
250
- sql << ' TO ' << Array(roles).collect do |r|
288
+ sql << ' TO ' << Array.wrap(roles).collect do |r|
251
289
  r = r.to_s
252
290
  if r.upcase == 'PUBLIC'
253
291
  'PUBLIC'
@@ -299,22 +337,22 @@ module ActiveRecord
299
337
  class PostgreSQLRevokePrivilege < PostgreSQLPrivilege
300
338
  def to_sql #:nodoc:
301
339
  my_query_options = {
302
- :quote_objects => true
340
+ :quote_objects => true,
341
+ :named_object_type => true
303
342
  }.merge query_options
304
343
 
305
344
  sql = 'REVOKE '
306
345
  sql << 'GRANT OPTION FOR ' if options[:grant_option_for]
307
- sql << "#{Array(privileges).collect(&:to_s).collect(&:upcase).join(', ')} ON "
346
+ sql << "#{Array.wrap(privileges).collect(&:to_s).collect(&:upcase).join(', ')} ON "
308
347
 
309
348
  if options[:all]
310
- if !ActiveRecord::PostgreSQLExtensions::Features.modify_mass_privileges?
311
- raise ActiveRecord::PostgreSQLExtensions::FeatureNotSupportedError.new('modify mass privileges')
312
- end
349
+ ActiveRecord::PostgreSQLExtensions::Features.check_feature(:modify_mass_privileges)
313
350
 
314
351
  sql << "ALL #{type.to_s.upcase}S IN SCHEMA #{base.quote_schema(objects)}"
315
352
  else
316
- sql << "#{type.to_s.upcase} "
317
- sql << Array(objects).collect do |t|
353
+ sql << "#{type.to_s.upcase} " if my_query_options[:named_object_type]
354
+
355
+ sql << Array.wrap(objects).collect do |t|
318
356
  if my_query_options[:quote_objects]
319
357
  if my_query_options[:ignore_schema]
320
358
  base.quote_generic_ignore_scoped_schema(t)
@@ -327,7 +365,7 @@ module ActiveRecord
327
365
  end.join(', ')
328
366
  end
329
367
 
330
- sql << ' FROM ' << Array(roles).collect do |r|
368
+ sql << ' FROM ' << Array.wrap(roles).collect do |r|
331
369
  r = r.to_s
332
370
  if r.upcase == 'PUBLIC'
333
371
  'PUBLIC'
@@ -10,27 +10,38 @@ module ActiveRecord
10
10
 
11
11
  module ConnectionAdapters
12
12
  class PostgreSQLAdapter
13
+ # Creates a PostgreSQL ROLE. See PostgreSQLRole for details on options.
13
14
  def create_role(name, options = {})
14
15
  execute PostgreSQLRole.new(self, :create, name, options).to_sql
15
16
  end
16
17
  alias :create_user :create_role
17
18
 
19
+ # Alters a PostgreSQL ROLE. See PostgreSQLRole for details on options.
18
20
  def alter_role(name, options = {})
19
21
  execute PostgreSQLRole.new(self, :alter, name, options).to_sql
20
22
  end
21
23
  alias :alter_user :alter_role
22
24
 
23
- def drop_role(name, options = {})
25
+ # Drop PostgreSQL ROLEs.
26
+ #
27
+ # ==== Options
28
+ #
29
+ # * <tt>:if_exists</tt> - don't raise an error if the ROLE doesn't
30
+ # exist. The default is false.
31
+ def drop_role(*args)
32
+ options = args.extract_options!
33
+ args.flatten!
34
+
24
35
  sql = 'DROP ROLE '
25
36
  sql << 'IF EXISTS ' if options[:if_exists]
26
- sql << Array(name).collect { |r| quote_role(r) }.join(', ')
37
+ sql << Array.wrap(args).collect { |r| quote_role(r) }.join(', ')
27
38
  execute("#{sql};")
28
39
  end
29
40
  alias :drop_user :drop_role
30
41
  end
31
42
 
32
- # This is a base class for PostgreSQLGrantPrivilege and
33
- # PostgreSQLRevokePrivilege and is not meant to be used directly.
43
+ # This is a base class for creating and altering ROLEs and is not meant to
44
+ # be used directly.
34
45
  class PostgreSQLRole
35
46
  attr_accessor :base, :action, :name, :options
36
47
 
@@ -99,17 +110,17 @@ module ActiveRecord
99
110
 
100
111
  if options[:in_role].present?
101
112
  sql << 'IN ROLE'
102
- sql << Array(options[:in_role]).collect { |r| base.quote_role(r) }.join(', ')
113
+ sql << Array.wrap(options[:in_role]).collect { |r| base.quote_role(r) }.join(', ')
103
114
  end
104
115
 
105
116
  if options[:role].present?
106
117
  sql << 'ROLE'
107
- sql << Array(options[:role]).collect { |r| base.quote_role(r) }.join(', ')
118
+ sql << Array.wrap(options[:role]).collect { |r| base.quote_role(r) }.join(', ')
108
119
  end
109
120
 
110
121
  if options[:admin].present?
111
122
  sql << 'ADMIN'
112
- sql << Array(options[:admin]).collect { |r| base.quote_role(r) }.join(', ')
123
+ sql << Array.wrap(options[:admin]).collect { |r| base.quote_role(r) }.join(', ')
113
124
  end
114
125
 
115
126
  "#{sql.join(' ')};"