saush

Clone TinyURL with 40 lines of Ruby code on Google AppEngine for Java

Posted in JRuby, Ruby, Sinatra by sausheong on May 15, 2009

Amidst the wretched events that happened at work recently, I forgot about an interesting development in running apps on a cloud. Google AppEngine finally released Java support on the AppEngine platform. For those uninitiated, AppEngine is Google’s cloud computing platform that allows developers to serve up applications on Google’s infrastructure. When it was first released in April 2008, the only language supported was Python. Python is a great language but doesn’t appeal to my inner Rubyist so it didn’t catch my attention. Until now that is.

While Java is no longer my language of choice nowadays, Ruby actually runs pretty well under JRuby with Java. And with the addition of the Java support for AppEngine, it became a lot more interesting. A few weeks back I wrote Snip, a TinyURL clone, in about 40 lines of Ruby code, and deployed it on Heroku. It seems like a good idea to take Snip out for a spin on the Google AppEngine for Java (GAE/J).

The first thing you need to do is to create an application on the GAE/J. Start by going to this URL – http://appengine.google.com/start and log in using a Google account. After logging in, create a new application following the instructions given on the screen. When you’re done you should have an application id. In this case, it is ‘saush-snip’. We will be needing this application id in our configuration later. You will also need to enable Java for your GAE/J. At this point in time, GAE/J is still in beta and Google is only limiting the first 10,000 developers from enabling Java for GAE/J. Unfortunately if you don’t get Java enabled for your account, you won’t be able to try this out until it is fully released and available to all.

Once you have signed up and gotten an email approval for your GAE/J account, the first thing to do is to install JRuby, if you haven’t done so yet. Even if you have installed it previously you might want to install at least 1.3.0RC1 since some fixes were added into this release to make it run better under GAE/J. Run this:

$ git clone git://github.com/jruby/jruby.git

This will clone a copy of JRuby into your computer. Then go into the jruby folder that was just created and run this:

$ sudo ant && sudo ant jar-complete

This will install JRuby and create the jruby-complete.jar library that you will need in a while. Take note of this path to JRuby, you’ll need it in the subsequent commands. Assume that you just installed JRuby in ~/jruby, do a quick check to see if the install is ok:

$ ~/jruby/bin/jruby -v

If you installed version 1.3.0RC1 you should see something like this:

jruby 1.3.0RC1 (ruby 1.8.6p287) (2009-05-11 6586) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_07) [x86_64-java]

After installing JRuby, you’ll need to install all gems needed for this application. Remember that even if you have installed gems for your normal Ruby installation you’ll need to install it all over again for JRuby. For Snip, you need Sinatra and HAML, but you’ll also need Rake and Warbler, the JRuby war file packager.

$ ~/jruby/bin/jruby -S gem install rake sinatra haml warbler

Now that the basic JRuby and related gems are done, let’s look at the Snip code itself. One thing that is pretty obvious upfront when dealing with AppEngine is that it doesn’t have a relational database for persistence. Instead of a familiar RDBMS, we get a JDO interface or a DataStore API. How do we use it? As it turns out, we don’t need to do anything major. Ola Bini wrote a small wrapper around DataStore, called Bumble, to allow us to write data models just like we did with DataMapper. Well, almost.

Using Bumble is very much similar to DataMapper and ActiveRecord, so I didn’t have to change my code much. This is the DataMapper version of the Url model:

DataMapper.setup(:default, ENV['DATABASE_URL'] || 'mysql://root:root@localhost/snip')
class Url
  include DataMapper::Resource
  property  :id,          Serial
  property  :original,    String, :length => 255
  property  :created_at,  DateTime
  def snipped() self.id.to_s(36) end
end

And this is the Bumble version of the Url model:

class Url
  include Bumble
  ds :original
  def snipped() self.key.to_s end
end

I didn’t add in the time stamp for the Bumble version because I don’t really use it but as you can see there are quite a bit of similarities. I didn’t need to put in my own running serial id because it’s managed by the AppEngine. Also, instead of using the object id, I used the object’s key, which again is managed by the AppEngine. A key is a unique identifier of an entity across all apps belonging to the user. The key is created automatically by Bumble through the low-level DataStore Java APIs. Besides this, using the Url class is slightly different also. Instead of

@url = Url.first(: original => uri.to_s)

We use:

@url = Url.find(: original => uri.to_s)

Finally because we don’t use the id anymore and use the key instead, we don’t need to do the base 36 conversion so we let the AppEngine handle everything. Instead of:

get '/:snipped' do redirect Url[params[:snipped].to_i(36)].original end

We use:

get '/:snipped' do redirect Url.get(params[:snipped]).original end

This is the full source code:

%w(rubygems sinatra bumble uri).each  { |lib| require lib}

get '/' do haml :index end

post '/' do
  uri = URI::parse(params[:original])
  raise "Invalid URL" unless uri.kind_of? URI::HTTP or uri.kind_of? URI::HTTPS
  @url = Url.find(:original => uri.to_s)
  @url = Url.create(:original => uri.to_s) if @url.nil?
  haml :index
end

get '/:snipped' do redirect Url.get(params[:snipped]).original end

error do haml :index end

use_in_file_templates!

class Url
  include Bumble
  ds : original
  def snipped() self.key.to_s end
end

__END__

@@ layout
!!! 1.1
%html
  %head
    %title Snip! on Google AppEngine
    %link{:rel => 'stylesheet', :href => 'http://www.w3.org/StyleSheets/Core/Modernist', :type => 'text/css'}
  = yield

@@ index
%h1.title Snip! on Google AppEngine
- unless @url.nil?
  %code= @url.original
  snipped to
  %a{:href => env['HTTP_REFERER'] + @url.snipped}
    = env['HTTP_REFERER'] + @url.snipped
#err.warning= env['sinatra.error']
%form{:method => 'post', :action => '/'}
  Snip this:
  %input{:type => 'text', :name => 'original', :size => '50'}
  %input{:type => 'submit', :value => 'snip!'}
%small copyright ©
%a{:href => 'http://blog.saush.com'}
  Chang Sau Sheong
%br
  %a{:href => 'http://github.com/sausheong/snip-appengine'}
    Full source code

The code is ready but here comes the packaging. GAE/J is a Java servlet environment, which means our app needs to be packaged into a war (Java Web ARchive). Fortunately instead of building up the war by hand we can use Warbler, the JRuby war packager. Before running Warbler, we need to have a couple of things. Firstly we need to build the warble configuration file:

$ mkdir config
$ ~/jruby/bin/jruby -S warble config

We create a directory called config and get Warbler to copy a default configuration file to it. Replace the contents with this minimal setup. If you want to explore more, read warble.rb itself.

Warbler::Config.new do |config|
	config.dirs = %w(lib public views)
	config.includes = FileList["appengine-web.xml", "snip.rb", "config.ru", "bumble.rb"]
	config.gems = ['sinatra', 'haml']
	config.gem_dependencies = true
	config.war_name = "saush-snip"
	config.webxml.booter = :rack
	config.webxml.jruby.init.serial = true
	config.java_libs.reject! { |lib| lib =~ /jruby-complete/ }
end

Note that we don’t really need the public and view directories in Snip because everything is in a single file. The 2 other configuration files we will need are appengine-web.xml and config.ru. We also need to include the snip.rb and bumble.rb into the war file for deployment. To get bumble.rb, go to Ola Bini’s Bumble GitHub repository and get the file that is in the sub-folder (not the main one). The last line tells us not to include the jruby-complete.jar library in the lib folder when we run Warbler. I’ll explain this in a minute. Also note the war file is the application id of the application we created in the GAE admin console earlier on (saush-snip).

Next, create a lib folder. Go to the GAE/J download site and download the GAE/J Java library. It should be called something like appengine-api-1.0-sdk-1.2.0.jar. Copy that into the lib folder you’ve just created. We will also need the Java libraries in the lib folder. Normally for a JRuby deployment, Warbler will package it in, but Google has a 1,000 file limit which Ola Bini kindly pointed out. He also provided a script to split the JRuby library into 2 files. You can find the script here and when you run it, it should split jruby-complete.jar into 2 files named jruby-core-1.3.0RC1.jar and jruby-stdlib-1.3.0RC1.jar. You will also need JRuby-Rack but it’s included in Warbler and as you will see later, Warbler will copy it into the war when you run it. JRuby-Rack is an adapter for the Java servlet environment that allows Sinatra (or any Rack-based application) to run.

The next piece is appengine-web.xml. I used Ola Bini’s version as the base:

<appengine -web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>saush-snip</application>
    <version>1</version>
    <static -files />
    <resource -files />
    <sessions -enabled>false</sessions>
    <system -properties>
      <property name="jruby.management.enabled" value="false" />
      <property name="os.arch" value="" />
      <property name="jruby.compile.mode" value="JIT"/> <!-- JIT|FORCE|OFF -->
      <property name="jruby.compile.fastest" value="true"/>
      <property name="jruby.compile.frameless" value="true"/>
      <property name="jruby.compile.positionless" value="true"/>
      <property name="jruby.compile.threadless" value="false"/>
      <property name="jruby.compile.fastops" value="false"/>
      <property name="jruby.compile.fastcase" value="false"/>
      <property name="jruby.compile.chainsize" value="500"/>
      <property name="jruby.compile.lazyHandles" value="false"/>
      <property name="jruby.compile.peephole" value="true"/>
			<property name="jruby.rack.logging" value="stdout"/>
   </system>
</appengine>

The list row in the property line sets logging to STDOUT, which is very useful for debugging. If you don’t set this, you might not be able to see any console output. Again, we need to set the application id that we got earlier on (saush-snip).

Finally we need a config.ru Rackup file to start the whole thing:

%w(rubygems sinatra snip).each  { |lib| require lib}
root_dir = File.dirname(__FILE__)
set :environment, :production
set :root, root_dir
set :app_file, File.join(root_dir, 'snip.rb')
disable :run
run Sinatra::Application

You can of course also find all these things in the Snip-AppEngine repository at git://github.com/sausheong/snip-appengine.git. However it is so much more fun to do it step by step right?

Now that we have all the pieces let’s package our files for deployment. First we need to generate the exploded war file:

$ ~/jruby/bin/jruby -S warble

You should see output like this:

mkdir -p tmp/war/WEB-INF/gems/specifications
cp /Users/saush/jruby/lib/ruby/gems/1.8/specifications/sinatra-0.9.1.1.gemspec tmp/war/WEB-INF/gems/specifications/sinatra-0.9.1.1.gemspec
mkdir -p tmp/war/WEB-INF/gems/gems
JRuby limited openssl loaded. gem install jruby-openssl for full support.

http://wiki.jruby.org/wiki/JRuby_Builtin_OpenSSL

cp /Users/saush/jruby/lib/ruby/gems/1.8/specifications/rack-0.9.1.gemspec tmp/war/WEB-INF/gems/specifications/rack-0.9.1.gemspec
cp /Users/saush/jruby/lib/ruby/gems/1.8/specifications/haml-2.0.9.gemspec tmp/war/WEB-INF/gems/specifications/haml-2.0.9.gemspec
mkdir -p tmp/war/WEB-INF/lib
mkdir -p tmp/war/WEB-INF/public
mkdir -p tmp/war/WEB-INF/views
cp lib/appengine-api-1.0-sdk-1.2.0.jar tmp/war/WEB-INF/lib/appengine-api-1.0-sdk-1.2.0.jar
cp lib/jruby-core-1.3.0RC1.jar tmp/war/WEB-INF/lib/jruby-core-1.3.0RC1.jar
cp lib/jruby-stdlib-1.3.0RC1.jar tmp/war/WEB-INF/lib/jruby-stdlib-1.3.0RC1.jar
cp appengine-web.xml tmp/war/WEB-INF/appengine-web.xml
cp snip.rb tmp/war/WEB-INF/snip.rb
cp config.ru tmp/war/WEB-INF/config.ru
cp bumble.rb tmp/war/WEB-INF/bumble.rb
cp /Users/saush/.gem/jruby/1.8/gems/warbler-0.9.13/lib/jruby-rack-0.9.4.jar tmp/war/WEB-INF/lib/jruby-rack-0.9.4.jar
cp /Users/saush/.gem/jruby/1.8/gems/warbler-0.9.13/lib/jruby-rack-0.9.4.jar tmp/war/WEB-INF/lib/jruby-rack-0.9.4.jar
mkdir -p tmp/war/WEB-INF
jar cf saush-snip.war  -C tmp/war .

You will also get a saush-snip.war file and a bunch of files under the tmp folder, which is really just the war file exploded. We won’t need the war file (saush-snip.war) itself for deployment, only the tmp directory. Before doing the deployment, we need to make a minor adjustment to the tmp/war/WEB-INF/gems/gems/sinatra-0.9.1.1/lib/sinatra.rb file. Somehow the line ‘use_in_file_templates!’ gives an error when deploying to the GAE/J so comment it out.

That’s it! We are ready for the deployment. To deploy run this command:

$ ~/appengine-java-sdk-1.2.0/bin/appcfg.sh --email= --passin update tmp/war/

You should see output like this:

Reading application configuration data...
2009-05-15 19:51:38.916::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
Beginning server interaction for saush-snip...
Password for :
0% Creating staging directory
5% Scanning for jsp files.
20% Scanning files on local disk.
25% Scanned 250 files.
28% Initiating update.
31% Cloning 340 application files.
33% Cloned 100 files.
34% Cloned 200 files.
35% Cloned 300 files.
40% Uploading 0 files.
90% Deploying new version.
95% Will check again in 1 seconds
98% Will check again in 2 seconds
99% Closing update: new version is ready to start serving.
99% Uploading index definitions.
Update complete.
Success.
Cleaning up temporary files...

Now go to http://saush-snip.appspot.com and you should be able to see the new deployment of Snip on the Google AppEngine for Java.

A few other tutorials on the Internet also describe how to deploy Sinatra or Rails-based apps on GAE/J, amongst which Samuel Goebert’s tutorial and Ola Bini’s stand out the most.

A few thoughts on comparing Heroku and GAE/J since I’ve deployed on both of them now. Heroku is definitely the easier platform to deploy, with a just few simple steps compared to the hoops I had to jump for GAE/J. Heroku also has the arguably more familiar persistence mechanism as it uses the familiar RDBMS (postgresql) compared to Google’s DataStore implementation, which today only has Ola Bini’s Bumble implementation compared to the established ActiveRecord, DataMpper and Sequel ORMs. In addition, Google’s implementation has many limitations, some harder to understand than others, which forces applications to suit this platform rather than having the platform really service the application.

On the other hand, Google’s set of tools in its console is really impressive, with graphs of usage, data and log viewers. Google AppEngine also has memcached, url fetcher, integration with Google accounts, sending out mails and hosts of other helper services. Also, Google’s brand name does add some weight when you throw in more mission critical projects though Heroku, backed by Amazon’s EC2 services is no push-over.  As for costs, until large deployments emerge it is hard to say which is the better deal, also it really depends highly on usage patterns.

So are they evenly balanced out? Not for me at least. For now I would still favor Heroku over GAE/J because Heroku is more friendly to Ruby developers. But who knows? It’s exciting times for Rubyists.

About these ads

37 Responses

Subscribe to comments with RSS.

  1. sausheong said, on May 16, 2009 at 3:58 am

    I just read the GAE/J blog that they increased the number of early signups to 25,000 from 10,000 last month, which is good news.

  2. […] April 13th, 2009 by sausheong Update: I deployed the same code to the Google AppEngine for Java and described how I did it here – http://blog.saush.com/2009/05/clone-tinyurl-with-40-lines-of-ruby-code-on-google-appengine-for-java/ […]

  3. Martin Gross said, on May 17, 2009 at 9:40 pm

    Nice! Inspired me to finally use my GAE/J account.

  4. […] Clone TinyURL with 40 lines of Ruby code on Google AppEngine for Java is a comprehensive walkthrough-cum-tutorial that needs little explanation. You learn how to implement a URL shortening service and then deploy it on Google App Engine using JRuby and a little magic. […]

  5. Bart Burkhardt said, on November 16, 2009 at 4:59 pm

    GAE does not support naked domains so it’s useless for URL shortener services.

  6. dezignia said, on December 3, 2009 at 2:21 pm

    Nice! If your looking for a feature rich open source URL Shortener using Google’s App Engine infrastructure then consider Loo.Lu – http://code.google.com/p/loolu/

  7. kyaw kyaw naing said, on December 10, 2009 at 4:16 pm

    cool, man.
    thanks a lot.
    I will be watching your next post.

  8. rubypdf said, on December 25, 2009 at 1:02 am

    I like the script, thanks for your share, and it would be better if it had the same function as bit.ly, :)

  9. RichardBlogorodni said, on May 1, 2010 at 2:41 pm

    Май для россиян — это особенный месяц, отмеченный двумя очень важными весенними праздниками. День Победы и Праздник весны и труда — это дни, когда мы в очередной раз благодарим старшее поколение за мир и блага, ради которых они трудились и сражались.
    Спраздничком Вас blog.saush.com

  10. BayPirate said, on September 25, 2014 at 9:48 pm

    Turn free movement back on and they’re going to follow you forward.
    Apple Pages templates be useful when you need to save time in creating your projects.
    If anything, the years have only increased my appreciation for Thomas & Mac – Neil’s
    swashbuckling time travel tale.

  11. online marketeer Rotterdam said, on September 28, 2014 at 10:42 pm

    U kunt ook een groot aantal andere functies in zoals spellingcontrole en tekst voorspelling.
    EEN gestroomlijnde interface met één klik toegang tot en verstrekking van video-oproepen van de desktop zelf
    ook bijdragen aan het totale video bellen. Goed, in netwerken groei van globe, een van de belangrijkste contribuanten is
    kabelinstallatie Sacramento.

  12. http://www.memecenter.com/jonaszekaq22 said, on September 30, 2014 at 12:37 am

    While you can listen to what they are saying, you need to make sure you are following your doctors directions
    and doing what is best for your body so that
    you can get better. Take control of your health and boost your immune
    system with the nutrients it needs from food and supplements chock
    full of antioxidants. He brains with a pistol two cops who pull him over.

  13. DNN said, on September 30, 2014 at 12:45 pm

    Transfer any gear require to so allows with a entire cracked,
    treatments the best gas mileage possible. ” Do keep receipts and bills of all expenses incurred in the accident to be presented to your insurer. Check hydraulic steering fluid and top off as needed.

  14. kks.by said, on September 30, 2014 at 2:22 pm

    Я люблю эти цвета! Это является,
    фактически, идеальным фото.Очень приятная глубина :)
    Вот это красивое фото с очень хорошим вкусом :) Фантастические
    фото очень радуют душу.
    Ваши фотографии выглядят
    отлично!!!

  15. cartier bracelet said, on September 30, 2014 at 5:38 pm

    I’m really enjoying the design and layout of your blog.
    It’s a very easy on the eyes which makes it much more enjoyable for me to come
    here and visit more often. Did you hire out a designer to
    create your theme? Excellent work!

  16. Marrakech riad massage said, on October 1, 2014 at 8:45 am

    He said he and Hayes had location riad marrakech 40 people
    no problem replying in the affirmative, and of any change in the face of college sports.
    A European Commission spokesperson refused to comment on both the Mediterranean Sea.

    Walking the old fortifications, complete with 17th century cannons,
    is like talking a walk in the woods – my daughter had her birthday tea here last year
    it was superb.

  17. www.youtube.com said, on October 2, 2014 at 1:32 pm

    I couldn’t resist commenting. Perfectly written!

  18. torrent search said, on October 4, 2014 at 1:45 pm

    It causes it to be the first choice with the users
    as well as the most wanted and comfy P2P torrent engine available in the world.
    The reasons behind installation without CD
    can be anything with the two: you do not have the Windows CD or perhaps your CD drive is just not functioning.

    So, the website walks a very thin tightrope, legally speaking.

  19. fifa 14 hack coins no survey said, on October 4, 2014 at 6:45 pm

    It’s in fact very complex in this active life to listen news on Television, so I just
    use web for that purpose, and get the latest information.

  20. wear wizards said, on October 5, 2014 at 2:27 pm

    Very nice post. I simply stumbled upoon your blog and wanted to say that I have truly loved surfing around
    your weblog posts. After all I will be subscribing iin your rss feed and I am hoping
    you write once more soon!

  21. gratis telefon sex said, on October 5, 2014 at 2:52 pm

    Hva er vel mer opphissende enn tanken på fri og uforpliktende
    Sex med еn fremmed. Noen av ԁe beste opplefelsene jeg har hatt har vært
    Ԁе tøffeste, meen ikle fordi det involverte vold eller smerte.
    Ɗen fullstendig oppslukte stemmen din som unnslapp en klynkende, fortvilet,
    nærmest desperat lyd.

  22. pills said, on October 6, 2014 at 9:26 am

    You really make it appear really easy with your presentation however I in finding this matter to be really
    something which I think I would never understand.

    It sort of feels too complicated and very vast for me. I am looking ahead in your next publish, I’ll attempt to get the dangle of it!

  23. Water damage said, on October 6, 2014 at 9:30 am

    Link exchange is nothing else but it is only placing the other person’s web site link on your page at
    appropriate place and other person will also do same in support of you.

  24. Richard said, on October 6, 2014 at 4:16 pm

    4)Kitna must stay the most effective qb within NFC Main. About The Author Sandeep Srivastava is a renowned sports news specialist who has worked with many big widely used news portals.

    Many stage producers that are trying to give the perception of class and a luxurious setting add high-end reproductions of famous paintings to the background to spice up the scene.

  25. search said, on October 6, 2014 at 7:28 pm

    Spot on with this write-up, I actually believe that this
    web site needs far more attention. I’ll probably
    be returning to read more, thanks for the advice!

  26. En este caso también se incluye Gol Stadium para poder ver todos y cada
    uno de los contenidos a través de Internet.

  27. Steven said, on October 8, 2014 at 3:22 pm

    It’s remarkable to visit this web site and reading
    the views of all colleagues concerning this piece of writing, while I am also keen of getting experience.

  28. easytechliving.jux.com said, on October 9, 2014 at 1:29 am

    Marvelous, what a website it is! This webpage provides hekpful information to
    us, keep itt up.

  29. spmba.de said, on October 9, 2014 at 5:21 am

    I am genuinely delighted to glance at this website
    posts which consists of tons of valuable data, thanks for providing these data.

  30. review Diabetes protocol said, on October 9, 2014 at 10:22 am

    This gene works to give instructions for making ceruloplasmin, a protein, which helps
    in the transport and processing of iron. Farxiga
    is a sodium-glucose co-transporter 2 (SGLT2) inhibitor that
    blocks the reabsorption of glucose by the kidney, increases glucose excretion, and lowers blood glucose levels.
    Certain vitamins and minerals have also been found helpful in lowering blood
    sugar and thus widely used in the treatment of diabetes.

  31. Christine said, on October 9, 2014 at 10:14 pm

    If you will get Master Resell rights, you’ll be able to sell the software program, e-book or PDF file, now
    and again video series as well for cash you decide on,
    or simply provide it with away to your subscribers.
    This article will speak about Internet marketing resulting
    in getting a master’s degree in Internet marketing online.
    Use online follow-up methods for example email and be prepared to use direct mail.

  32. Modesta said, on October 14, 2014 at 6:33 pm

    I don’t know if it’s just me or if everyone else experiencing problems with your
    site. It appears like some of the written text in your posts are running off the screen. Can somebody else please provide feedback and let me know if
    this is happening to them too? This could be a problem with my web browser because I’ve had this
    happen previously. Many thanks

  33. gay said, on October 16, 2014 at 8:05 pm

    It’s an awesome post in support of all the online people; they will take advantage from
    it I am sure.

  34. Dilcindo said, on October 17, 2014 at 4:50 am

    Truly when someone doesn’t know after that its up to other users that
    they will assist, so here it takes place.

  35. Cortney said, on October 31, 2014 at 8:06 am

    It’s nice to sprinkle a little chili salt over your potatoes to give them that extra
    kick. Paying homage to your family’s delectable culinary
    prowess can be a thoughtful gift for a mother, grandmother or mother-in-law
    as well. There is an endless range of craft and
    make-your-own packs available using a range of
    materials.

  36. https://delicious.com/ said, on November 22, 2014 at 2:05 pm

    To find out more about exclusive guitar classes in North Toronto please
    contact Gabriella directly for occasions and costs that are offered.

  37. trucchi per fare soldi said, on December 7, 2014 at 12:35 am

    A fascinating discussion is definitely worth
    comment. I do think that you ought to publish more on this topic, it may not be a taboo subject
    but generally people do not discuss such topics.

    To the next! Many thanks!!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 450 other followers

%d bloggers like this: