How can I use PostGIS functions in rails? - ruby-on-rails

The query I'd like to run is:
SELECT zcta.geoid10, ST_AsGeoJSON(ST_simplify(zcta.geom,500)) FROM zcta WHERE zcta.geoid10 = '90210'
However in the Rails console when I enter this:
testquery = "SELECT zcta.geoid10, ST_AsGeoJSON(ST_simplify(zcta.geom,500)) FROM zcta WHERE zcta.geoid10 = '90210'"
Zcta.find_by_sql testquery
I get the following returned:
=> [#<Zcta >]
If I do a basic query asking for the result of any column I get the response I expect. This only happens with PostGIS functions. Any idea what to do?

Alias the calculated column and you will get a method added to the returned objects.
Zcta.
select("*, ST_AsGeoJSON(ST_simplify(geom,500)) as my_geo").
where(geoid10: '90210').each do |result|
puts result.my_geo
end

Related

Rails show record start from db with specific letters

I want to show only those inquiry processes where company name starts from specific letters. I thought I should use start_with? but in the code below I've an error
NoMethodError (undefined method `start_with?'
def call
InquiryProcess.all.includes(inquiry_field_responses: :inquiry_field).select do |process|
process.inquiry_field_responses.select do |inquiry_field_responses|
inquiry_field_responses.inquiry_field.name == 'company_name'
end&.last&.value == start_with?('aaa')
end
end
I would do something like this:
field = InquiryField.find_by!(name: 'company_name')
response_table = InquiryFieldResponse.arel_table
responses = field.inquiry_field_responses.where(response_table[:value].matches('aaa%'))
processes = InquiryProcess.where(id: responses.select(:inquiry_process_id))
First select the field that you want to check the values of. From there select all responses that belong to that specific field and start with 'aaa'. Then select the processes using the responses.
The issue with your current code is that you'll do:
'some string' == start_with?('aaa')
Which should be:
'some string'.start_with?('aaa')
Or more specific to your case:
end.last&.value&.start_with?('aaa')
It's much more easier.
Just use SQL for this:
InquiryProcess.where("company_name LIKE ?", "aaa%")
This shows you all inquiry_processes that company_name starts with aaa

How to access raw SQL statement generated by update_all (ActiveRecord method)

I'm just wondering if there's a way to access the raw SQL that's executed for an update_all ActiveRecord request. As an example, take the simple example below:
Something.update_all( ["to_update = ?"], ["id = ?" my_id] )
In the rails console I can see the raw SQL statement so I'm guessing it's available for me to access in some way?
PS - I'm specifically interested in update_all and can't change it to anything else.
Thanks!
If you look at the way update_all is implemented you can't call to_sql on it like you can on relations since it executes directly and returns an integer (the number of rows executed).
There is no way to tap into the flow or get the desired result except by duplicating the entire method and changing the last line:
module ActiveRecord
# = Active Record \Relation
class Relation
def update_all_to_sql(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
if eager_loading?
relation = apply_join_dependency
return relation.update_all(updates)
end
stmt = Arel::UpdateManager.new
stmt.set Arel.sql(#klass.sanitize_sql_for_assignment(updates))
stmt.table(table)
if has_join_values? || offset_value
#klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
else
stmt.key = arel_attribute(primary_key)
stmt.take(arel.limit)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
end
#- #klass.connection.update stmt, "#{#klass} Update All"
stmt.to_sql
end
end
end
The reason you see the log statements is that they are logged by the connection when it executes the statements. While you can override the logging its not really possible to do it for calls from a single AR method.
If you have set RAILS_LOG_LEVEL=debug Rails shows you which SQL statement it executed.
# Start Rails console in debug mode
$ RAILS_LOG_LEVEL=debug rails c
# Run your query
[1] pry(main)> Something.update_all( ["to_update = ?"], ["id = ?" my_id] )
SQL (619.8ms) UPDATE "somethings" WHERE id = 123 SET to_update = my_id;
# ^it prints out the query it executed

How to use ST_GeoHash and ST_MakePoint postgis functions in rails before save?

How to build in rails postgis point, then geohash and save them into database before send response to client? I would like to make it through ST_MakePoint and ST_GeoHash function, I prefer to avoid execute SQL and extracting data by [0]["st_makepoint"], if it is possible how to insert this functions to execute them automatically when inserting all attributes? I've installed squeel gem, maybe can I merge this functions to the query?
My current rails code:
before_save :set_geopoint
def set_geopoint
#attributes -> {"latitude" => 51.90,"longitude" => 16.42,"geopoint" => nil}
#self.geopoint = "ST_MakePoint(#{latitude}, #{longitude})")" #not working
#self.geopoint = ActiveRecord::Base.connection.execute("SELECT ST_MakePoint(#{latitude}, #{longitude})")[0]["st_makepoint"]
#self.geohash = "ST_GeoHash(#{self.geopoint})"
#self.geohash = ActiveRecord::Base.connection.execute("SELECT ST_GeoHash(ST_SetSRID(#{self.geopoint},4326),5);").first["st_geohash"]
end
I did it through SQL before trigger function but I'm still looking for rails approach.
CREATE FUNCTION geopoint_trigger() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO posts( geopoint ) VALUES( ST_GeomFromText('POINT(' || NEW.latitude || ' ' || NEW.longitude || ')') );
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
To generate a point that you can save in a postgis enabled database, you need to generate that point with a factory. What you're doing here:
self.geopoint = "ST_MakePoint(#{latitude}, #{longitude})")" #not working
is just setting self.geopoint to a string and trying to save it. As your database geopoint field is (I assume) set to accept a point, it fails.
I suggest you use the rgeo gem to add geo factories to your models. Add it to your Gemfile.
https://github.com/rgeo/rgeo
You need to make sure that in your migration, you are using a point as column type like this:
t.point :geopoint, geographic: true
In your Post model, you then need to specify a factory like this:
RGEO_FACTORY = RGeo::Geographic.spherical_factory(srid: 4326)
And you also need to tell rgeo what factory to use on your geopoint column.
set_rgeo_factory_for_column :geopoint, RGEO_FACTORY
Now in your before_save, simply do:
self.geopoint = RGEO_FACTORY.point(latitude, longitude)
and it should work...
EDIT
If you want to use Postgis functions in your Rails models to get the GeoHash for instance, then you could do something like this:
post_geohash = Post.select("ST_GeoHash(geopoint) as geohash").where(id: some_post_id).geohash
You could also create an instance method that does that on your Post model:
def geohash
Post.select("ST_GeoHash(geopoint) as geohash").where(id: id).geohash
end
Not sure this works as it's not tested but you get the idea.

dynamic query for different values rails 4 activerecord

Here is the query I am trying in my controller
query = []
if id
query = "category_id: #{id}"
end
#posts = Post.where(query)
But throwing error as ERROR: syntax error at or near ":"
Why this is not working any other way to do it
if id
query << {sub_category_id: id}
end
if test
query << {test_id: test}
end
#posts = Post.where(query)
Is there any way of doing like this
Change query to a hash instead of string:
if id
query = { category_id: id }
end
#posts = Post.where(query)
The reason query = "category_id: #{id}" did not work is because the supplied string is literally used in the query generated by ActiveRecord, i.e. your select query will have category_id: 1 (assuming id is 1) in the where clause. And this is not a valid SQL syntax.
Please read on how you can use strings in conditions following this link. Thanks to #RustyToms for suggesting the link.
Update: ( Add extra conditions to the query hash )
if id
query[:sub_category_id] = id
end
if test
query[:test_id] = test
end
#posts = Post.where(query)
Another way to do this:
#posts = Post.scoped
#posts = #posts.where(category_id: id) if id
(in case you're playing codegolf)
Edit: (this is definitely a side note that isn't at all relevant)
Your original solution relies on one of my least favorite features of Ruby. Consider the following code:
if false
a = 4
end
puts a
I would expect the puts a to fail with a NameError (undefined local variable "a"), but no! The Ruby parser hits a = and then initalizes its value to nil. So, despite the fact that there is no way for the innards of that if statement to run, it still impacts the other code.

Converting Rails model to SQL insert Query?

Is there a way to convert a Rails model into an insert query?
For instance, if I have a model like:
m = Model.new
m.url = "url"
m.header = "header"
How can I get the corresponding SQL query ActiveRecord would generate if I did m.save?
I want to get: "INSERT INTO models(url, header) VALUES('url', 'header')" if possible.
Note: I don't want to actually save the model and get the query back (from log file, etc). I want to get the query IF I chose to save it.
On Rails 4.1, I found the below code snippet working:
record = Post.new(:title => 'Yay', :body => 'This is some insert SQL')
record.class.arel_table.create_insert
.tap { |im| im.insert(record.send(
:arel_attributes_with_values_for_create,
record.attribute_names)) }
.to_sql
Thanks to https://coderwall.com/p/obrxhq/how-to-generate-activerecord-insert-sql
Tested in Rails 3.2.13: I think I got it right this time, it definitely does not persist to the db this time. It also won't fire validations or callbacks so anything they change won't be in the results unless you've called them some other way.
Save this in lib as insert_sqlable.rb and you can then
#in your models or you can send it to ActiveRecord::Base
include InsertSqlable
Then it is model.insert_sql to see it.
#lib/insert_sqlable
module InsertSqlable
def insert_sql
values = arel_attributes_values
primary_key_value = nil
if self.class.primary_key && Hash === values
primary_key_value = values[values.keys.find { |k|
k.name == self.class.primary_key
}]
if !primary_key_value && connection.prefetch_primary_key?(self.class.table_name)
primary_key_value = connection.next_sequence_value(self.class.sequence_name)
values[self.class.arel_table[self.class.primary_key]] = primary_key_value
end
end
im = self.class.arel_table.create_insert
im.into self.class.arel_table
conn = self.class.connection
substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
binds = substitutes.map do |arel_attr, value|
[self.class.columns_hash[arel_attr.name], value]
end
substitutes.each_with_index do |tuple, i|
tuple[1] = conn.substitute_at(binds[i][0], i)
end
if values.empty? # empty insert
im.values = Arel.sql(self.class.connectionconnection.empty_insert_statement_value)
else
im.insert substitutes
end
conn.to_sql(im,binds)
end
end
It turns out the code is in ActiveRecord::Relation and not ActiveRecord::Persistence. The only significant change is the last line which generates the sql instead of performing it.
If you dont want to save the model you call m.destroy when you are done with the object.
You can log the sql query by debugging it like this
Rails.logger.debug "INSERT INTO models(url, header) VALUES(#{m.url}, #{m.header}).inspect
After search a lot over the Internet and forums, I think I found a better solution for your problem: just requires two line of code.
I found a good gem that do exactly what you want, but this gem only works for Rails 3.2 and older. I talked with author and he doesn't want support this gem anymore. So I discovered by myself how to support Rails 4.0 and now I'm maintaining this gem.
Download the "models-to-sql-rails" gem here, supporting Rails 4.0 and older.
With this gem, you can easily do the following. (the examples inside values are just a joke, you will get the correct values when using it in your object).
For objects:
object.to_sql_insert
# INSERT INTO modelName (field1, field2) VALUES ('Wow, amaze gem', 'much doge')
For array of objets:
array_of_objects.to_sql_insert
# INSERT INTO modelName (field1, field2) VALUES ('Awesome doge', "im fucking cop")
# INSERT INTO modelName (field1, field2) VALUES ('much profit', 'much doge')
# (...)
Just see the Github of this project and you'll find how to install and use this wonderful gem.

Resources