SQL column type from Arel::Attributes::Attribute object - ruby

tl;dr Given an Arel::Attributes::Attribue object, say Model.arel_table[:created_at] how do get it's SQL type?
Context: I'm bypassing the ActiveRecord infrastructure in favour of Arel to write some SQL reports that need to be generated really efficiently. Using Arel's to_sql method I'm generating the final SQL and executing it directly via ActiveRecord::Base.connection.execute. However, I need to apply SQL transformations to certain columns (eg. change timezone of timestamps stored in GMT). Since the number of columns is large (and varying, based on user input), I don't want to hard code these transformations. I'd like to look at the SQL type of the columns being selected and apply the transformation accordingly.

If you have the ActiveRecord class set up then you have access to its columns and columns_hash methods. Those will give you column objects (instances of ActiveRecord::ConnectionAdapters::Column) and there you should find type and sql_type methods. For example:
> Model.columns_hash['created_at'].type
=> :datetime
> Model.columns_hash['created_at'].sql_type
=> "timestamp without time zone"
The sql_type will be database-specific (that's PostgreSQL above), the type will match the type in your migrations so you probably want to use that instead of sql_type.
That said, you could probably get away with use the usual ActiveRecord relation methods (which should deal with conversions and time zones for you) and then call to_sql at the end:
sql = Model.where('created_at > ?', some_time).select('pancakes').to_sql
and then feed that SQL into execute or select_rows. That will let you use most of the usual ActiveRecord stuff while avoiding the overhead of creating a bunch of ActiveRecord wrappers that you don't care about.

Something that might be helpful specifically in arel is type_cast_for_database. This can be used on an arel table:
Model.arel_table.type_cast_for_database(:id, 'test')
=> 0
Model.arel_table.type_cast_for_database(:id, '47test')
=> 47
While you don't get the type specifically you can see if values like strings are going to be converted to a number or something.
EDIT
It's important to note that this only works if the arel table has a type_caster able_to_type_cast?. If you get it from the model like above, it should have a type caster.

Related

Calling PostGIS functions within Ruby

I need to execute the PostGIS function st_intersection within an SQL SELECT clause in Ruby. At the moment I am doing it as raw SQL query:
sql_query = "SELECT id, ST_ASEWKT(ST_INTERSECTION(geometry, ?)) FROM trips WHERE status='active';"
intersections = Trip.execute_sql(sql_query, self[:geometry].to_s)
This way has the disadvantage that I receive the result as text and I need to parse the objects out of the strings. Much nicer would be the use of the ActiveRecord interface to make queries. However, I could not find any solution yet to run PostGIS functions (e.g. st_intersection) within ActiveRecord.
An earlier version of the activerecord-postgis-adapter's README showed a nice example using the gem squeel:
my_polygon = get_my_polygon # Obtain the polygon as an RGeo geometry
MySpatialTable.where{st_intersects(lonlat, my_polygon)}.first
As this is not part of the current README anymore, I am wondering whether this is not recommended or if there are any better alternatives.
There are two problems to solve here.
The first is using an SQL function within a .select clause. Ordinarily this is pretty easy—you just use AS to give the result a name. Here's an example from the ActiveRecord Rails Guide:
Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)")
The resulting Order objects would have ordered_date and total_price attributes.
This brings us to the second problem, which is that Rails doesn't give us an easy way to parameterize a select (i.e. use a ? placeholder), so (as far as I can tell) you'll need to do it yourself with sanitize_sql_array:
sql_for_select_intersection = sanitize_sql_array([
"ST_ASEWKT(ST_INTERSECTION(geometry, ?)) AS intersection",
geometry,
])
This will return a sanitized SQL fragment like ST_ASEWKT(ST_INTERSECTION(geometry, '...')), which you can then use to specify a field in select:
Trip.where(status: "active").select(:id, sql_for_select_intersection))
The resulting query will return Trip objects with id and intersection attributes.

Nhibernate Update timestamp

Is there a way to do
"UPDATE Item SET start_date = CURRENT_TIMESTAMP" ?
in Nhibernate without using hql/sql.
I am trying to avoid hql/sql because the rest of my code is in criteria. I want to do something like :
var item = session.get<Item>(id)
item.start_date = current_timestamp
There are two ways and sql is correct one.
Either you will
load all entities, change, update and commit, or
write sql query and let dbms handle most of the work
I am trying to avoid hql/sql because the rest of my code is in criteria
That is not a valid argument. Criteria is an API intended for relational search, and it does not support mass updates.
Different tasks, different APIs.
In this case, you can use either HQL or SQL, as the syntax is the same. I recommend the former, because you'll be using your entity/property names instead of table/column ones.

how to escape a string before insert or update in Ruby

In ruby ActiveRecord doesn't provide dynamic binding for update and insert sqls, of course i can use raw sql, but that need maintain connection, so i want to know if there is simpler way to escape update or insert sql before executing like code below:
ActiveRecord::Base.connection.insert(sql)
i think i can write code by gsub, but i know if there has been a ready method to do it.
In Rails >= 3.2.5 the following works for me:
evil_input = '"\';%#{}\"foo'
ActiveRecord::Base.connection.quote(evil_input)
=> "'\"'';%\#{}\\\"foo'"
You could do this:
ActiveRecord::Base.send(:sanitize_sql,["select * from my_table where description='%s' and id='%s'","mal'formed", 55], "my_table")
Of course, this means that you have the params separately. Not sure if it will work otherwise, but try it out.
It's not real clear what you are asking for because your title talks about escaping a string before insert or update, then in the tags you talk about SQL injection.
If you need to have ActiveRecord automatically encode/modify content before insertion or updating, have you checked ActiveRecord's Callbacks? The before_save callback will fire when you do an update or create. If you want to modify the content before it's to be stored, that's a good place to do it.
If you want to use SQL parameters instead of inserting the variables into your "update" or "insert" statements to avoid SQL injection, then use AR's variable bindings. Scroll down to the section on "Conditions".

Using SQL function as a group by field in datamapper (ruby)

I'm trying to use a slightly more interesting GROUP BY cause than what seems to be supported out of the box by DataMapper.
The query I want to generate looks something like this:
SELECT SUM(downloads), SUM(uploads) FROM daily_stats GROUP BY YEARWEEK(date)
I'd like to be able to just tell DataMapper something along these lines:
DailyStat.aggregate(:downloads.sum, :uploads.sum, :fields => [ :date.yearweek ])
Where would I begin? Is this easily achievable?
I would very much like the same thing, unfortunately dm-aggregates isn't written in a manner which makes this possible.
My recommendation is to drop down into SQL since you're receiving sums from your table.
DataMapper.repository.adapter.select('SELECT SUM(downloads), SUM(uploads) FROM daily_stats GROUP BY YEARWEEK(date)')
That should give you an array of structs with the summed downloads and uploads.

Stop Activerecord from loading Blob column

How can I tell Activerecord to not load blob columns unless explicitly asked for? There are some pretty large blobs in my legacy DB that must be excluded for 'normal' Objects.
I just ran into this using rail 3.
Fortunately it wasn't that difficult to solve. I set a default_scope that removed the particular columns I didn't want from the result. For example, in the model I had there was an xml text field that could be quite long that wasn't used in most views.
default_scope select((column_names - ['data']).map { |column_name| "`#{table_name}`.`#{column_name}`"})
You'll see from the solution that I had to map the columns to fully qualified versions so I could continue to use the model through relationships without ambiguities in attributes. Later where you do want to have the field just tack on another .select(:data) to have it included.
fd's answer is mostly right, but ActiveRecord doesn't currently accept an array as a :select argument, so you'll need to join the desired columns into a comma-delimited string, like so:
desired_columns = (MyModel.column_names - ['column_to_exclude']).join(', ')
MyModel.find(id, :select => desired_columns)
I believe you can ask AR to load specific columns in your invocation to find:
MyModel.find(id, :select => 'every, attribute, except, the, blobs')
However, this would need to be updated as you add columns, so it's not ideal. I don't think there is any way to specifically exclude one column in rails (nor in a single SQL select).
I guess you could write it like this:
MyModel.find(id, :select => (MyModel.column_names - ['column_to_exclude']).join(', '))
Test these out before you take my word for it though. :)
A clean approach requiring NO CHANGES to the way you code else where in your app, i.e. no messing with :select options
For whatever reason you need or choose to store blobs in databases.
Yet, you do not wish to mix blob columns in the same table as your
regular attributes. BinaryColumnTable helps you store ALL blobs in
a separate table, managed transparently by an ActiveRecord model.
Optionally, it helps you record the content-type of the blob.
http://github.com/choonkeat/binary_column_table
Usage is simple
Member.create(:name => "Michael", :photo => IO.read("avatar.png"))
#=> creates a record in "members" table, saving "Michael" into the "name" column
#=> creates a record in "binary_columns" table, saving "avatar.png" binary into "content" column
m = Member.last #=> only columns in "members" table is fetched (no blobs)
m.name #=> "Michael"
m.photo #=> binary content of the "avatar.png" file

Resources