activerecord-postgresql-extensions 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c65ffc95f16b554687be6eb67df6ddffc03c6283
4
- data.tar.gz: 658228acaf0587e77eefc4d67eb31c4138bc65b2
3
+ metadata.gz: 19e521403cd36cf7625c8ccd2824cae61db46ca6
4
+ data.tar.gz: 068388117de07b1cf28a342b6135cf1879ba7623
5
5
  SHA512:
6
- metadata.gz: 1c235b23d95dee1557b29fb8b7c10ab930284434f89b0d43fc742b7994460b7874d1e1eb4b58ce39efdf0504578e797e22c0b25eacf1f122743d470992720c74
7
- data.tar.gz: 0405416cfeb48626e6f26580649858938fcd3636c5853e43385f3d28b08220d18508d546e170739835fd781abfdd0f37bf68ccd5dfcc56c54a24363b25bde53b
6
+ metadata.gz: 2644c7baf1ada6bae7f49c7755dc81de31db289294ddff377c7ff990a4a73f4ad3614dd9bee0981d2a463743ce50536909da4b48ff4ed885a7d1c5bb945c03b1
7
+ data.tar.gz: 9deb3cd3329c7c28be94d1cf162e597e9176cfeccdf82353635153e7024b74ae739b22666760f528c93d429a95b7ab859354b73b792bfc9928ae54b6e9b7d284
data/Gemfile CHANGED
@@ -12,6 +12,7 @@ gem "rdoc", "~> 3.12"
12
12
  gem "rake", "~> 10.0"
13
13
  gem "minitest", "~> 4.7"
14
14
  gem "minitest-reporters"
15
+ gem "guard"
15
16
  gem "guard-minitest"
16
17
  gem "simplecov"
17
18
 
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 2167961 Ontario Inc., Zoocasa <code@zoocasa.com>
1
+ Copyright (c) 2014 Zoocasa, Brokerage <code@zoocasa.com>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
@@ -23,45 +23,40 @@ module ActiveRecord
23
23
 
24
24
  module ConnectionAdapters
25
25
  class PostgreSQLAdapter
26
+ # Adds a generic constraint.
27
+ def add_constraint(table, constraint)
28
+ execute("ALTER TABLE #{quote_table_name(table)} ADD #{constraint};")
29
+ end
30
+
26
31
  # Adds a CHECK constraint to the table. See
27
32
  # PostgreSQLCheckConstraint for usage.
28
33
  def add_check_constraint(table, expression, options = {})
29
- sql = "ALTER TABLE #{quote_table_name(table)} ADD "
30
- sql << PostgreSQLCheckConstraint.new(self, expression, options).to_s
31
- execute("#{sql};")
34
+ add_constraint(table, PostgreSQLCheckConstraint.new(self, expression, options))
32
35
  end
33
36
 
34
37
  # Adds a UNIQUE constraint to the table. See
35
38
  # PostgreSQLUniqueConstraint for details.
36
39
  def add_unique_constraint(table, columns, options = {})
37
- sql = "ALTER TABLE #{quote_table_name(table)} ADD "
38
- sql << PostgreSQLUniqueConstraint.new(self, columns, options).to_s
39
- execute("#{sql};")
40
+ add_constraint(table, PostgreSQLUniqueConstraint.new(self, columns, options))
40
41
  end
41
42
 
42
43
  # Adds a FOREIGN KEY constraint to the table. See
43
44
  # PostgreSQLForeignKeyConstraint for details.
44
45
  def add_foreign_key_constraint(table, columns, ref_table, *args)
45
- sql = "ALTER TABLE #{quote_table_name(table)} ADD "
46
- sql << PostgreSQLForeignKeyConstraint.new(self, columns, ref_table, *args).to_s
47
- execute("#{sql};")
46
+ add_constraint(table, PostgreSQLForeignKeyConstraint.new(self, columns, ref_table, *args))
48
47
  end
49
48
  alias :add_foreign_key :add_foreign_key_constraint
50
49
 
51
50
  # Adds an EXCLUDE constraint to the table. See
52
51
  # PostgreSQLExcludeConstraint for details.
53
52
  def add_exclude_constraint(table, excludes, options = {})
54
- sql = "ALTER TABLE #{quote_table_name(table)} ADD "
55
- sql << PostgreSQLExcludeConstraint.new(self, table, excludes, options).to_s
56
- execute("#{sql};")
53
+ add_constraint(table, PostgreSQLExcludeConstraint.new(self, table, excludes, options))
57
54
  end
58
55
 
59
56
  # Adds a PRIMARY KEY constraint to the table. See
60
57
  # PostgreSQLPrimaryKeyConstraint for details.
61
58
  def add_primary_key_constraint(table, columns, options = {})
62
- sql = "ALTER TABLE #{quote_table_name(table)} ADD "
63
- sql << PostgreSQLPrimaryKeyConstraint.new(self, columns, options).to_s
64
- execute("#{sql};")
59
+ add_constraint(table, PostgreSQLPrimaryKeyConstraint.new(self, columns, options))
65
60
  end
66
61
  alias :add_primary_key :add_primary_key_constraint
67
62
 
@@ -44,68 +44,15 @@ module ActiveRecord
44
44
  end
45
45
  end
46
46
 
47
- class PostgreSQLGeometryColumnDefinition
48
- end
47
+ class PostgreSQLGeometryColumnDefinition < ColumnDefinition
48
+ attr_reader :base, :column_name, :options
49
+ attr_accessor :default, :null
49
50
 
50
- class PostgreSQLTableDefinition < TableDefinition
51
- # This is a special spatial type for the PostGIS extension's
52
- # data types. It is used in a table definition to define
53
- # a spatial column.
54
- #
55
- # Depending on the version of PostGIS being used, we'll try to create
56
- # geometry columns in a post-2.0-ish, typmod-based way or a pre-2.0-ish
57
- # AddGeometryColumn-based way. We can also add CHECK constraints and
58
- # create a GiST index on the column all in one go.
59
- #
60
- # In versions of PostGIS prior to 2.0, geometry columns are created using
61
- # the AddGeometryColumn and will created with CHECK constraints where
62
- # appropriate and entries to the <tt>geometry_columns</tt> will be
63
- # updated accordingly.
64
- #
65
- # In versions of PostGIS after 2.0, geometry columns are creating using
66
- # typmod specifiers. CHECK constraints can still be created, but their
67
- # creation must be forced using the <tt>:force_constraints</tt> option.
68
- #
69
- # The <tt>geometry</tt> and <tt>geography</tt> methods are shortcuts to
70
- # calling the <tt>spatial</tt> method with the <tt>:spatial_column_type</tt>
71
- # option set accordingly.
72
- #
73
- # ==== Options
74
- #
75
- # * <tt>:spatial_column_type</tt> - the column type. This value can
76
- # be one of <tt>:geometry</tt> or <tt>:geography</tt>. This value
77
- # doesn't refer to the spatial type used by the column, but rather
78
- # by the actual column type itself.
79
- # * <tt>:geometry_type</tt> - set the geometry type. The actual
80
- # data type is either "geometry" or "geography"; this option refers to
81
- # the spatial type being used.
82
- # * <tt>:add_constraints</tt> - automatically creates the CHECK
83
- # constraints used to enforce ndims, srid and geometry type.
84
- # The default is true.
85
- # * <tt>:force_constraints</tt> - forces the creation of CHECK
86
- # constraints in versions of PostGIS post-2.0.
87
- # * <tt>:add_geometry_columns_entry</tt> - automatically adds
88
- # an entry to the <tt>geometry_columns</tt> table. We will
89
- # try to delete any existing match in <tt>geometry_columns</tt>
90
- # before inserting. The default is true. This value is ignored in
91
- # versions of PostGIS post-2.0.
92
- # * <tt>:create_gist_index</tt> - automatically creates a GiST
93
- # index for the new geometry column. This option accepts either
94
- # a true/false expression or a String. If the value is a String,
95
- # we'll use it as the index name. The default is true.
96
- # * <tt>:ndims</tt> - the number of dimensions to allow in the
97
- # geometry. This value is either 2 or 3 by default depending on
98
- # the value of the <tt>:geometry_type</tt> option. If the
99
- # <tt>:geometry_type</tt> ends in an "m" (for "measured
100
- # geometries" the default is 3); for everything else, it is 2.
101
- # * <tt>:srid</tt> - the SRID, a.k.a. the Spatial Reference
102
- # Identifier. The default depends on the version of PostGIS being used
103
- # and the spatial column type being used. Refer to the PostGIS docs
104
- # for the specifics, but generally this means either a value of -1
105
- # for versions of PostGIS prior to 2.0 for geometry columns and a value
106
- # of 0 for versions post-2.0 and for all geography columns.
107
- def spatial(column_name, opts = {})
108
- opts = {
51
+ def initialize(base, column_name, opts)
52
+ @base = base
53
+ @column_name = column_name
54
+
55
+ @options = {
109
56
  :spatial_column_type => :geometry,
110
57
  :geometry_type => :geometry,
111
58
  :add_constraints => true,
@@ -115,78 +62,70 @@ module ActiveRecord
115
62
  :srid => ActiveRecord::PostgreSQLExtensions::PostGIS.UNKNOWN_SRID
116
63
  }.merge(opts)
117
64
 
118
- if opts[:ndims].blank?
119
- opts[:ndims] = if opts[:geometry_type].to_s.upcase =~ /M$/
65
+ if options[:ndims].blank?
66
+ options[:ndims] = if options[:geometry_type].to_s.upcase =~ /M$/
120
67
  3
121
68
  else
122
69
  2
123
70
  end
124
71
  end
125
72
 
126
- assert_valid_spatial_column_type(opts[:spatial_column_type])
127
- assert_valid_geometry_type(opts[:geometry_type])
128
- assert_valid_ndims(opts[:ndims], opts[:geometry_type])
73
+ assert_valid_spatial_column_type(options[:spatial_column_type])
74
+ assert_valid_geometry_type(options[:geometry_type])
75
+ assert_valid_ndims(options[:ndims], options[:geometry_type])
129
76
 
130
77
  column_type = if ActiveRecord::PostgreSQLExtensions::PostGIS.VERSION[:lib] < '2.0'
131
- opts[:spatial_column_type]
78
+ options[:spatial_column_type]
132
79
  else
133
- column_args = [ opts[:geometry_type].to_s.upcase ]
80
+ column_args = [ options[:geometry_type].to_s.upcase ]
134
81
 
135
- if ![ 0, -1 ].include?(opts[:srid])
136
- column_args << opts[:srid]
82
+ if ![ 0, -1 ].include?(options[:srid])
83
+ column_args << options[:srid]
137
84
  end
138
85
 
139
- "#{opts[:spatial_column_type]}(#{column_args.join(', ')})"
86
+ "#{options[:spatial_column_type]}(#{column_args.join(', ')})"
140
87
  end
141
88
 
142
- column = self[column_name] || ColumnDefinition.new(base, column_name, column_type)
143
- column.default = opts[:default]
144
- column.null = opts[:null]
89
+ super(base, column_name, column_type)
145
90
 
146
- unless @columns.include?(column)
147
- @columns << column
148
- if opts[:add_constraints] && (
149
- ActiveRecord::PostgreSQLExtensions::PostGIS.VERSION[:lib] < '2.0' ||
150
- opts[:force_constraints]
91
+ @default = options[:default]
92
+ @null = options[:null]
93
+
94
+ if options[:add_constraints] && (
95
+ ActiveRecord::PostgreSQLExtensions::PostGIS.VERSION[:lib] < '2.0' ||
96
+ options[:force_constraints]
97
+ )
98
+ table_constraints << PostgreSQLCheckConstraint.new(
99
+ base,
100
+ "ST_srid(#{base.quote_column_name(column_name)}) = (#{options[:srid].to_i})",
101
+ :name => "enforce_srid_#{column_name}"
102
+ )
103
+
104
+ table_constraints << PostgreSQLCheckConstraint.new(
105
+ base,
106
+ "ST_ndims(#{base.quote_column_name(column_name)}) = #{options[:ndims].to_i}",
107
+ :name => "enforce_dims_#{column_name}"
151
108
  )
152
- table_constraints << PostgreSQLCheckConstraint.new(
153
- base,
154
- "ST_srid(#{base.quote_column_name(column_name)}) = (#{opts[:srid].to_i})",
155
- :name => "enforce_srid_#{column_name}"
156
- )
157
109
 
110
+ if options[:geometry_type].to_s.upcase != 'GEOMETRY'
158
111
  table_constraints << PostgreSQLCheckConstraint.new(
159
112
  base,
160
- "ST_ndims(#{base.quote_column_name(column_name)}) = #{opts[:ndims].to_i}",
161
- :name => "enforce_dims_#{column_name}"
113
+ "geometrytype(#{base.quote_column_name(column_name)}) = '#{options[:geometry_type].to_s.upcase}'::text OR #{base.quote_column_name(column_name)} IS NULL",
114
+ :name => "enforce_geotype_#{column_name}"
162
115
  )
163
-
164
- if opts[:geometry_type].to_s.upcase != 'GEOMETRY'
165
- table_constraints << PostgreSQLCheckConstraint.new(
166
- base,
167
- "geometrytype(#{base.quote_column_name(column_name)}) = '#{opts[:geometry_type].to_s.upcase}'::text OR #{base.quote_column_name(column_name)} IS NULL",
168
- :name => "enforce_geotype_#{column_name}"
169
- )
170
- end
171
116
  end
172
117
  end
118
+ end
173
119
 
174
- # We want to split up the schema and the table name for the
175
- # upcoming geometry_columns rows and GiST index.
176
- current_scoped_schema, current_table_name = if self.table_name.is_a?(Hash)
177
- [ self.table_name.keys.first, self.table_name.values.first ]
178
- elsif base.current_scoped_schema
179
- [ base.current_scoped_schema, self.table_name ]
180
- else
181
- schema, table_name = base.extract_schema_and_table_names(self.table_name)
182
- [ schema || 'public', table_name ]
183
- end
184
-
185
- if opts[:add_geometry_columns_entry] &&
186
- opts[:spatial_column_type].to_s != 'geography' &&
120
+ def geometry_columns_entry(table_name)
121
+ return [] unless options[:add_geometry_columns_entry] &&
122
+ options[:spatial_column_type].to_s != 'geography' &&
187
123
  ActiveRecord::PostgreSQLExtensions::PostGIS.VERSION[:lib] < '2.0'
188
124
 
189
- self.post_processing << sprintf(
125
+ current_scoped_schema, current_table_name = extract_schema_and_table_names(table_name)
126
+
127
+ [
128
+ sprintf(
190
129
  "DELETE FROM \"geometry_columns\" WHERE f_table_catalog = '' AND " +
191
130
  "f_table_schema = %s AND " +
192
131
  "f_table_name = %s AND " +
@@ -194,50 +133,53 @@ module ActiveRecord
194
133
  base.quote(current_scoped_schema.to_s),
195
134
  base.quote(current_table_name.to_s),
196
135
  base.quote(column_name.to_s)
197
- )
136
+ ),
198
137
 
199
- self.post_processing << sprintf(
138
+ sprintf(
200
139
  "INSERT INTO \"geometry_columns\" VALUES ('', %s, %s, %s, %d, %d, %s);",
201
140
  base.quote(current_scoped_schema.to_s),
202
141
  base.quote(current_table_name.to_s),
203
142
  base.quote(column_name.to_s),
204
- opts[:ndims].to_i,
205
- opts[:srid].to_i,
206
- base.quote(opts[:geometry_type].to_s.upcase)
143
+ options[:ndims].to_i,
144
+ options[:srid].to_i,
145
+ base.quote(options[:geometry_type].to_s.upcase)
207
146
  )
208
- end
147
+ ]
148
+ end
209
149
 
210
- if opts[:create_gist_index]
211
- index_name = if opts[:create_gist_index].is_a?(String)
212
- opts[:create_gist_index]
213
- else
214
- "#{current_table_name}_#{column_name}_gist_index"
215
- end
150
+ def geometry_column_index(table_name)
151
+ return [] unless options[:create_gist_index]
152
+
153
+ current_scoped_schema, current_table_name = extract_schema_and_table_names(table_name)
154
+
155
+ index_name = if options[:create_gist_index].is_a?(String)
156
+ options[:create_gist_index]
157
+ else
158
+ "#{current_table_name}_#{column_name}_gist_index"
159
+ end
216
160
 
217
- self.post_processing << PostgreSQLIndexDefinition.new(
161
+ [
162
+ PostgreSQLIndexDefinition.new(
218
163
  base,
219
164
  index_name,
220
165
  { current_scoped_schema => current_table_name },
221
166
  column_name,
222
167
  :using => :gist
223
168
  ).to_s
224
- end
225
-
226
- self
169
+ ]
227
170
  end
228
171
 
229
- def geometry(column_name, opts = {})
230
- self.spatial(column_name, opts)
172
+ def to_sql
173
+ column_sql = "#{base.quote_column_name(name)} #{sql_type}"
174
+ column_options = {}
175
+ column_options[:null] = null unless null.nil?
176
+ column_options[:default] = default unless default.nil?
177
+ add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
178
+ column_sql
231
179
  end
232
180
 
233
- def geography(column_name, opts = {})
234
- opts = {
235
- :srid => ActiveRecord::PostgreSQLExtensions::PostGIS.UNKNOWN_SRIDS[:geography]
236
- }.merge(opts)
237
-
238
- self.spatial(column_name, opts.merge(
239
- :spatial_column_type => :geography
240
- ))
181
+ def table_constraints
182
+ @table_constraints ||= []
241
183
  end
242
184
 
243
185
  private
@@ -296,6 +238,128 @@ module ActiveRecord
296
238
  end
297
239
  end
298
240
  end
241
+
242
+ def extract_schema_and_table_names(table_name)
243
+ # We want to split up the schema and the table name for the
244
+ # upcoming geometry_columns rows and GiST index.
245
+ if table_name.is_a?(Hash)
246
+ [ table_name.keys.first, table_name.values.first ]
247
+ elsif base.current_scoped_schema
248
+ [ base.current_scoped_schema, table_name ]
249
+ else
250
+ schema, table_name = base.extract_schema_and_table_names(table_name)
251
+ [ schema || 'public', table_name ]
252
+ end
253
+ end
254
+ end
255
+
256
+ class PostgreSQLTableDefinition < TableDefinition
257
+ # This is a special spatial type for the PostGIS extension's
258
+ # data types. It is used in a table definition to define
259
+ # a spatial column.
260
+ #
261
+ # Depending on the version of PostGIS being used, we'll try to create
262
+ # geometry columns in a post-2.0-ish, typmod-based way or a pre-2.0-ish
263
+ # AddGeometryColumn-based way. We can also add CHECK constraints and
264
+ # create a GiST index on the column all in one go.
265
+ #
266
+ # In versions of PostGIS prior to 2.0, geometry columns are created using
267
+ # the AddGeometryColumn and will created with CHECK constraints where
268
+ # appropriate and entries to the <tt>geometry_columns</tt> will be
269
+ # updated accordingly.
270
+ #
271
+ # In versions of PostGIS after 2.0, geometry columns are creating using
272
+ # typmod specifiers. CHECK constraints can still be created, but their
273
+ # creation must be forced using the <tt>:force_constraints</tt> option.
274
+ #
275
+ # The <tt>geometry</tt> and <tt>geography</tt> methods are shortcuts to
276
+ # calling the <tt>spatial</tt> method with the <tt>:spatial_column_type</tt>
277
+ # option set accordingly.
278
+ #
279
+ # ==== Options
280
+ #
281
+ # * <tt>:spatial_column_type</tt> - the column type. This value can
282
+ # be one of <tt>:geometry</tt> or <tt>:geography</tt>. This value
283
+ # doesn't refer to the spatial type used by the column, but rather
284
+ # by the actual column type itself.
285
+ # * <tt>:geometry_type</tt> - set the geometry type. The actual
286
+ # data type is either "geometry" or "geography"; this option refers to
287
+ # the spatial type being used, i.e. "POINT", "POLYGON", ""
288
+ # * <tt>:add_constraints</tt> - automatically creates the CHECK
289
+ # constraints used to enforce ndims, srid and geometry type.
290
+ # The default is true.
291
+ # * <tt>:force_constraints</tt> - forces the creation of CHECK
292
+ # constraints in versions of PostGIS post-2.0.
293
+ # * <tt>:add_geometry_columns_entry</tt> - automatically adds
294
+ # an entry to the <tt>geometry_columns</tt> table. We will
295
+ # try to delete any existing match in <tt>geometry_columns</tt>
296
+ # before inserting. The default is true. This value is ignored in
297
+ # versions of PostGIS post-2.0.
298
+ # * <tt>:create_gist_index</tt> - automatically creates a GiST
299
+ # index for the new geometry column. This option accepts either
300
+ # a true/false expression or a String. If the value is a String,
301
+ # we'll use it as the index name. The default is true.
302
+ # * <tt>:ndims</tt> - the number of dimensions to allow in the
303
+ # geometry. This value is either 2 or 3 by default depending on
304
+ # the value of the <tt>:geometry_type</tt> option. If the
305
+ # <tt>:geometry_type</tt> ends in an "m" (for "measured
306
+ # geometries" the default is 3); for everything else, it is 2.
307
+ # * <tt>:srid</tt> - the SRID, a.k.a. the Spatial Reference
308
+ # Identifier. The default depends on the version of PostGIS being used
309
+ # and the spatial column type being used. Refer to the PostGIS docs
310
+ # for the specifics, but generally this means either a value of -1
311
+ # for versions of PostGIS prior to 2.0 for geometry columns and a value
312
+ # of 0 for versions post-2.0 and for all geography columns.
313
+ def spatial(column_name, opts = {})
314
+ column = self[column_name] || PostgreSQLGeometryColumnDefinition.new(base, column_name, opts)
315
+
316
+ unless @columns.include?(column)
317
+ @columns << column
318
+ end
319
+
320
+ table_constraints.concat(column.table_constraints)
321
+ post_processing.concat(column.geometry_columns_entry(table_name))
322
+ post_processing.concat(column.geometry_column_index(table_name))
323
+
324
+ self
325
+ end
326
+ alias_method :geometry, :spatial
327
+
328
+ def geography(column_name, opts = {})
329
+ opts = {
330
+ :srid => ActiveRecord::PostgreSQLExtensions::PostGIS.UNKNOWN_SRIDS[:geography]
331
+ }.merge(opts)
332
+
333
+ self.spatial(column_name, opts.merge(
334
+ :spatial_column_type => :geography
335
+ ))
336
+ end
337
+ end
338
+
339
+ class PostgreSQLTable < Table
340
+ def spatial(column_name, opts = {})
341
+ column = PostgreSQLGeometryColumnDefinition.new(@base, column_name, opts)
342
+
343
+ post_processing.concat(column.geometry_columns_entry(@table_name))
344
+ post_processing.concat(column.geometry_column_index(@table_name))
345
+
346
+ @base.add_column(@table_name, column_name, column.sql_type, opts)
347
+
348
+ column.table_constraints.each do |constraint|
349
+ @base.add_constraint(@table_name, constraint)
350
+ end
351
+ end
352
+ alias_method :geometry, :spatial
353
+
354
+ def geography(column_name, opts = {})
355
+ opts = {
356
+ :srid => ActiveRecord::PostgreSQLExtensions::PostGIS.UNKNOWN_SRIDS[:geography]
357
+ }.merge(opts)
358
+
359
+ self.spatial(column_name, opts.merge(
360
+ :spatial_column_type => :geography
361
+ ))
362
+ end
299
363
  end
300
364
  end
301
365
  end