activerecord-postgresql-extensions 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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