Close.io API connection with Ruby syntax error - ruby

My question pertains to connecting to the Close.io API and specifically updating the custom fields associated with leads - http://developer.close.io/#Leads
It states in your documentation that...
custom: To update a single custom field without removing the others,
use custom.field_name: updated_value instead ofcustom: { all:
'fields', listed: 'here' }. You can also unset a single field by
using custom.field_name: null.
But this is causing some quirks in our project. Every update to a single field continues to remove the others.
So here's our code -
# We're using the close.io gem ( a ruby wrapper ) - https://github.com/taylorbrooks/closeio
# First we get the signed in user's email address
# and query closeio to pull the appropriate lead associated with it
closeio_lead_id = (Closeio::Lead.where query: "email:['#{current_user.email}']")[0]['id']
#Next we're attempting to update the lead custom field of "kk_referral" with a float
(Closeio::Lead.update closeio_lead_id,
custom: { kk_referral: Referral.where( :user_id => current_user.id).count.to_f }
)
# And it works! Yay But then we run the next line to update another custom field...
# the last action is erased. what the hell?
(Closeio::Lead.update closeio_lead_id,
custom.kk_blog_posts_submitted: Comment.where( :user_id => current_user.id).count.to_f
)
# So we attempted to store the in a variable so we can replicate some of the direction provided in the documentation.
closeio_comment = Comment.where( :user_id => current_user.id).count.to_f
(Closeio::Lead.update closeio_lead_id,
custom.kk_blog_posts_submitted: closeio_comment
)
# And nothing is coming through. AHhhh. Only the original syntax works.
When attempting to use the syntax provided in the documentation....
custom.field_name: some_variable_storing_a_float
I get a syntax error relating to the colon after field_name.
And when attempting to use more friendly Ruby syntax....
custom.field_name => some_variable_storing_a_float
I get an error on using the custom local variable. (Exactly reads "NameError: undefined local variable or method `custom' for main:Object )
Any advice? Thanks!

Finally figured it out folks.
Here's the syntax -
Closeio::Lead.update 'lead_id', 'custom.Field Name' => 'Value'

Related

Fix deprecation warning `Dangerous query method` on `.order`

I have a custom gem which creates a AR query with input that comes from an elasticsearch instance.
# record_ids: are the returned ids of the ES results
# order: is the order of the of the ids that ES returns
search_class.where(search_class.primary_key => record_ids).order(order)
Right now the implementation is that I build the order string directly into the order variable so it looks like this: ["\"positions\".\"id\" = 'fcdc924a-21da-440e-8d20-eec9a71321a7' DESC"]
This works fine but throws a deprecation warning which ultimately will not work in rails6.
DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s): "\"positions\".\"id\" = 'fcdc924a-21da-440e-8d20-eec9a71321a7' DESC". Non-attribute arguments will be disallowed in Rails 6.0. This method should not be called with user-provided values, such as request parameters or model attributes. Known-safe values can be passed by wrapping them in Arel.sql()
So I tried couple of different approaches but all of them with no success.
order = ["\"positions\".\"id\" = 'fcdc924a-21da-440e-8d20-eec9a71321a7' DESC"]
# Does not work since order is an array
.order(Arel.sql(order))
# No errors but only returns an ActiveRecord_Relation
# on .inspect it returns `PG::SyntaxError: ERROR: syntax error at or near "["`
.order(Arel.sql("#{order}"))
# .to_sql: ORDER BY [\"\\\"positions\\\".\\\"id\\\" = 'fcdc924a-21da-440e-8d20-eec9a71321a7' DESC\"]"
order = ['fcdc924a-21da-440e-8d20-eec9a71321a7', ...]
# Won't work since its only for integer values
.order("idx(ARRAY#{order}, #{search_class.primary_key})")
# .to_sql ORDER BY idx(ARRAY[\"fcdc924a-21da-440e-8d20-eec9a71321a7\", ...], id)
# Only returns an ActiveRecord_Relation
# on .inspect it returns `PG::InFailedSqlTransaction: ERROR:`
.order("array_position(ARRAY#{order}, #{search_class.primary_key})")
# .to_sql : ORDER BY array_position(ARRAY[\"fcdc924a-21da-440e-8d20-eec9a71321a7\", ...], id)
I am sort of stuck since rails forces attribute arguments in the future and an has no option to opt out of this. Since the order is a code generated array and I have full control of the values I am curious how I can implement this. Maybe someone had this issue before an give some useful insight or idea?
You could try to apply Arel.sql to the elements of the array, that should work, ie
search_class.where(search_class.primary_key => record_ids)
.order(order.map {|i| i.is_a?(String) ? Arel.sql(i) : i})

pg gem: 'Warning: no type cast defined for type "numeric" '

I'm having trouble getting typed results out of the pg gem.
require 'pg'
require_relative 'spec/fixtures/database'
client = PG.connect( DB[:pg] )
client.type_map_for_queries = PG::BasicTypeMapForQueries.new(client)
client.type_map_for_results = PG::BasicTypeMapForResults.new(client)
client.exec( %|select * from testme;| ) do |query|
query.each {|r| puts r.inspect }
end
This program gives the output:
Warning: no type cast defined for type "money" with oid 790. Please cast this type explicitly to TEXT to be safe for future changes.
Warning: no type cast defined for type "numeric" with oid 1700. Please cast this type explicitly to TEXT to be safe for future changes.
{"string"=>"thing", "logical"=>true, "cash"=>"£1.23", "reel"=>"2.34", "day"=>#<Date: 2015-12-31 ((2457388j,0s,0n),+0s,2299161j)>, "float"=>3.45}
So: booleans and floats and dates (and integers) get converted, but not numerics or the money type.
Can anyone tell me how to "cast the type explicitly", assuming that I don't want to hard-code a solution for each table?
Hijacking this thread, as after some digging I finally found a way to add a custom decoder/encoder, so posting an example below:
require 'ipaddr'
require 'pg'
class InetDecoder < PG::SimpleDecoder
def decode(string, tuple=nil, field=nil)
IPAddr.new(string)
end
end
class InetEncoder < PG::SimpleEncoder
def encode(ip_addr)
ip_addr.to_s
end
end
# 0 if for text format, can also be 1 for binary
PG::BasicTypeRegistry.register_type(0, 'inet', InetEncoder, InetDecoder)
Here's a catch all for those seeking to cast strings by default:
client = PG.connect( DB[:pg] )
map = PG::BasicTypeMapForResults.new(conn)
map.default_type_map = PG::TypeMapAllStrings.new
client.type_map_for_results = map
Got the same problem with a text-ish field. Solved by duplicating a coder and editing its OID.
text_coder = client.type_map_for_results.coders.find { |c| c.name == 'text' }
new_coder = text_coder.dup.tap { |c| c.oid = 19 } # oid from the warning
conn.type_map_for_results.add_coder(new_coder)
How I got there: it might interest the next guy, if the problem is similar but not identical.
I read other people online talking about type_map_for_results, but how they didn't know how to define a coder. Since it was a text field in my case, I decided to try cloning an existing one. I knew I could find a textual pre-set in a Rails app, so I opened a rails console and searched:
adapter = ActiveRecord::Base.connection
connection = adapter.instance_variable_get("#connection")
mapping = connection.type_map_for_results
cd mapping # my console of choice is `pry`
ls # spotted a likely getter named `coders`
cd coders # again
ls # spotted getter `name` and setter `oid=`
So I put together the code in the solution. Gave it a try, and it worked.
It had not been straightforward to find, so I decided to exit lurker mode and share it on SO. Thereby: thanks #Andreyy for bringing me in :)
[pry cd and ls]
Google the error message: "Warning: no type cast defined for type"
You can find it's source github.
Reding the class, I would guess lines from 150 to 214 could be consiredered
examples:
register_type 0, 'text', PG::TextEncoder::String
alias_type 0, 'varchar', 'text'
Since register_type and alias_type are class methods of PG::BasicTypeRegistry::CoderMap I would play with them just and see if anything changes:
PG::BasicTypeRegistry::CoderMap.alias_type 0, 'money', 'text'
PG::BasicTypeRegistry::CoderMap.alias_type 0, 'numeric', 'text'
Reading the comments in the class it seems that the coding/decoding of those and some other fields is not implemented.
You might consider using a higher level ORM library like AvtiveRecord which implements more types (money).

Chef attributes "no implicit conversion of String into Integer"

I'm writing a chef recipe which simply creates a database config file, but I'm stumped simply access the attributes. I have a few PHP applications being deployed to each instance, and OpsWorks uses the same recipes for everyone, so I have a few different settings in the attributes file.
attributes/database-settings.rb
# API
default[:api][:path] = 'app/config/database.php';
default[:api][:host] = 'test';
default[:api][:database] = 'test';
default[:api][:username] = 'test';
default[:api][:password] = 'test';
recipes/database-settings.rb
Chef::Log.info("Database settings!");
node[:deploy].each do |application, deploy|
if node.has_key?(application)
Chef::Log.info("Application: #{application}");
path = node["api"]["path"]; # ERROR HAPPENING HERE
Chef::Log.info("Path: #{path}");
template path do
source "database.erb"
mode 0440
variables({
:host => node["api"]["host"],
:database => node["api"]["database"],
:username => node["api"]["username"],
:password => node["api"]["password"]
})
end
end
end
The error I'm getting is no implicit conversion of String into Integer. I've tried creating and accessing the settings in every way I can think of, such as...
node[:api][:path] # no implicit conversion of Symbol into Integer
node['api']['path'] # no implicit conversion of String into Integer
node[:api].path # undefined method `path' for #<Chef::Node::ImmutableArray:0x007fa4a71086e8>
node[application][:path] # no implicit conversion of Symbol into Integer
I'm sure there's something very obvious I'm doing wrong here, but I've tried everything I can think of an I just can't seem to find any way of getting this to work?! Ideally I'd like to use a variable where I can "api", but using an if/else wouldn't be too terrible for 3 apps...
That is a common error seen when you try to access an object thinking it is a hash, but is actually an array. In fact, from one of your errors, it can be read that node["api"] is a Chef::Node::ImmutableArray.
Ok so the problem wasn't really that I was accessing the config wrongly, it was that the different attribute files were all being merged into a single config and I didn't realise this.
I had these config files...
attributes/database_settings.rb
default[:api][:path] = 'app/config/database.php';
default[:api][:username] = 'example';
attributes/writable_directories.rb
default[:api] = ['public/uploads', 'storage/cache'];
When I tried to access default[:api][:path] I was actually accessing the array of directories when seemed to override the database settings attributes. Moving these into default[:directories][:api] and default[:database][:api][:path] etc fixed this.
Note that you will also get this error if you accidentally enter a space between "node" and the items indexing it:
node[:foo][:bar]
will work, while
node [:foo][:bar]
will throw this exception. It can be hard to spot.

Sinatra can't convert Symbol into Integer when making MongoDB query

This is a sort of followup to my other MongoDB question about the torrent indexer.
I'm making an open source torrent indexer (like a mini TPB, in essence), and offer both SQLite and MongoDB for backend, currently.
However, I'm having trouble with the MongoDB part of it. In Sinatra, I get when trying to upload a torrent, or search for one.
In uploading, one needs to tag the torrent — and it fails here. The code for adding tags is as follows:
def add_tag(tag)
if $sqlite
unless tag_exists? tag
$db.execute("insert into #{$tag_table} values ( ? )", tag)
end
id = $db.execute("select oid from #{$tag_table} where tag = ?", tag)
return id[0]
elsif $mongo
unless tag_exists? tag
$tag.insert({:tag => tag})
end
return $tag.find({:tag => tag})[:_id] #this is the line it presumably crashes on
end
end
It reaches line 105 (noted above), and then fails. What's going on? Also, as an FYI this might turn into a few other questions as solutions come in.
Thanks!
EDIT
So instead of returning the tag result with [:_id], I changed the block inside the elsif to:
id = $tag.find({:tag => tag})
puts id.inspect
return id
and still get an error. You can see a demo at http://torrent.hypeno.de and the source at http://github.com/tekknolagi/indexer/
Given that you are doing an insert(), the easiest way to get the id is:
id = $tag.insert({:tag => tag})
id will be a BSON::ObjectId, so you can use appropriate methods depending on the return value you want:
return id # BSON::ObjectId('5017cace1d5710170b000001')
return id.to_s # "5017cace1d5710170b000001"
In your original question you are trying to use the Collection.find() method. This returns a Mongo::Cursor, but you are trying to reference the cursor as a document. You need to iterate over the cursor using each or next, eg:
cursor = $tag.find_one({:tag => tag})
return cursor.next['_id'];
If you want a single document, you should be using Collection.find_one().
For example, you can find and return the _id using:
return $tag.find_one({:tag => tag})['_id']
I think the problem here is [:_id]. I dont know much about Mongo but `$tag.find({:tag => tag}) is probably retutning an array and passing a symbol to the [] array operator is not defined.

How do I delete an embedded doc from Mongoid?

I'm having some issues deleting my document using Mongoid...
The code actually does delete the gallery, but I get a browser error which looks like:
Mongoid::Errors::DocumentNotFound at /admin/galleries/delete/4e897ce07df6d15a5e000001
The suspect code is below:
def self.removeGalleryFor(user_session_id, gallery_id)
person = Person.any_in(session_ids: [user_session_id])
return false if person.count != 1
return false if person[0].userContent.nil?
return false if person[0].userContent.galleries.empty?
gallery = person[0].userContent.galleries.find(gallery_id) #ERROR is on this line
gallery.delete if !gallery.nil?
end
My Person class embeds one userContent which embeds many galleries.
Strangely enough I've got a couple of tests around this which work fine...
I'm really not sure what's happening - my gallery seems to be found fine, and is even deleted from Mongo.
Any ideas?
find throws an error if it can't find a document with the given id. Instead of checking presence of given gallery and returning nil if it doesn't exist, you directly ask mongodb while querying to remove any such gallery.
def self.remove_gallery_for(user_session_id, gallery_id)
user_session_id = BSON::ObjectId.from_string(user_session_id) if user_session_id.is_a?(String)
gallery_id = BSON::ObjectId.from_string(gallery_id) if gallery_id.is_a?(String)
# dropping to mongo collection object wrapped by mongoid,
# as I don't know how to do it using mongoid's convenience methods
last_error = Person.collection.update(
# only remove gallery for user matching user_session_id
{"session_ids" => user_session_id},
# remove gallery if there exists any
{"$pull" => {:userContent.galleries => {:gallery_id => gallery_id}}},
# [optional] check if successfully removed the gallery
:safe => true
)
return last_error["err"].nil?
end
This way you do not load the Person, you don't even get the data from monogdb to application server. Just get the gallery removed if it exists.
But you should prefer #fl00r's answer if you need to fire callbacks and switch to destroy instead of delete
def self.removeGalleryFor(user_session_id, gallery_id)
# person = Person.where(session_ids: user_session_id).first
person = Person.any_in(session_ids: [user_session_id])
if person && person.userContent && person.userContent.galleries.any?
gallery = person.userContent.galleries.where(id: gallery_id).first
gallery.delete if gallery
end
end
ps:
In Ruby usually under_score naming rather then CamelCase is used
Kudos to Rubish for pointing me to a solution that at least passes my tests - for some reason fl00r's code didn't work - it looks like it should, but doesn't for some reason...
Person.collection.update(
{"session_ids" => user_session_id},
{"$pull" => {'userContent.galleries' => {:_id => gallery_id}}},
:safe => true
)
=> this code will pass my tests, but then once it's running in sinatra it doesn't work.... so frustrating!
have posted this code with tests on github https://github.com/LouisSayers/bugFixes/tree/master/mongoDelete

Resources