find_by() in json data - ruby-on-rails

I am using ahoy gem for analytics. In the ahoy_events table, I have properties column of json data type. I want to find specific data based on that column.
Suppose I have
{"tag":"a","class":"bigyapan-6","page":"/client/dashboard","text":"","href":"http://www.google.com"}
this as data and I want to find_by class.
In the rails c I ran Ahoy::Event.find_by(properties[:class]: "bigyapan-6") and it gave me an err
Ahoy::Event.find_by(properties["class"]: "bigyapan-6")
SyntaxError: unexpected ')', expecting end-of-input

This is a syntax error since properties[:class] is not a valid hash key in Ruby. To query Postgres JSON columns you need to provide the query as a string:
Ahoy::Event.find_by("properties ->> 'class' = 'bigyapan-6'")
ActiveRecord does not take a nested hash in this case like it would for an association. I doubt that ActiveRecord will ever support this since its very RBDMS specific and the type coercion thing (-> vs ->>) would make it really complex.
# this won't work.
Ahoy::Event.find_by(properties: { class: 'bigyapan-6' })
See:
http://edgeguides.rubyonrails.org/active_record_postgresql.html#json
http://robertbeene.com/rails-4-2-and-postgresql-9-4/

Try the following:
Ahoy::Event.find_by properties["class"].to_sym 'bigyapan-6'
I think it is getting confused with string followed by :

Related

Rails - Querying a JSON Field

In my Rails 5 app, I'm using the Ahoy gem which uses a jsonb field (called properties) that I don't know how to query.
A sample of the data in the properties field:
"{\"id\":\"11\"}"
What I'm trying to do is:
Find the records created in the last seven days (using the time field)
Group the records by the id in the properties field
Order the records based on which id had the most records
I tried this:
#foo = Ahoy::Event.where(name: "Viewed Product").where(time: 7.days.ago..Time.now).group("properties(id)").order('COUNT("properties(id)") DESC')
but I received the error, PG::UndefinedColumn: ERROR: column "properties(id)" doe not exist
I found this somewhat similar question in the issues for the gem, so I tried breaking the query into parts:
#foo = Ahoy::Event.where(name: "Viewed Product").where(time: 7.days.ago..Time.now)
#foo.group("properties REGEXP '[{,]\"id\"...).count
but then my IDE gives the error unterminated string meets the end of file.
Can someone please help me determine what I'm doing wrong?
If you are using PostgreSQL, I believe that you would have to refer to json columns like this:
.group("properties ->> 'id'")

Rails serialize not storing correctly

I am setting up stripe connect with the example from https://github.com/rfunduk/rails-stripe-connect-example and am running into a problem using serialize to store stripe_account_status which should be stored as an array.
This is how it should be stored (Generated from the above example link)
{"details_submitted"=>false, "charges_enabled"=>true, "transfers_enabled"=>false, "fields_needed"=>["legal_entity.first_name", "legal_entity.last_name", "legal_entity.dob.day", "legal_entity.dob.month", "legal_entity.dob.year", "legal_entity.address.line1", "legal_entity.address.city", "legal_entity.address.postal_code", "bank_account"], "due_by"=>nil}
And this is how my application is storing it
{:details_submitted=>false, :charges_enabled=>true, :transfers_enabled=>false, :fields_needed=>["legal_entity.first_name", "legal_entity.last_name", "legal_entity.dob.day", "legal_entity.dob.month", "legal_entity.dob.year", "legal_entity.address.line1", "legal_entity.address.city", "legal_entity.address.postal_code", "bank_account"], :due_by=>nil}
As far as I am concerned everything is set up the same. The only difference is that the first example uses
serialize :stripe_account_status, JSON
and my app just has
serialize :stripe_account_status
The reason for this is that when I add JSON I this error:
JSON::ParserError - 795: unexpected token at '':
I have tried finding out the JSON error including changing the config/initializers/cookies_serializer.rb to use :hybrid but this is giving me the same error.
Could someone point me into the right direction of either fixing the JSON issue OR finding a way to make sure the stripe_account_status is stored as an array correctly.
Below is the methods used to store the array:
if #account
user.update_attributes(
currency: #account.default_currency,
stripe_account_type: 'managed',
stripe_user_id: #account.id,
secret_key: #account.keys.secret,
publishable_key: #account.keys.publishable,
stripe_account_status: account_status
)
end
def account_status
{
details_submitted: account.details_submitted,
charges_enabled: account.charges_enabled,
transfers_enabled: account.transfers_enabled,
fields_needed: account.verification.fields_needed,
due_by: account.verification.due_by
}
end
Thanks I really appreciate any direction you could point me!
When you ask Rails to serialize an attribute on a model, it will default to storing the object as YAML string.
You can ask Rails to serialize differently, as you have noticed by providing a class to do the serialization e.g
serialize :stripe_account_status, JSON
The reason why this isn't working when you add it is because you presumably already have a record in the database using the YAML and so Rails can't parse this as a valid JSON string when reading from the DB. If it's just development data that you don't need, you can delete the records and then use JSON, otherwise you will need to convert the current YAML strings to JSON.
Rails will also symbolize the keys of a hash when parsing a serialized string in the database. This is the only difference between the hashes in your question and shouldn't matter in practise. Should you need String keys for some reason, you can use the #stringify_keys method on the hash provided by Rails.

JSON date string parse error in Rails

I've got a large data model in a Rails application that I'm trying to make serializable and output to JSON. I defined the #serializable_hash method on the model and blacklisted a few attributes. My goal is to whitelist attributes on the controller layer to accept back that same structure and simply ignore values that I don't want "accessible".
One such attribute is giving me trouble when I PUT update with the aforementioned JSON. I get an error while parsing request parameters:
SyntaxError (/Users/brad/.rvm/gems/ruby-1.9.3-p194#project-rails32/gems/actionpack-3.2.13/lib/action_dispatch/http/request.rb:261: syntax error, unexpected tINTEGER, expecting $end
...02933", "software_date"=>"09/05/14", "software_version"=>"10...
... ^):
# stack trace...
As far as Rails is concerned, this is just a string right? Why is it expecting an end of input here? For the record, taking this attribute out before submitting my request results in a successful update, so I'm sure this is what's causing the issue.
Turns out I'm the unluckiest guy in the world.
Familiarize yourself with Ruby's %Q() method, and you'll understand where I'm at.
Under my very very specific case, when Rails is parsing params for a request:
in JSON format
which has a key mapping to an Array
and any value under that Array contains a '/'
the parse will fail. Why?
My version of Rails (3.2.13) uses the AwesomePrint library version 0.3.2 when parsing out parameters for the params hash. The #grep method for that version of the gem evaluates matches using:
%Q/#{match}/
Simply because the specified delimiter is a '/', the evaluation fails and parsing crashes. DO NOT LET THIS HAPPEN TO YOU.

Mongoid: getting mongo error code from Moped::Errors

I'm building a Rails server with the model stored in MongoDB using Mongoid.
There are cases where a user can attempt to add a document to the mongo database with a duplicate index value. Is there a way to retrieve the MongoDB error code (in this case 11000) without parsing the error message so that I can make my exception handling more robust?
EDIT: Title had Mongoid::Errors instead of Moped::Errors
I developed the mongoid_token gem and encountered this exact problem, since the core functionality of this gem relies on being able to identify if a particular field (in this case, the token) is the cause of the key duplication.
If all you're after is the error code, the yes - you can get this. However, if you need more precise details (such as the field name), you will need parse the error description.
Also, if you're testing for duplicate keys, I think you'll need to check for both error codes 11000 and 11001 (duplicate key on update). A partial list of the mongoDB error codes is here.
I've paraphrased some of the code from the gem below:
begin
#... do whatever
rescue Moped::Errors::OperationFailure => e
description = e.details['err']
if [11000, 11001].include?(e.details['code'])
# Duplicate key error
end
end

When would Rails' ActiveRecord generate IN clause with empty parentheses? "IN ()"?

Assuming I call ActiveRecord model Foo like this:
Foo.sum(:bar, :conditions => { :baz_id => some_value })
When would that result in invalid SQL query with empty list in IN clause like below:
SELECT sum(`foos`.bar) AS sum_bar FROM `foos` WHERE (`foos`.baz_id IN ())
I am using activerecord 2.3.12.
I can't check right now as I don't have application with Rails 2.3 running on my machine, but how about empty array? I think AR checks if what you provide is collection (array) and if so, then it generates "IN (...)" part, and then it generates entries for "IN" clause with proper escaping.
EDIT:
Empty array generates (NULL), so it's valid. Array with empty array generates serialized YAML so it's not that case.
I checked in source code and it's possible to generate empty "IN ()" part, you just need to:
provide a parameter which is one of this: Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope, line 2031
it should #respond_to?(:map), but should not #acts_like?(:string), line 2424
its #empty? should return false for some reason, or it should not respond_to?(:empty?), line 2425
it has to respond to #map, but should return empty array, line 2428
And then it generates what you have.
I have no idea how it could happen in real code. Maybe there are other ways to create this case in real life code...
Is that the SQL it generates (IN ())? In my test (AR 2.3.8), it issues a IN (NULL), which works just fine.

Resources