Friday, July 29, 2011

Remove a specific ActiveRecord error message from a models errors collection

Example (where user is an instance variable of my User model):

Tuesday, July 12, 2011

Retrieve monthly search volume for a keyword using the Google Adwords API and Ruby

I recently needed to pull down monthly search totals for a number of keywords, after a quick search, that came up empty, I decided to whip one out and post the code (simplified of course), which uses the Google Ads API, Ruby and the Ruby Google Ads API client library (http://code.google.com/p/google-api-ads-ruby/). In order to run this example, you must first initialize your sandbox accounts, described in my previous posting, as well as have ruby and the Ads API libraries installed.

Monday, July 11, 2011

Adwords API Sandbox Initialization Using Adwords4r GEM

The following snippet initializes new accounts for the specified google account, within the Adwords SANDBOX , using the Adwords4r gem. Replace "your-user-name" with your google accounts user name. Happy Adwords'n.

Wednesday, June 15, 2011

Simple PHP Bing SERP Ranking Checker

I needed a quick test script that I could fire up easily on almost any machine to validate the differences I was seeing in Bing results across their cluster as well as investigate their geo-targeted and facebook integrated results, so I whipped up this handy PHP script for scraping SERP rankings. Instead of filing it away in my massive archive of utility scripts, I thought I'd release it.

Basic PHP script for scraping SERP rankings from Bing. For ease of packaging, I've included the connection class in the main script. If you're planning on building this out, I recommend putting it in a separate file and including it for easier maintenance.

Features:

* Free!
* Returns top 50 results
* Returns # of results for search term/query
* Encapsulated HTTP connection class using libCurl PHP wrappers
* Gzip compression (Can be enabled or disabled)
* Accessors for customizing User-Agent and Referer HTTP headers
* Accessors for setting cookies and additional HTTP headers

NOTE: If you copy/paste this into a file, be sure to enclose it ALL within tags.

Wednesday, June 01, 2011

ActiveModel Object With Multi-Part Attribute/Date Support

Simple Ruby ActiveModel class that works with View and Form helpers in Rails 3. Appears to handle dates properly. Let me know if any problems arise:


class CreditCard
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming

attr_accessor :name
attr_accessor :number
attr_accessor :expiration_date
attr_accessor :code

validates_presence_of [ :name, :number, :expiration_date, :validation_code ]
validates_format_of :number, :with => /^\d+$/, :if => Proc.new {|o| !o.card_number.blank? && !o.errors[:card_number] }
validates_format_of :code, :with => /^\d+$/, :if => Proc.new {|o| !o.code.blank? && !o.errors[:code] }

def initialize( attrs = {} )
if !attrs.nil? then
dattrs = {}
attrs.each do |n, v|
if n.match( /^(.+)\(.+\)$/ ) then
an = Regexp.last_match[1]
dattrs[an] = [] if dattrs[an].nil?
dattrs[an] << { :n => n, :v => v }
else
send( "#{n}=", v)
end
end
dattrs.each do |k, v|
vs = v.sort_by{|hv| hv[:n] }.collect{|hv| hv[:v] }
p1 = vs[0]
p2 = ( vs[1].size() > 0 ? ( vs[1].size() == 1 ? "0#{vs[1]}" : vs[1] ) : "01" )
p3 = ( vs[2].size() > 0 ? ( vs[2].size() == 1 ? "0#{vs[2]}" : vs[2] ) : "01" )
dv = [ p1, p2, p3 ].join( "-" )
begin send( "#{k}=", Date.parse( dv ) ); rescue; end
end
end
end

def persisted?
false
end

end

Monday, May 23, 2011

Handling gzip responses in Ruby Net::HTTP library


require 'net/http'

debug = Proc.new{|msg| STDERR.puts "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] #{msg}" }

page = nil
http = Net::HTTP.new( "www.google.com", 80 )
req = Net::HTTP::Get.new( "/search?num=20&hl=en&noj=1&q=test&btnG=Search", { "Accept-Encoding" => "gzip", "User-Agent" => "gzip" } )
debug.call( "Performing HTTP GET request for (#{req.path})." )
res = http.request( req )
debug.call( "Received HTTP Response Code (#{res.code})" )
case res
when Net::HTTPSuccess then
begin
if res.header[ 'Content-Encoding' ].eql?( 'gzip' ) then
debug.call( "Performing gzip decompression for response body." )
sio = StringIO.new( res.body )
gz = Zlib::GzipReader.new( sio )
page = gz.read()
debug.call( "Finished decompressing gzipped response body." )
else
debug.call( "Page is not compressed. Using text response body. " )
page = res.body
end
rescue Exception
debug.call( "Error occurred (#{$!.message})" )
# handle errors
raise $!.message
end
end

puts page

Saturday, March 19, 2011

Directly access/validate underlying ActiveRecord model association record values

I recently ran into a situation where proper table normalization in my design left me with a clunky UI implementation, and no change logging. I also needed a way to directly access underlying (has-one) records for an object, through it's own accessor (think batch import). In the past, for chang logging, I've used ActiveRecord observers, or used an existing library such as Paper Trail, but I wanted this to fit into my scoped non-library dependent design and allow easy access to the underlying has-one records.

I've altered the exact implementation, the below is just an example:

Tables (users, phones) - sqlite3 syntax:

  • create table users (id integer primary key, first_name text, last_name text, created_at text, updated_at text, deleted_at text);

  • create table phones (id integer primary key, user_id integer, phone_type text, number text, created_at text, updated_at text, deleted_at text);



Models (User, Phone):


class User < ActiveRecord::Base
default_scope :conditions => { :deleted_at => nil }
has_many :phones

validates_presence_of [ :first_name, :last_name, :cell_phone ]
validates_format_of :cell_phone, :with => /\d{3}-\d{3}-\d{4}/, :if => Proc.new {|o| !o.errors.on( :cell_phone ) }

accepts_nested_attributes_for :phones

def cell_phone
@cell_phone || ( !self.phones.empty? ? self.phones.cell.first.number : nil )
end

def cell_phone=( value )
@cell_phone = value
if self.phones.cell.empty? then
self.phones.build( :phone_type => 'cell', :number => value )
else
phone = self.phones.cell.first
self.phones_attributes = [ { :id => phone.id, :number => value } ]
end
end

def destroy
self.update_attribute( :deleted_at, Time.now )
end
end


class Phone < ActiveRecord::Base
default_scope :conditions => { :deleted_at => nil }
named_scope :cell, :conditions => { :phone_type => 'cell' }

belongs_to :user

def destroy
self.update_attribute( :deleted_at, Time.now )
end
end


OK ... but what's it do?

  • Validation that plays nice with form view helpers:


    >> u = User.new( :first_name => 'Noel', :last_name => 'Geren' )
    => #
    >> u.valid?
    => false
    >> u.errors.full_messages.join(", ")
    => "Cell phone can't be blank"

  • Direct access to underlying has-one (cell phone record):


    u = User.new( :first_name => 'Noel', :last_name => 'Geren' )
    => #
    >> u.cell_phone = '123-123-1234'
    => "123-123-1234"
    >> u.cell_phone
    => "123-123-1234"

Sunday, February 13, 2011

MySQL Gem and “uninitialized constant MysqlCompat::MysqlRes” on Linux

Stumbled across the same MySQL Gem error (uninitialized constant MysqlCompat::MysqlRes) that I ran into recently on OSX/Snow Leopard when upgrading/building the MySQL 2.8.11 Gem against MySQL 5.5 client libraries on Linux.

Steps to fix this are below:

... assuming you've already unpackaged the mysql source into a directory (my steps assume it's in /usr/local/mysql) ...


  1. Install the MySQL Ruby gem w/ the correct architecture (-arch i386 for 32bit or -arch x86_64 for 64bit): env ARCHFLAGS="-arch x86_64" gem install --no-rdoc --no-ri mysql -- --with-mysql-dir=/usr/local/mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config

  2. Check if an existing mysql library configuration file exists for the linux shared library (ld) in /etc/ld.so.conf.d directory.

  3. If the library exists, edit it and update the path to your source (/usr/local/mysql/lib)

  4. If the library DOES NOT exist, create a new file named "mysql.conf" containing "/usr/local/mysql/lib" (echo "/usr/local/mysql/lib" > /etc/ld.so.conf.d/mysql.conf).

  5. run "ldconfig"



You can verify your MySQL gem's library references by using "ldd" on the "mysql_api.so" file within the gem's installation directory:

BEFORE (w/out update):

ngeren@....:/usr/local/lib/ruby/gems/1.8/gems/mysql-2.8.1/lib# ldd mysql_api.so
linux-gate.so.1 => (0xb7f50000)
libmysqlclient.so.16 => not found
libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb7f15000)
libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7eee000)
librt.so.1 => /lib/tls/i686/cmov/librt.so.1 (0xb7ee5000)
libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb7ee1000)
libcrypt.so.1 => /lib/tls/i686/cmov/libcrypt.so.1 (0xb7eaf000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7d51000)
/lib/ld-linux.so.2 (0xb7f51000)

AFTER:

ngeren@.....:/usr/local/lib/ruby/gems/1.8/gems/mysql-2.8.1/lib# ldd mysql_api.so
linux-gate.so.1 => (0xb809a000)
libmysqlclient.so.16 => /usr/local/mysql/lib/libmysqlclient.so.16 (0xb7d47000)
libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb7d2e000)
libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7d07000)
librt.so.1 => /lib/tls/i686/cmov/librt.so.1 (0xb7cfe000)
libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb7cfa000)
libcrypt.so.1 => /lib/tls/i686/cmov/libcrypt.so.1 (0xb7cc8000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7b6a000)
/lib/ld-linux.so.2 (0xb809b000)

Friday, February 11, 2011

AuthorizeNetCimGateway create_customer_profile and update_customer_payment_profile not supporting validation mode

ActiveMerchant::Billing::AuthorizeNetCimGateway "create_customer_profile" and "update_customer_payment_profile" method does not support the "validation_mode" option, which tells Authorize.net how to perform validations against the optionally provided payment profile(s) (See bottom of page 14 of CIM XML implementation guide - "validationMode"). The patch below implements the support for the missing option in both methods.


module ActiveMerchant
module Billing
class AuthorizeNetCimGateway < Gateway

alias :original_build_create_customer_profile_request :build_create_customer_profile_request
def build_create_customer_profile_request(xml, options)
add_profile( xml, options[:profile] )
xml.tag!( 'validationMode', CIM_VALIDATION_MODES[ options[:validation_mode] ] ) if options[:validation_mode]
xml.target!
end

alias :original_build_update_customer_payment_profile_request :build_update_customer_payment_profile_request
def build_update_customer_payment_profile_request(xml, options)
xml.tag!('customerProfileId', options[:customer_profile_id])
xml.tag!('paymentProfile') do
add_payment_profile(xml, options[:payment_profile])
end
xml.tag!( 'validationMode', CIM_VALIDATION_MODES[ options[:validation_mode] ] ) if options[:validation_mode]
xml.target!
end
end
end
end

Wednesday, February 09, 2011

Ruby, Rails and MySQL 5.5 Oh my!

I've seen many postings regarding issues with running 64bit Ruby, Rails and MySQL 5.5 in Leopard/Snow Leopard, especially regarding the missing const error "MysqlCompat::MysqlRes" and the mysql.bundle library issues. After investing about an hour, I had an upgraded MySQL 5.5 and MySQL gem 2.8.1 installation, and I did it like this ...


  1. Download and install MySQL 5.5 (I untar it in /usr/local and create a "msyql" symlink to ease upgrades) - configure your /etc/my.cnf as necessary.

  2. Fire up your new MySQL installation to make sure it works (/usr/local/mysql/bin/mysqld_safe ...) will work for now, or you can go ahead and enable startup via launchctl.

  3. Uninstall all mysql gems (root and individual user level gems).

  4. Install the new gem for root:

    1. sudo su - (if you're not already root)

    2. env ARCHFLAGS="-arch x86_64" gem install --no-rdoc --no-ri mysql -- --with-mysql-dir=/usr/local/mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config

    3. install_name_tool -change libmysqlclient.16.dylib /usr/local/mysql/lib/libmysqlclient.16.dylib /Library/Ruby/Gems/1.8/gems/mysql-2.8.1/lib/mysql_api.bundle

    4. vi (or mate) /Library/Ruby/Gems/1.8/gems/mysql-2.8.1/test/test_mysql.rb - add a require "rubygems"

    5. ruby test_mysql.rb - see if everything passes:
      (115 tests, 391 assertions, 0 failures, 0 errors)



  5. For user installed gem, do the same as you did for the root user, except update your paths accordingly.



--Noel (ngeren)