CommonThread

Displaying articles with tag

Twitter Hacking

Posted by ben, Thu Jun 05 13:59:00 UTC 2008

I got overloaded with work the other night so I decided to take a break from it and hack out something fairly useless but fun. I landed on a twitter bot that updates a few times daily with weather updates for my area or others I might care about. Right now I only have a Birmingham Weather and a Montgomery Weather.

The code requires 2 gems twitter & yahoo-weather. you can install them both with:

sudo gem install yahoo-weather twitter --no-ri --no-rdoc

Then the code was very simple for querying and updating:

#! /usr/bin/env ruby

require 'rubygems'
require 'yahoo-weather'
require 'twitter'

client = YahooWeather::Client.new

cities = [['35212', 'wxbhm'], ['36108', 'wxmgm']]

cities.each do |city|
  response = client.lookup_location(city[0])
  forecast = "Today's weather: #{response.forecasts.first.text}, Hi: #{response.forecasts.first.high}F, Lo: #{response.forecasts.first.low}F (Currently #{response.condition.text}, #{response.condition.temp}F)" 
  Twitter::Base.new(city[1], 'SUPER_SECRET').update(forecast)
end

Then to make it happen automagically, welcome cron:

00 13 * * * /home/deploy/twitter_wx/update_wx.rb
45 21 * * * /home/deploy/twitter_wx/update_wx.rb

It updates at 8:00am & 4:45pm. I had to put the dates in UTC and take into consideration that I am Central Timezone and add 5 hours to the time for now … if someone knows a better way to handle this in cron please let me know.

0 comments | Filed Under: | Tags:

Slicehost API Saves The Day

Posted by ben, Tue May 20 22:25:00 UTC 2008

Thanks to slicehost for giving me an API for managing my DNS zones and records via ActiveResource becauase it saved me lots of time and tedious work. I needed to update my TTL settings in all my domains and rather than having to edit all 61 entries I simply made a ruby script to do it for me:

require 'rubygems'
require 'activeresource'

API_PASSWORD = "MY_SECRET_PASSWORD" 
DEFAULT_TTL = 86400

class Record < ActiveResource::Base
  self.site = "https://#{API_PASSWORD}@api.slicehost.com/" 
end

records = Record.find(:all)

for record in records
  record.ttl = DEFAULT_TTL
  record.save
end

and while we are at it lets output all of our info:

require 'rubygems'
require 'activeresource'

API_PASSWORD = "MY_SECRET_PASSWORD" 

class Zone < ActiveResource::Base
  self.site = "https://#{API_PASSWORD}@api.slicehost.com/" 
end

class Record < ActiveResource::Base
  self.site = "https://#{API_PASSWORD}@api.slicehost.com/" 
end

zones = Zone.find(:all)
records = Record.find(:all)

for zone in zones
  puts "zone: #{zone.origin}" 

  for record in records.find_all{|record| record.zone_id == zone.id}
    puts "  #{record.record_type}: #{record.name} - #{record.data}" 
  end
end

There, that was easy.

1 comment | Filed Under: | Tags:

Complex Forms ... Just Easier

Posted by ben, Thu Apr 17 00:33:00 UTC 2008

I love having the ability to easily create/update multiple objects with a single form. There are some nice railscasts by Ryan Bates that describe how to do this in Ruby on Rails. What I needed was a way to make writing the attribute accessor methods easier. I also decided that there just weren’t enough rails plugins out there … so off to kill two birds with one stone.

The Plugin

The plugin is called association-attributes and it can be found on github. The goal was to make the process more semantic and most of all DRY. I decided that since we already had to have a method on the model to handle the association we would pattern after that. So, the two methods available are has_one_attr & has_many_attr.

The Model

So for a practical example of what this looks like in your model we will use a persons contact info. We also have a plugin out there that makes contact info easier since that is such a standard need and there is no reason to rewrite that all the time.

class Person < ActiveRecord::Base
  has_one :address, :as => :addressable, :dependent => :destroy
  has_one_attr :address

  has_many :phone_numbers, :as => :phoneable, :dependent => :destroy
  has_many_attr :phone_numbers
end

What that does is creates the methods address_attributes & phone_number_attributes on Person so that we can use them in our form. These newly created methods handle creating & updating existing entries for addresses and phone numbers.

The View

We use HAML for all of our views so these examples are no exception.

- fields_for("person[phone_numbers_attributes][]", @person.phone_numbers.new) do |phf|
  %label{:for => "number"} Phone Number:
    = phf.hidden_field :id
    = phf.text_field :number
A few things to note from this example are:
  • The fields_for has an extra [] on the first parameter … this is because we have a has_many and that tells the browser to treat that as part of an array so that we can have more than one of that same name and it will get put into the array.
  • We are only doing a new item but you would use the same technique to display existing items by replacing @person.phone_numbers.new with a variable of the actual phone number.
  • We have an id hidden field … this is if we use an existing phone number object it will update rather than creating a new object. (it still works without that but deletes the old number and creates a new one)
- fields_for("person[address_attributes]", @person.build_address) do |af|
  %p
    %label{:for => "line_1"} Street Address:
    = af.text_field :line_1
  %p
    %label{:for => "city"} City:
    = af.text_field :city
  %p
    %label{:for => "state"} State:
    = af.select :state, Address::VALID_STATES.map{|s| [s,s]}.sort
  %p
    %label{:for => "zip"} Zip:
    = af.text_field :zip

In future versions I will have a helper method that writes the fields_for for you as well. Looping through current objects and having an option to put a blank one at the end.

The Result

The result of this is that now when you submit a form to create or update a person that has these subforms using fields_for then the appropriate phone number and address objects will get created or updated as well. No more having to create the person first then associate the extra objects later. Even in the case of a has_many where the object is new it will create the object for you then create all the association objects next.

0 comments | Filed Under: | Tags:

Tip: Ruby Here Document

Posted by anthony crumley, Wed Dec 19 23:12:00 UTC 2007

“Here, document. Come here boy. That’s a good document.” Often times we need to create fairly complex strings in code. The result is usually multiple concatenations that tend to get a little wild and woolly. One solution is to put the string in a separate document or template that uses ERB. This can be overkill for a one-off situation. To tame this mess Ruby provides the here document. I assume the strange name comes from it being like putting the complex string in a separate document but it is right here instead of there.

Here documents begin with << or <<- followed by a word which will be the string terminator. If the document begins with << then the terminator must be in the left most column which doesn’t look good in most situations, although it does make the end easy to find.

The most straight forward use is assignment to a variable.

  xml = <<-XML_END
    <project>
      <owner>#{project.owner}</owner>
      <manager>#{project.manager}</manager>
    </project>
  XML_END

The here document can also be used as a parameter to a method. The first time I saw this one it freaked me out a little until I understood it.

def create(project, user)
  ...
end

update(<<PROJECT_XML, current_user)
  <project>
    <owner>#{project.owner}</owner>
    <manager>#{project.manager}</manager>
  </project>
PROJECT_XML

0 comments | Filed Under: | Tags:

Tip: Ruby Break, Redo, Next and Retry

Posted by anthony crumley, Sat Dec 15 09:16:00 UTC 2007

Ruby has some interesting loop flow control statements. These work with while, until, and for loops as well as iterators. For some reason, I find it very interesting that they work with iterators.

Break simply breaks out of the most immediate loop and resumes with the next statement after the loop. It is like Bobby Petrino leaving the Atlanta Falcons to coach at Arkansas. Just stop whatever is going on and move to the next thing without looking back.

Redo repeats the current iteration of the loop without rechecking the condition. It is like a do over or mulligan.

Next skips to the end of the current iteration and begins the next one normally. It is like the Soup Nazi, “No soup for you! Come back, one year. Next!!”

Retry starts the whole loop over again from the beginning. It is like the movie Groundhog Day. You can just keep repeating it until you get it right.

0 comments | Filed Under: | Tags:

Tip: Ruby Range Operators

Posted by anthony crumley, Fri Nov 23 08:10:00 UTC 2007

There are actually two Ruby range operators. The .. operator is inclusive of the end points and the … operator is exclusive of the high value end point.

a = [0, 1, 2, 3]

a[0..-1] => [0, 1, 2, 3]
a[0...-1] => [0, 1, 2]

You would think … would be helpful for using a.length for the high end point but it doesn’t seem to matter.

a[0..a.length] => [0, 1, 2, 3]
a[0...a.length] => [0, 1, 2, 3]

0 comments | Filed Under: | Tags:

Ruby CSV

Posted by anthony crumley, Wed Nov 21 21:43:00 UTC 2007

Comma delimited files are still used for importing and exporting of data. The Ruby Standard Library includes a class for manipulating them. When a file is read each row is converted to an array with each element of the array being a string. Files are created by writing out arrays. Following is a marketing email example.

File of people to send an email to.

John,Doe,john@commonthread.com
Peter,Rabbit,peter@commonthread.com
Billy,Goat,billy@commonthread.com

Creating the file.

require 'csv'

csv = CSV.open('MarketingEmail.csv', 'w')
csv << ['John', 'Doe', 'john@commonthread.com']
csv << ['Peter', 'Rabbit', 'peter@commonthread.com']
csv << ['Billy', 'Goat', 'billy@commonthread.com']
csv.close

Sending emails to each person in the file.

require 'csv'

reader = CSV.open('MarketingEmail.csv', 'r')
reader.each do |row|
  ...
  to = row[2]
  body = "Dear #{row[0]}," ...
  ...
end

2 comments | Filed Under: | Tags:

Sexy Inject

Posted by ben, Wed Nov 21 01:00:00 UTC 2007

I saw @pragdave twitter on a sexier looking inject statement so I figured I would monkey patch the array class to get me some sexy inject action.

class Array
  alias old_inject inject
  def inject(v=nil, &block)
    if v.is_a?(Symbol)
      old_inject {|s,n| s.send(v,n)}
    else
      old_inject(v || 0, &block)
    end
  end
end

now what used to look like this …

[1,3,5,7].inject {|sum,val| sum + val}

... now looks like this …

[1,3,5,7].inject(:+)

... and both give me 16

0 comments | Filed Under: | Tags:

Ruby Array negative element references

Posted by anthony crumley, Mon Nov 19 22:31:00 UTC 2007

One aspect of Ruby that is non-intuitive at first is the use of negative array indexes. For instance the string ‘anthony’ would have the following indexes.

-7 -6 -5 -4 -3 -2 -1
a n t h o n y
0 1 2 3 4 5 6

The positive numbers go from beginning to end and the negative from end to beginning. ‘anthony’[0, 1] returns ‘a’ and ‘anthony’[-1, 1] returns ‘y’. At first the negative index is a little weird but begins to make sense as it sinks in a some.

In my opinion, it would have been better if the positive numbers started with one also but I guess that is the VB programmer in me.

The real tricky part comes when you bring ranges into the mix. ‘anthony’[1..3] returns ‘nth’ and ‘anthony’[-4..-2] returns ‘hon’ which makes sense when you refer to the table above. One rule to remember is that the first number in the range must be before the second number or an empty string is returned. ‘anthony’[3..1] and ‘anthony’[-2..-4] both return ””. Now, with this in mind, what does ‘anthony’[1..-1]return? My first thought was that it would return an empty string because the number 1 is after -1. That is not true though because the positions relative to the beginning of the array are compared rather than the actual numbers. Next I thought it would return positions 1, 0, and -1 which would be ‘nay’ but that would just be weird. It actually returns ‘nthony’ because it returns everything from position 1 to position -1 reading from beginning to end. In this case 1 is the second position and -1 is the last position so everything except the letter in the first position is returned. The mixing of positive and negative indexes with ranges takes some practice so play around in IRB and you should get the hang of it.

0 comments | Filed Under: | Tags: