How can I access an ActiveRecord column that is a hash through the rails console? I am trying to get the properties column.
Example:
=> #<Ahoy::Event:0x007ffde80e3088
id: "abc123def465ghi798j9",
visit_id: "098dca809oiu567hjg678jh",
user_id: 26,
name: "click",
properties:
{"host"=>"localhost/sponsors",
"name"=>"click",
"type"=>"post-to-host",
"value"=>0,
"target"=>"whereintheworldiscarmonsandiego.com/rd/r.php",
"partner"=>"Uber",
"trigger"=>"click",
"utm_source"=>1234567890,
"utm_campaign"=>"organic"},
time: Fri, 06 May 2016 21:42:23 UTC +00:00,
user_type: "Applicant">
I am attempting:
semi/pseudo code
Ahoy::Event.where(properties["type"]: "post-to-host")
if this is a sequel style database.. you can't. the issue with serializing a column (using a hash column) is that youre bypassing the intrinsic sequel abstractions. which means you can't search by that column (effectively).
You can either break out properties to a relationship or do some really hacky stuff like
Ahoy::Event.where('properties LIKE ?', {type: 'post-to-host'})
essentially, whats happening is that the column is actually stored as a yaml formatted string, and when you access it in ruby, it just serializes the string from yaml, so in order to query on it, you'd have to query it like a string which will be pretty ineffective and generally error prone.
tl;dr dont use hash columns (serialize) for data you intend to query on
In Ahoy, we have built method to access json properties. You cn try following syntax:
1. Ahoy::Event.where_properties(type: "post-to-host")
2. Ahoy::Event.where_props(type: "post-to-host")
Related
I have an 'Audit' model with name:string and data:hstore attributes - as follows:
#<Audit
id: 1,
name: "my audit",
data: {"publisher"=>"some publisher", "display_version"=>"17.012.20098"},
created_at: "2017-10-10 13:09:56",
updated_at: "2017-10-10 13:09:56">
I want to produce a report that contains 3 columns:
name, display_version, count
To achieve this I assume that I need to group by "name" and "data -> 'display_version'"
I am struggling with the query and AR syntax.
What I have so far is:
Parent.audits.group(:name, :data -> 'display_version)'
Even this doesn't work. Can someone help? I have searched for examples without any luck.
You can pass a string containing the SQL grouping expression to group so you could say:
Parent.audits.group(%q{name, data -> 'display_version'})
or:
Parent.audits.group(%q{audits.name, audits.data -> 'display_version'})
And then you could throw a #count call on the end to get the data you want:
Parent.audits.group(%q{name, data -> 'display_version'}).count
Use jsonb instead of hstore (for a variety of reasons)
'Audit' model with name:string and data:jsonb
Audit.rb
store_accessor :data, :publisher, :display_version
Then try:
Parent.audits.pluck(:name, :publisher, :display_version)
Good Resource on JSONB and Postgresql
hstore columns don’t allow a nested structure; they also store all values as strings, which will require type coercion on both database and application layers. With json/jsonb columns you don’t have this problem; numbers (integers/float), booleans, arrays, strings and nulls are accepted, and you can even nest structures in any way you want.
The recommendation is that you stop using hstore in favor of jsonb;
just remember that you have to use PostgreSQL 9.4+ for this to work.
I am fetching values from a database table (client). I am interested in two columns (first_name and id). Since I am sending the response as a json format, I would like to achieve a result like this
students: ["first_name": "adam", id:"12", ....]
I tried Student.all.map{|s| [s.first_name, s.id]} but here I just get the values(adam, 12) not the indexes ("first_name", "id")
is there a way to make the column name appear in the response without complicating the query
Less magic, then mohamed's answer, just using basic tools:
#students.map{|st| {id: st.id, first_name: st.first_name}}
You can use only with json:
Student.all.to_json(:only=>[:id, :first_name])
That will work if you not overriding as_json or to_json in student model
Is it possible to combine active record query with postgreSQL raw query ?
Czce.where("time > ? ", "2014-02-09".to_datetime).raw_query
def self.raw_query
raw_q = 'SELECT cast(ticktime as timestamp(1)) AS ticktime
,max(bid_price) as price, max(bid_volume) as volume
From czces
Group BY 1
ORDER BY 1
limit 1000;'
ActiveRecord::Base.connection.select_all(raw_q)
end
If I do this with find_by_sql, the result from database is missing many columns.
And the conditional .where("ticktime > ? ", "2014-02-09".to_datetime) still not works
Here's the query expression Czce.where("ticktime > ? ", "2014-02-09".to_datetime).find_by_sql(raw_q)
[998] #<Czce:0x007fc881443080> {
:id => nil,
:ticktime => Fri, 07 Feb 2014 01:16:41 UTC +00:00
},
[999] #<Czce:0x007fc881442d38> {
:id => nil,
:ticktime => Fri, 07 Feb 2014 01:16:42 UTC +00:00
}
But the expected result should contains price, volume
from (pry):3:in `block (2 levels) in <top (required)>'
[4] pry(main)> result[0]
{
"ticktime" => "2014-02-28 07:00:00",
"price" => "7042",
"volume" => "2"
}
[5] pry(main)> result[1]
{
"ticktime" => "2014-02-28 06:59:59",
"price" => "18755",
"volume" => "525"
}
In short, no.
Raw select_all queries are done as pure SQL sent to the server, and records sent back as raw data.
From the Rails Guide for select_all (emphasis mine):
select_all will retrieve objects from the database using custom SQL
just like find_by_sql but will not instantiate them. Instead, you will
get an array of hashes where each hash indicates a record.
You could iterate over the resulting records and do something with those, perhaps store them in your own class and then use that information in subsequent calls via ActiveRecord, but you can't actually directly chain the two. If you're going to drop down into raw SQL (and certainly there are myriad reasons you may want to do this), you might as well grab everything else you would need in that same context at the same time, in the same raw query.
There's also find_by_sql, which will return an array of instantiated ActiveRecord objects.
From the guide:
The find_by_sql method will return an array of objects even if the
underlying query returns just a single record.
And:
find_by_sql provides you with a simple way of making custom calls to
the database and retrieving instantiated objects.
However, that's an actual instantiated object, which, while perhaps easier in many respects, since they would be mapped to an instance of the model and not simply a hash, chaining, say, where to that is not the same as a chained call to the base model class, as would normally be done.
I would recommend doing everything you can in the SQL itself, all server side, and then any further touch-up filtering you want to do can be done client-side in Rails by iterating over the records that are returned.
Just try to not use base connection itself, try expand the standard rails sql form like:
Czces.select("cast(ticktime as timestamp(1)) AS ticktime,max(bid_price) as price, max(bid_volume) as volume")
.group("1").order("1").limit(1000)
but if you just explain a condition, i.e. want do you really wnat to get from a query, we'll try to write a proper sql.
For various reasons, I'm creating an app that takes a SQL query string as a URL parameter and passes it off to Postgres(similar to the CartDB SQL API, and CFPB's Qu). Rails then renders a JSON response of the results that come from Postgres.
Snippet from my controller:
#table = ActiveRecord::Base.connection.execute(#query)
render json: #table
This works fine. But when I use Postgres JSON functions (row_to_json, json_agg), it renders the nested JSON property as a string. For example, the following query:
query?q=SELECT max(municipal) AS series, json_agg(row_to_json((SELECT r FROM (SELECT sch_yr,grade_1 AS value ) r WHERE grade_1 IS NOT NULL))ORDER BY sch_yr ASC) AS values FROM ed_enroll WHERE grade_1 IS NOT NULL GROUP BY municipal
returns:
{
series: "Abington",
values: "[{"sch_yr":"2005-06","value":180}, {"sch_yr":"2005-06","value":180}, {"sch_yr":"2006-07","value":198}, {"sch_yr":"2006-07","value":198}, {"sch_yr":"2007-08","value":158}, {"sch_yr":"2007-08","value":158}, {"sch_yr":"2008-09","value":167}, {"sch_yr":"2008-09","value":167}, {"sch_yr":"2009-10","value":170}, {"sch_yr":"2009-10","value":170}, {"sch_yr":"2010-11","value":153}, {"sch_yr":"2010-11","value":153}, {"sch_yr":"2011-12","value":167}, {"sch_yr":"2011-12","value":167}]"
},
{
series: "Acton",
values: "[{"sch_yr":"2005-06","value":353}, {"sch_yr":"2005-06","value":353}, {"sch_yr":"2006-07","value":316}, {"sch_yr":"2006-07","value":316}, {"sch_yr":"2007-08","value":323}, {"sch_yr":"2007-08","value":323}, {"sch_yr":"2008-09","value":327}, {"sch_yr":"2008-09","value":327}, {"sch_yr":"2009-10","value":336}, {"sch_yr":"2009-10","value":336}, {"sch_yr":"2010-11","value":351}, {"sch_yr":"2010-11","value":351}, {"sch_yr":"2011-12","value":341}, {"sch_yr":"2011-12","value":341}]"
}
So, it only partially renders the JSON, running into problems when I have nested JSON arrays created with the Postgres functions in the query.
I'm not sure where to start with this problem. Any ideas? I am sure this is a problem with Rails.
ActiveRecord::Base.connection.execute doesn't know how to unpack database types into Ruby types so everything – numbers, booleans, JSON, everything – you get back from it will be a string. If you want sensible JSON to come out of your controller, you'll have to convert the data in #table to Ruby types by hand and then convert the Ruby-ified data to JSON in the usual fashion.
Your #table will actually be a PG::Result instance and those have methods such as ftype (get a column type) and fmod (get a type modifier for a column) that can help you figure out what sort of data is in each column in a PG::Result. You'd probably ask the PG::Result for the type and modifier for each column and then hand those to the format_type PostgreSQL function to get some intelligible type strings; then you'd map those type strings to conversion methods and use that mapping to unpack the strings you get back. If you dig around inside the ActiveRecord source, you'll see AR doing similar things. The AR source code is not for the faint hearted though, sorry but this is par for the course when you step outside the narrow confines of how AR things you should interact with databases.
You might want to rethink your "sling hunks of SQL around" approach. You'll probably have an easier time of things (and be able to whitelist when the queries do) if you can figure out a way to build the SQL yourself.
The PG::Result class (the type of #table), utilizes TypeMaps for type casts of result values to ruby objects. For your example, you could use PG::TypeMapByColumn as follows:
#table = ActiveRecord::Base.connection.execute(#query)
#table.type_map = PG::TypeMapByColumn.new [nil, PG::TextDecoder::JSON.new]
render json: #table
A more generic approach would be to use the PG::TypeMapByOid TypeMap class. This requires you to provide OIDs for each PG attribute type. A list of these can be found in pg_type.dat.
tm = PG::TypeMapByOid.new
tm.add_coder PG::TextDecoder::Integer.new oid: 23
tm.add_coder PG::TextDecoder::Boolean.new oid: 16
tm.add_coder PG::TextDecoder::JSON.new oid: 114
#table.type_map = tm
This is one example of one entry in my database:
Market id: 1, name: "Independence Park (Independently Run Farmers Market...", address: "3945 N. Springfield Ave., Chicago, IL", zipcode: "60618", created_at: "2013-01-01 21:22:24", updated_at: "2013-01-01 21:22:24"
All I want to do is list the 43 zipcodes from all the entries in my database. Why don't these queries work?
Market.all.each { |m| m.zipcode }
Market.all.zipcode
m = Market.all
m.each{ |m| m.zipcode }
Thanks!
If all you want is an array of zip codes, I would suggest to try this:
Market.pluck(:zipcode)
The other answers have pointed out the correct way to do what you want, but haven't explained why your other attempts didn't work (which is technically the question you asked).
The reason attempts 1 and 3 don't work is that each doesn't return anything, it's only use is to loop through a set of records and perform an action on them (such as updating them or using some data to call an external service). Replacing each with map would fix them, as map uses the return value of the block to return an array of values. This has a disadvantage in that map is an Array method, so all of your records will have to be loaded into memory before the values can be found.
Attempt 2 doesn't work as you're trying to call a field name on an ActiveRecord::Relation (all), so you'll end up raising a NoMethodError.
The neatest solution, and one that has already been pointed out, is to use pluck, which not only returns all the values for the requested fields, but does so at the database query level, so you call should be more efficient.
You could also do the following, it returns an array of zipcodes:
Market.all.map(&:zipcode)
Use Benchmark to determine which is better.