Chef attributes "no implicit conversion of String into Integer" - ruby

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.

Related

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 value not getting parsed in another attribute

I am setting attributes in default.rb as
default[:my_app] = {
:vol => "data02",
:commitlog => "/foo/bar/node[:vol]/commitlog",
}
But :vol value is not getting parsed in commitlog attribute and I am getting following error.
mError executing action `create` on resource 'directory[/foo/bar/node[:vol]/comitlog]'[0m
You're missing the String interpolation syntax, e.g. y = "The value of X is #{X}." You probably want:
default[:my_app] = {
:vol => "data02",
:commitlog => "/foo/bar/#{node[:vol]}/commitlog",
}
Also, keep in mind that if you make one attribute depend on the value of another, you might override node[:my_app][:vol] later and expect the value of node[:my_app][:commitlog] to change with it, and it may not. The attributes will be parsed together, potentially before your override affects the first one.
Even after I am using the interpolation syntax, and when I am using node[:my_app][:commitlog] in recipe it shows /foo/bar//commitlog

Close.io API connection with Ruby syntax error

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'

DataMapper first_or_create doesn't work... any ideas why?

Well, I've finally decided that I'm not crazy. So, that leaves DataMapper.
Here's what I'm doing. I have a model Msrun which has 1 Metric.
tmp = Msrun.first_or_create # I'll skip the boring details
tmp.metric = Metric.first_or_create( {msrun_id: tmp.id}, {metric_input_file: #metricsfile} )
p tmp.metric # => #<Metric #metric_input_file=nil #msrun_id=1>
tmp.metric.metric_input_file = #metricsfile
p tmp.metric # => #<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>
So, why doesn't this work? I'm reading http://datamapper.org/docs/create_and_destroy and doing what it shows working. This has been terribly arduous. Thanks for any help.
Update:
I still can't figure out what is going on, but to prove I'm not insane...
puts Metric.all # => []
tmp.metric = Metric.first_or_create( {msrun_id: tmp.id}, {metric_input_file: #metricsfile} )
puts Metric.all # => [] #??????????????
tmp.metric.metric_input_file = #metricsfile
p tmp.metric # => #<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>
tmp.metric.save
puts Metric.all # => [#<Metric #metric_input_file=#<Pathname:/home/ryanmt/Dropbox/coding/rails/metrics_site/spec/tfiles/single_metric.txt> #msrun_id=1>]
So, not only is first_or_create not delivering on the behavior I expect by reading the source
def first_or_create(conditions = {}, attributes = {})
first(conditions) || create(conditions.merge(attributes))
end
but it is also not even creating.
I'm probably missing something here (more of those boring details might help) but if the metric exists, it's metric_input_file shouldn't be updated, i.e., it's only set when new. If you're after updating then you can do
.first_or_create(msrun_id: tmp.id).update(metric_input_file: #metricsfile)
Or if not hitting the database twice is relevant, then
m = Metric.first_or_new(msrun_id: tmp.id)
[set..save..assign]
But if it's not being set on new models, I don't see what would cause that from the code posted so far, more..?
[UPDATED]
Based on your new code, I'd say this is "a classic case" of a false DM save. I usually add the following line to an initialization section, e.g., application.rb in Rails.
DataMapper::Model.raise_on_save_failure = true
Unfortunately, the exception raised never tells you why (there's a special place in hell for that choice, right next to people who talk in theaters.) But it's typically one of:
a slightly incorrect association definition
a has/belongs_to that isn't "required: false" and isn't set
putting the wrong datatype into a field, e.g., a string into a decimal
a validation failing
If you want to post your model definitions, the problem may be spottable there.
In addition to the answer above, I've seen this call die (like, literally halt all execution) with no error when I was doing a find_or_create that would have created an object that violated the primary key constraint. This is because the datamapper model was not in sync with the actual database schema.

How to create XML object from string using xml-mapping in Ruby

I'm using xml-mapping in Ruby (on Sinatra) for some XML stuff. Generally I follow this tutorial: http://xml-mapping.rubyforge.org/. I can create objects and write them to XML strings using
login.save_to_xml.to_s
But when I try
login = Login.load_from_xml(xml_string)
I get the following error:
XML::MappingError - no value, and no default value: Attribute username not set (XXPathError: path not found: username):
Here is the XML string I receive:
<login><username>ali</username><password>baba</password></login>
This is what the class looks like:
class Login
include XML::Mapping
text_node :username, "username"
text_node :password, "password"
end
So the class name is the same, the nodes are named the same. I actually get the exact same string when I create an instance of my object and fill it with ali/baba:
test = Login.new
test.username = "ali"
test.password = "baba"
p test.save_to_xml.to_s
<login><username>ali</username><password>baba</password></login>
What am I missing?
Thanks,
MrB
EDIT:
When I do
test = login.save_to_xml
And then
login = Login.load_from_xml(test)
it works. So the problem seems to be that I'm passing a string, while the method is expecting.. well, something else. There is definitely a load_from_xml(string) method in the rubydocs, so not sure what to pass here. I guess I need some kind of reverse to_s?
It looks like you save_to_xml creates a REXML::Element. Since that works, you may want to try:
Login.load_from_xml(REXML::Document.new(xml_string).root)
See the section on "choice_node" for a more detailed example http://xml-mapping.rubyforge.org/

Resources