views:

305

answers:

2

I'm trying to manually manage some geometry (spatial) columns in a rails model.

When updating the geometry column I do this in rails:

self.geom="POINTFROMTEXT('POINT(#{lat},#{lng})')"

Which is the value I want to be in the SQL updates and so be evaluated by the database. However by the time this has been through the active record magic, it comes out as:

INSERT INTO `places` (..., `geom`) VALUES(...,'POINTFROMTEXT(\'POINT(52.2531519,20.9778386)\')')

In other words, the quotes are escaped. This is fine for the other columns as it prevents sql-injection, but not for this. The values are guaranteed to be floats, and I want the update to look like:

INSERT INTO `places` (..., `geom`) VALUES(...,'POINTFROMTEXT('POINT(52.2531519,20.9778386)')')

So is there a way to turn escaping off for a particular column? Or a better way to do this?

(I've tried using GeoRuby+spatial adapter, and spatial adaptor seems too buggy to me, plus I don't need all the functionality - hence trying to do it directly).

+1  A: 
  1. You can use an after_save method, write them with a direct SQL UPDATE call. Annoying, but should work.

  2. You should be able to create a trigger in your DB migration using the 'execute' method... but I've never tried it.

  3. Dig into ActiveRecord's calculate functionality: max/min/avg, etc. Not sure whether this saves you much over the direct SQL call in after_save. See calculations.rb.

  4. You could patch the function that quotes the attributes (looking for POINTFROMTEXT and then skip the quoting). This is pretty easy to find, as all the methods start with quote. Start with ActiveRecord::Base #quote_value.

ndp
Thanks. I've actually tried the trigger method but it doesn't seem to work - something in mysql bounces the update before the trigger gets called it seems. I have revisited spatial adapter now and it seems to work but is sensitive to the rails version (and maybe also the mysql version - need to do more testing)
frankodwyer
Oh and after_save will probably not work out of the box, as rails first attempts to write a NULL to geom, which fails as there is a not null constraint on geom. So would need somehow to prevent rails updating that attribute itself first.
frankodwyer
+1  A: 

The Rails Spatial Adapter should implement exactly what you need. Although, before I found GeoRuby & Spatial Adapter, I was doing this:

  • Have two fields: one text field and a real geometry field, on the model
  • On a after_save hook, I ran something like this:

    connection.execute "update mytable set geom_column=#{text_column} where id=#{id}"

But the solution above was just a hack, and this have additional issues: I can't create a spatial index if the column allows NULL values, MySQL doesn't let me set a default value on a geometry column, and the save method fails if the geometry column doesn't have a value set.

So I would try GeoRuby & Spatial Adapter instead, or reuse some of its code (on my case, I am considering extracting only the GIS-aware MysqlAdapter#quote method from the Spatial Adapter code).

ehabkost
thanks, this is what I wound up doing. I found that it is a bit fussy about rails versions (e.g. didn't seem to work on the latest when I tried but was ok on 2.3.2), but when it works it does the trick.
frankodwyer
The amount of rails-version check trickery on the Spatial Adapter code was what the reason why I chose to just copy the MysqlAdapter.quote code instead of trying to use the whole module. :)
ehabkost