Posts Tagged ‘ruby’

Ruby Dynamic DNS Script for Slicehost

Monday, July 6th, 2009

I know everyone and there mother has written one of these, so I guess I’m now my mother.  Run this as a cron to enable a pretty cheapo dynamic DNS via Slicehost.  You’ll need the ActiveResource gem to use it.


#! /usr/bin/env ruby

# */5 * * * * /usr/bin/dynamicdns.rb

require 'rubygems'

require 'activeresource'

require 'open-uri'

 

# Configure your stuff

@api_key      = 'YOUR_API_KEY'

@record_id    = 1337 #the record ID you are updating

@ttl          = 10 * 60 #ten minutes, yeah sure.

@record_type  = 'A'

@record_name  = 'vpn'

@ip_site      = 'http://checkip.dyndns.org/'

@ip_path      = '/tmp/lastip'

@log          = '/tmp/lastip.log'                                                               

API_SITE      = "https://#{@api_key}@api.slicehost.com/"

 

class Record < ActiveResource::Base;self.site = API_SITE;end;

 

# Check the last ip address

@last_ip = File.exist?(@ip_path) ? File.read(@ip_path).strip : ''

 

begin

  @ip = open(@ip_site){|f| f.read}.match(/[0-9.]+/)[0]

rescue Exception

  File.open(@log,'a+'){|f| f.puts "#{Time.now} [ERROR] - Failed to open #{@ip_site}"}

  exit(1)

end

 

# Update the email address

if @ip != @last_ip

  File.open(@ip_path,'w+'){|f| f.puts @ip} 

 

  record = Record.find(@record_id)

  record.name = @record_name

  record.record_type = @record_type

  record.data = @ip

  record.ttl  = @ttl

  record.save                                                                   

 

  File.open(@log,'a+'){|f| f.puts "#{Time.now} [INFO] - IP updated: #{@ip}"}

else

  File.open(@log,'a+'){|f| f.puts "#{Time.now} [INFO] - IP did not change"}

end

DataMapper is Viewable

Friday, December 5th, 2008

Even when I’m panicking I’m a fan of datamapper.  I like how clean it generally makes my code, but I do notice that I tend to have the same queries cropping up from time to time either in the same application or in other apps using my model library.  This just wasn’t dry enough for me, so I made dm-is-viewable.  It gives sql-like view functionality to DataMapper Resources.  It’s a pretty simple plugin, but lets take a look, um kay?

Let’s start with a really simple User class…


class User
  include DataMapper::Resource
  belongs_to :location

  property :id, Serial
  property :name, String
  property :username, String
  property :password, String

  property :gender, String
  property :age, Integer

  property :favorite_color, String
  property :favorite_number, Integer
end

class Location
  include DataMapper::Resource

  has n, :users

  property :id, Serial
  property :name, String
  property :desc, String
end

Making your resource viewable is pretty easy, first grab the dm-is-viewable gem, then say that your resource is viewable. Views take the exact same parameters that you could pass to Resource.all. The only difference is that dm-is-viewable stores them until they are called.


class User
  include DataMapper::Resource
  is :viewable  

  #lets create some views
  create_view :legal_women, :gender => 'female', :age.gt => 18
  create_view :serial_killers, :favorite_number => 666, :favorite_color => 'black'

  #... Resource code *SNIP SNIP*
end

See the befores and afters below:


# Legal Women Before
User.all(:gender => 'female', :age.gt => 18)

# Legal Women After
User.view :legal_women

# Serial killers before
User.all(:favorite_number => 666, :favorite_color => 'black')

# Serial killers after
User.view :serial_killers

# You can also pass further limiting query parameters to #view.
# Legal Local Women Before
User.all(:gender => 'female', :age.gt => 18, User.location.name => 'Los Angeles')

# Legel Local Women After
User.view :legal_women, User.location.name => 'Los Angeles'

# Passing additional parameters FURTHERS the limitation of records, so..
User.view :legal_women, :gender => 'male'
# => Would return nil, the query would essentially be generated as:
#  SELECT * from users where gender = 'male' and gender = 'female'

Simply, clean, useful. Woot.

Examining WarningShot::FileResolver (As an example resolver)

Tuesday, November 11th, 2008

Read about it on GitHub, (who’s formatting didn’t bastardize it, as much).

Massive Flaw in DataMapper 0.9.6 - Write Once Read Many Many Many Many Many, Im outta space here…

Thursday, October 30th, 2008

So I found a pretty massive flaw in DataMapper (ticket @ lighthouse).  Whenever using the :fields attribute when doing Resource#first or Resource#all a crap load (techincally speaking) of queries are fired at MySQL.  I put a ticket in and a lot of information and links to some pasties with examples (see lighthouse).  I don’t have the time to look into this now, being that I have a deadline Saturday, but it’s a pretty serious problem and I’m sure a lot of people are experiencing it and don’t realize it.

To try it out for yourself lets use Merb (and Ive tried it without merb, and without any dm plugins and the problem still persists).

Steps to reproduce:


merb-gen app my_test_app

cd ./my_test_app

merb -i

DataMapper.auto_migrate!

#turn on dm loggering (thats what I call it)

DataObjects::Mysql.logger = DataObjects::Logger.new('log/dm.log', 0)

# create a few users, then, get their IDs, I dare you...

User.all(:fields=>[:id])

So, the problem is if you look at your mysql log or DataMapper Query Log you’ll see a crapload of selects trying to get what the first select in the list retrieved.  The funnier part is, the subsequent selects are selecting the fields that aren’t included in the :fields list.

Why is this a huge problem?  That query above for my 3 user test table generated about 90 trips to MySQL.  The ‘friend button’ on Vokle.com just generated about 240 trips to MySQL in one click, that’s how I found the issue in the first place.

Someone fix this please!?  If its not fixed by Saturday (my day ‘off’), I’m going to look into it.

Happy (bug) hunting.

WarningShot - New advanced ‘no more tears’ formula

Saturday, October 18th, 2008

WarningShot is a project I have been working on for quiet a while and I now feel that it is stable enough to be available to the masses.

WarningShot is a dependency resolution framework with the goal of making staging machines for software as simple as possible, so developers can focus on developing. Another goal of the project is to get rid the process of keeping Wikis on how to set up servers to meet the requirements for an application.

Like a lot of developers I have rake and cap scripts that do all sorts of things for me when I deploy software, like create symlinks for database.yml files, install gems, you name it.  I also have a different wiki page for every application and every type of server role.  Sometimes I forget to cross update stuff and next thing you know the next deployment sucks.

How does WarningShot solve this problem?  Well WarningShot is composed of one or many ‘machine recipes’ or config files (YAML).  Machine recipes are essentially list of requirements and dependencies to make a machine environment be ideal for your software (or just to your liking, I use warningshot to set up computers to my liking, keeping all my computers uniform).  Machine recipes are kept in your source repository and should be updated whenever features are added or removed.  So instead of updating a wiki, you update the config files, this way any version of your application that is release also has a set of specs with it that determine what it would take to properly stage a computer to run it.

You might be saying, ‘well that sounds neat, but what does it look like?’

WarningShot Machine Recipes can be as complicated or as simple as you want.  Each recipe can contain one or more ‘branch.’  A branch is a part of the Dependency Tree that is created when all of the related Machine Recipes are loaded for an application.  Here is a simple recipe to spec out gems that an example application might require.

---
:branch: gem
:environments:
  :production:
    - {name: 'merb-core', version: '>0.9.5'}
    - {name: 'dm-core', version: '>0.9.5'}
    - rack

In the above example I essentially stated that I am dependent on merb v 0.9.5 or greater, datamapper v 0.9.5 or greater and any version of rack when in production mode (there is also a ‘global’ environment that you can set requirements in as well to make them apply to all environments: test, qa, development, production, etc).

Here is a more complex Machine Recipe:

---
- :branch: gem
  :environments:
    :global:
      - {name: 'merb-core', version: '>0.9.5'}
      - {name: 'dm-core', version: '>0.9.5'}
      - rack
- :branch: file
  :environments:
    :global:
      - {source: 'http://example.com/nginx/nginx.conf', target: '/etc/nginx/nginx.conf', md5: '16ec37c499f64fc60e95650b500e30a4'}
- :branch: directory
  :environments:
    :global:
      - /tmp/uploads
      - /mnt/media/images
- :branch: symlink
  :environments:
    :global:
      - {source: '/tmp/uploads', target: './public/uploads', force: true}

The second machine recipe states my app’s gem, file, directory, and symlink dependency (note, when multiple branches are listed they should be prefixed with a hyphen).

So what makes this better than a wiki?  This:

$: sudo warningshot –resolve

A simple one line command and having to remember to set up dependencies becomes a thing of the past.

You can interface with WarningShot from your Cap or Vlad scripts and deploy to a machine that has never been set up and have nothing to worry about. WarningShot will give you statistics and let you know if there was anything it wasn’t able to fix.  Another great place to use WarningShot is to get up and running on new projects.  Instead of a developer spending a few hours reading Wikis on how to set up an application and configuring all the dependencies if WarningShot is installed and the configs are up to date a simple ‘warningshot –resolve’ will set the environment all up.

WarningShot can do a lot of things out of the box, er out of the github, whatever. Currently it supports the following dependency resolvers:

  • File
  • Directory
  • Symlink
  • Gem
  • URL
  • CoreLib
  • Manual
WarningShot can’t fix everything (yet) and that is why there is a ‘ManualResolver’ which is a glorified TODO list. If there is anything that you can’t resolve with WarningShot you can put it in the ManualResolver and after WarningShot fixes what it can it’ll output the TODOS so the developer knows exactly what else needs to be done.  No web page or wiki to hunt down, its all together.
Beside a TODO list the ManualResolver serves as a ‘feature request.’ Anything that can’t be resolved by WarningShot, in my opinion, should be able to be resolved by WarningShot. So if you use the ManualResolver, send me a feature request and tell me what I need to implement or even better join in on the project :).
This post is really just meant as an introduction, stay tuned for more posts about WarningShot in the near future.  If you are interested in using WarningShot and want some more reading check out these wikis.
If you are interested in hacking on it, check out http://github.com/coryodaniel/warningshot or http://warningshot.lighthouseapp.com.

LogGobbler; Getting your logs in one place.

Friday, August 22nd, 2008

LogGobbler is pretty simple pseudo REST logging service. It’s built on top of merb/datamapper and includes a client for logging activity. LogGobbler allows you to avoid disk I/O and store all of your logs in groups (or application groups) on a remote server.

LogGobbler is pretty quick, it uses merbs ‘run_later’ to just acknowledge the message and put it in a queue to be logged. All the URIs and request objects are cached on the client side so they don’t have to be recreated for each use. In the next release message queuing and a bulk log interface will exist so HTTP sessions don’t have to be created for every message.

All logs are stored via DataMapper (so choose your own storage engine) and are directly related to a ‘Gobbler’ or application for later retrieval and processing.

LogGobbler mimics the standard ruby logger interface, so you can just drop it in, instantiate the gobbler clients and you’re using it! (Oh, and of course start the server somewhere ;))

Here are some examples:

Configure a gobbler client


client_config = {
  :gobbler_host       => 'localhost',
  :gobbler_port       => 9090,
  :gobbler_use_ssl    => false,
  :authentication     => 'gobbler:p@ssw0rd', #false
  :log_level          => INFO
}

# Gobblers are grouped by their application names, a description is just needed for humans.
exception_config = client_config.merge({
  :description => "Logs all exceptions",
  :application => "My2.0App"
})

exception_logger  = LogGobbler::Client.new(exception_config)
exception_logger.info "My logger is online!"
exception_logger.info {"It really is, SRSLY"}

#An addition to the ruby standard logger interface is the ability to log exceptions themselves.
exception_logger.exception(Errno::ENOENT, "Oh noez!? an exception")
# or more professionally...
exception_logger.exception(Errno::ENOENT,"#{__FILE__} @ line: #{__LINE__}")

This logger keeps track of user logins (given a User class)


login_logger = LogGobbler::Client.new(client_config.merge({
  :description => "Track all logins",
  :application => "My2.0App"
}))

user = User.authentication("Somebody","secr3t_passw0rd")
if user.authenticated?
  login_logger.info("#{user.name} logged in from #{request.remote_ip}")
  # .... your cool app stuff .... #
else
  # .... your cool, 'you messed up logging in' stuff .... #
end

Of course, you can just use it for all of your general logging.


Merb.logger = LogGobbler::Client.new(client_config.merge({
  :description => "General logging activity",
  :application => "My Awesome App!"
}))

Merb.logger.info "My message was stored across the internetz?!"

If you’d like to play with or contribute to the gobbler, join in!

Modular Tables? DataMapper is Remixable.

Friday, August 22nd, 2008

One of my favorite things about Ruby is mixins.  I love the pseudo multiple inheritance it provides.  One thing that is frustrating to me though when working on developing back-ends is the amount of un-DRY code I am forced to create when working with the database.

For example, comments.  Look at sites like MySpace, Facebook, and SocialGiant3.0 (sarcasm), everything is commentable.  A user can comment on pictures, profiles, videos, and just about any other resource you can think of.

In this scenario you generally have one of two choices:

A.) You create a comments table for each of the resources: user_comments, profile_comments, etc.

B.) You create a giant comments table and horizontally partition it based on the comment_type.

I prefer the choice ‘A’, but the downfall, a very wet, anti-DRY set of models.  This is were dm-is-remixable comes in.  It gives you the ability to remix datamapper models into other models.

Whats that mean?

You write one model describing what a comment looks like and you mix it into User, Profile, etc; like magic, you’ve got a dry ‘comment’ model and a nice normalized table structure to store it in.

Here are some examples:

Create a remixable database model named Commentable


module Commentable
  include DataMapper::Resource

  is :remixable,
    :suffix => "comment"

  property :id,         Integer, :key => true, :serial => true
  property :comment,    String
  property :created_at, DateTime
end

Create a remixable image model and mix it into User


module Image
  include DataMapper::Resource

  is :remixable

  property :id,           Integer, :key => true, :serial => true
  property :description,  String
  property :path,         String
end

class User
  include DataMapper::Resource

  property :id,             Integer,
    :key          => true,
    :serial       => true

  property :first_name,     String,
    :nullable     => false,
    :length       => 2..50

  property :last_name,      String,
    :nullable     => false,
    :length       => 2..50

  remix n, :images, :as => "pics" #:as is optional and gives you an additional accessor
end

user = User.new
user.user_images
=>[] #Array of images
user.pics
=>[] #Array of images

Enhance the functionality of UserImages


class User
  enhance :images do
    def dimensions
      # ... get the dimensions of the image
    end

    def resize(x,y)
      # ... resize the image
    end
  end
end

user = User.first
user.pics.first.dimensions
=> "300x500"
user.pics.first.resize(100,300)
=> "100x300"

Remix commentables into Videos


class Video
  include DataMapper::Resource
  is :remixable

  property :id,           Integer, :key => true, :serial => true
  property :description,  String
  property :path,         String

  remix n, :commentables, :as => "comments" # as is optional, provides add'l accessor name
end

video = Video.new
video.comments
=>[] # Array of comments
video.video_comments
=>[] # same array of comments

Remixing commentables into User;


class User
  # ... from declaration above ...

  # This is some of the more complex syntax
  # here we are remixing a module between two of the same class, so we need some sort of alias
  # :as => is the accessor method
  # :for => is the class that this class remixes the module with
  # :via => what we want to call the other user object

  remix n, :commentables, :as => "comments", :for => "User", :via => "commentor"

end

commentee = User.first
commentor = User.get(2)
comment = UserComment.new
comment.comment = "Zomg!  I talk non-stop, you are awesome!"
comment.commentor = commentor
commentee.comments << comment
commentee.save

How do you get it?  Well its in dm-more.  If you dont know what DataMapper is, then git with the program.

From your favorite command line:

git clone git://github.com/sam/dm-more.git

Then from your source:

require ‘dm-is-remixable’