your own (little) gem: building an online business with ruby
TRANSCRIPT
Your own (little) gembuilding an online business with Ruby
Lindsay Holmwood <[email protected]>
(.com)
business
development
operations
have an idea
check that it’s profitable
are both fruit
apples oranges
web store book shop
are both businesses
financials
are important.
do them!
(conservative estimates are best)
check your tech
time is an expense
halve your deliverables
then halve them again
1-2 core features
write ideas down
development
development
Merb (merbivore.com)
development
Merb (merbivore.com)
DataMapper (datamapper.org)
development
Merb (merbivore.com)
DataMapper (datamapper.org)
MooTools (mootools.net)
development
bootstrap
bootstrap
$ sudo gem install merb-core merb-more
bootstrap
$ sudo gem install merb-core merb-more
$ sudo gem install dm-core dm-more do_sqlite3
bootstrap
$ sudo gem install merb-core merb-more
$ merb-gen app schoonerwatch.com
$ sudo gem install dm-core dm-more do_sqlite3
bootstrap
$ sudo gem install merb-core merb-more
$ merb-gen app schoonerwatch.com
$ cd schoonerwatch.com
$ sudo gem install dm-core dm-more do_sqlite3
bootstrap
$ sudo gem install merb-core merb-more
$ merb-gen app schoonerwatch.com
$ cd schoonerwatch.com
$ bzr init ; bzr add
$ bzr commit -m "initial commit"
$ sudo gem install dm-core dm-more do_sqlite3
bootstrap
$ sudo gem install merb-core merb-more
$ merb-gen app schoonerwatch.com
$ cd schoonerwatch.com
$ bzr init ; bzr add
$ bzr commit -m "initial commit"
$ sudo gem install dm-core dm-more do_sqlite3
s/bzr/git/g
merb-gen
merb-gen
$ merb &
merb-gen
$ merb &
$ merb-gen resource pub
merb-gen
$ merb &
$ merb-gen resource pub
$ bzr commit -m "added merb-gen'd pub resource"
merb-gen
$ merb &
$ merb-gen resource pub
$ bzr commit -m "added merb-gen'd pub resource"
$ vim spec/spec.opts spec/spec_helper.rb
spec/spec.opts
--colour--formatprofile
spec/spec_helper.rb
...
Spec::Runner.configure do |config| config.include(Merb::Test::ViewHelper) config.include(Merb::Test::RouteHelper) config.include(Merb::Test::ControllerHelper) end
DataMapper.auto_migrate!
run specs
run specs
$ rake spec:request
run specs
$ rake spec:request
.**......
Pending:resource(:pubs) GET contains a list of pubs (TODO) Called from ./spec/requests/pubs_spec.rb:21resource(:pubs) GET has a list of pubs (TODO) Called from ./spec/requests/pubs_spec.rb:33
Finished in 0.322787 seconds
9 examples, 0 failures, 2 pending
request specs
request specs
$ vim spec/requests/pubs_spec.rb
request specs
$ vim spec/requests/pubs_spec.rb
...
it "contains a sorted list of pubs" do @response.should have_xpath("//h3[contains(.,'Cheapest')]") @response.should have_xpath("//h3[contains(.,'Nearest')]")
end
run specs
$ rake spec:request
run specs
$ rake spec:request
Pending:resource(:pubs) GET has a list of pubs (TODO) Called from ./spec/requests/pubs_spec.rb:33
1)'resource(:pubs) GET contains a sorted list of pubs' FAILEDexpected following text to match xpath //h3[contains(.,'Cheapest')]’./spec/requests/pubs_spec.rb:21:
Finished in 0.411139 seconds
9 examples, 1 failure, 1 pending
run specs
haml
haml
$ vim config/dependencies.rb
haml
$ vim config/dependencies.rb
...
dependency "merb-haml", merb_gems_version
haml
$ vim config/dependencies.rb
...
dependency "merb-haml", merb_gems_version
$ vim config/init.rb
haml
$ vim config/dependencies.rb
...
dependency "merb-haml", merb_gems_version
$ vim config/init.rb
...
use_template_engine :haml
hamlise view
hamlise view
$ bzr mv app/views/pubs/index.html.erb \ app/views/pubs/index.html.haml
hamlise view
$ bzr mv app/views/pubs/index.html.erb \ app/views/pubs/index.html.haml
$ vim app/views/pubs/index.html.haml
hamlise view
$ bzr mv app/views/pubs/index.html.erb \ app/views/pubs/index.html.haml
$ vim app/views/pubs/index.html.haml
%h3 Nearest%h3 Cheapest
hamlise view
$ bzr mv app/views/pubs/index.html.erb \ app/views/pubs/index.html.haml
$ vim app/views/pubs/index.html.haml
%h3 Nearest%h3 Cheapest
$ rake spec:request
Pending:resource(:pubs) GET has a list of pubs (TODO) Called from ./spec/requests/pubs_spec.rb:33
Finished in 0.389194 seconds
9 examples, 0 failures, 1 pending
tests pass!
model specs
model specs
$ vim spec/models/pubs_spec.rb
model specs
$ vim spec/models/pubs_spec.rb
1.
model specs
$ vim spec/models/pubs_spec.rb
it "should have name, address, price" do @pubs.each do |pub| pub.name.should_not be_nil pub.address.should_not be_nil pub.price.should_not be_nil end end
1.
model specs
$ vim spec/models/pubs_spec.rb
it "should handle different url formats" do @pubs.each do |pub| pub.url = 'mygreatpub.com.au' pub.save.should be_true pub.url = 'http://mygreatpub.com.au/' pub.save.should be_true endend
2.
model specs
$ vim spec/models/pubs_spec.rb
it "should consistently format + store urls" do @pubs.each do |pub| adjective = %w[great swanky awesome stylish][rand(4)] pub.url = "#{adjective}pub.com.au" pub.save.should be_true pub.url.should =~ /^http:\/\/.+\/$/ endend
3.
model specs
model specs
$ vim spec/models/pubs_spec.rb
before(:each) do @pubs = 10.of { Pub.generate } end
model specs
$ vim spec/models/pubs_spec.rb
before(:each) do @pubs = 10.of { Pub.generate } end
require 'dm-sweatshop' Pub.fixture do { :name => ( name = %w[John Jane Jerry Justin][rand(4)] + "'s Pub"), :price => (1..30).to_a[rand(30)], :address => "#{(40..166).to_a[rand(126)]} Spring " + %w[Street Road Avenue][rand(3)], :url => "http://#{name.gsub(/\W/, '').downcase}.com/" } end
$ vim spec/spec_fixtures.rb
run specs
$ rake spec:models
run specs
$ rake spec:models
1)NameError in 'Pub should have name, address, price'address= is not a public property
2)NameError in 'Pub should handle different url formats'address= is not a public property
3) NameError in 'Pub should consistently format + store urls'address= is not a public property
Finished in 0.252169 seconds 3 examples, 3 failures
run specs
define properties
define properties
$ vim app/models/pub.rb
define properties
$ vim app/models/pub.rb
class Pub include DataMapper::Resource property :id, Serial property :name, String, :nullable => false property :address, String, :nullable => false property :price, BigDecimal, :nullable => false property :url, String
custom setter
$ vim app/models/pub.rb
...
def url=(string) string = "http://#{string}" unless string =~ /^http:\/\/.+\// string += '/' unless string =~ /\/$/ @url = string end
end
run specs
$ rake spec:models
run specs
$ rake spec:models
run specs
Finished in 0.488351 seconds
3 examples, 0 failures
win!
request specs
request specs
$ vim spec/requests/pubs_spec.rb
request specs
$ vim spec/requests/pubs_spec.rb
...
it "contains a list of cheapest pubs" do @response.should have_xpath("//div[@id=’cheapest’]")@response.should have_xpath("//div[@id=’cheapest’]//table//tr//td[@class='name']")@response.should have_xpath("//div[@id=’cheapest’]//table//tr//td[@class='address']")@response.should have_xpath("//div[@id=’cheapest’]//table//tr//td[contains(@class,'price')]")
end
run specs
$ rake spec:request
run specs
$ rake spec:request
Pending:resource(:pubs) GET has a list of pubs (TODO) Called from ./spec/requests/pubs_spec.rb:37
1)'resource(:pubs) GET contains a list cheapest pubs' FAILEDexpected following text to match xpath //div[@id='cheapest']./spec/requests/pubs_spec.rb:26:
Finished in 0.411139 seconds
10 examples, 1 failure, 1 pending
run specs
build partial
build partial
$ vim app/views/pubs/index.html.haml
build partial
$ vim app/views/pubs/index.html.haml
%h3 Nearest%h3 Cheapest%div#cheapest= partial "cheapest"
build partial
$ vim app/views/pubs/index.html.haml
%h3 Nearest%h3 Cheapest%div#cheapest= partial "cheapest"
$ vim app/views/pubs/_cheapest.html.haml
build partial
$ vim app/views/pubs/index.html.haml
%h3 Nearest%h3 Cheapest%div#cheapest= partial "cheapest"
$ vim app/views/pubs/_cheapest.html.haml
%table - @pubs.each_with_index do |pub, index| %tr %td.name= link_to pub.name, pub.url %td.address= pub.address %td{:class => "price cheapest-#{index}"} = '$' + sprintf("%.2f", pub.price)
run specs
$ rake spec:request
run specs
$ rake spec:request
Pending:resource(:pubs) GET has a list of pubs (TODO) Called from ./spec/requests/pubs_spec.rb:37
Finished in 0.854704 seconds
10 examples, 0 failures, 1 pending
run specs
nearest
nearest
$ vim app/views/pubs/index.html.haml
nearest
$ vim app/views/pubs/index.html.haml
%h3 Nearest%div#nearest= partial "nearest"
%h3 Cheapest%div#cheapest= partial "cheapest"
nearest
$ vim app/views/pubs/index.html.haml
%h3 Nearest%div#nearest= partial "nearest"
%h3 Cheapest%div#cheapest= partial "cheapest"
$ vim app/views/pubs/_nearest.html.haml
nearest
$ vim app/views/pubs/index.html.haml
%h3 Nearest%div#nearest= partial "nearest"
%h3 Cheapest%div#cheapest= partial "cheapest"
$ vim app/views/pubs/_nearest.html.haml
%div#map{:style => "width: 800px; height: 600px; margin: auto;"}
global layout
global layout
$ vim app/views/layout/application.html.erb
global layout
$ vim app/views/layout/application.html.erb
<%= js_include_tag 'mootools' %><%= js_include_tag 'SimpleTabs' %><%= js_include_tag 'application' %><script src="http://maps.google.com/maps?file=api&v=2&key=
<%= Merb.config.gmaps_api_key %>" type="text/javascript"></script>
api key
$ vim app/views/layout/application.html.erb
<%= js_include_tag 'mootools' %><%= js_include_tag 'SimpleTabs' %><%= js_include_tag 'application' %><script src="http://maps.google.com/maps?file=api&v=2&key=
<%= Merb.config.gmaps_api_key %>" type="text/javascript"></script>
$ vim config/environments/development.rb
c[:gmaps_api_key] = "foo"
production api key
$ vim app/views/layout/application.html.erb
<%= js_include_tag 'mootools' %><%= js_include_tag 'SimpleTabs' %><%= js_include_tag 'application' %><script src="http://maps.google.com/maps?file=api&v=2&key=
<%= Merb.config.gmaps_api_key %>" type="text/javascript"></script>
$ vim config/environments/production.rb
c[:gmaps_api_key] = "bar"
nearest
$ vim public/javascripts/application.js
window.addEvent('domready', function() {
});
dash o’ javascript
$ vim public/javascripts/application.js
window.addEvent('domready', function() {
if (GBrowserIsCompatible()) {var map = new GMap2($("map"));map.enableScrollWheelZoom();map.setCenter(new GLatLng(-33.86336, 151.207151), 12);var geocoder = new GClientGeocoder();
/* geocode top-ten addresses */$$('div#cheapest tr').each( function(element) {
showAddress(map, geocoder, element);});
}
});
custom geocoder
$ vim public/javascripts/application.js
function showAddress(map, geocoder, element) { var name = element.getElements('td.name a').get('html'); var address = element.getChildren('td.address').get('html'); var price = element.getElements('td.price').get('html'); var link = element.getElements('td.name a').get('href'); geocoder.getLatLng(address, function(point) {
if (point) { var marker = new GMarker(point);
marker.bindInfoWindowHtml("<h4>" + name + "</h4>" + ... ); map.addOverlay(marker); } }); }
some stylesheet foo
slices
freezing
param-protection
action-args
operations
monitoring
collectd
lightweight
statistics
collection
daemon
lightweight
statistics
collection
daemon
LoadPlugin cpuLoadPlugin dfLoadPlugin diskLoadPlugin interfaceLoadPlugin loadLoadPlugin memoryLoadPlugin networkLoadPlugin processesLoadPlugin rrdtoolLoadPlugin socketLoadPlugin swapLoadPlugin users
...
<Plugin df> MountPoint "/"</Plugin>
<Plugin interface> Interface "eth0" Interface "eth1"</Plugin>
...
<Plugin rrdtool> DataDir "/var/lib/collectd/rrd" CacheTimeout 180 CacheFlush 7200</Plugin>
<Plugin network> Server "my.monitoring.server.net"# Port 25826</Plugin>
...
<Plugin processes> Process "ruby"# Process "mysqld"</Plugin>
...
eek!
collectd-nagios
$ /usr/bin/collectd-nagios \ -s /var/run/collectd/socket \ -n cpu-0/cpu-idle \ -H my.server.com \ -w 40: -c 10:
0 critical, 1 warning, 0 okay | value=34.000000;;;;
$ /usr/bin/collectd-nagios \ -s /var/run/collectd/socket \ -n df/df-root -H my.server.com \ -d free -c 314572800: -w 524288000:
0 critical, 0 warning, 1 okay |free=924288000.0;;;;
0 good1 bad2 ugly
$ ls /var/lib/collectd/rrd/my.server.comcpu-0 cpu-1 df disk-sda disk-sda1 interface load memory swap users
apache apcups apple sensors
ascent battery cpu
cpufreq csv df
disk dns email
entropy exec filecount
hddtemp interface iptables
ipmi ipvs irqs
libvirt load logfile
mbmon memcached memory
multimeter mysql netlink
network nfs nginx
notify_desktop notify_email ntpd
nut onewire perl
ping postgresql powerdns
processes rrdtool sensors
serial snmp swap
syslog tail tape
tcpconns teamspeak2 thermal
unixsock users uuid
vmem vserver wireless
apache apcups apple sensors
ascent battery cpu
cpufreq csv df
disk dns email
entropy exec filecount
hddtemp interface iptables
ipmi ipvs irqs
libvirt load logfile
mbmon memcached memory
multimeter mysql netlink
network nfs nginx
notify_desktop notify_email ntpd
nut onewire perl
ping postgresql powerdns
processes rrdtool sensors
serial snmp swap
syslog tail tape
tcpconns teamspeak2 thermal
unixsock users uuid
vmem vserver wireless
configuration managementconfiguration management
language => client/server => library
Puppet
manifeststypes classes nodes
manifests => describetypes classes nodes
manifeststypes => manageclasses nodes
package { apache2: ensure => present}
package { apache2: ensure => present}
file { "/etc/apache2/apache2.conf",content => template("/etc/puppet/config/classes/httpd_server/httpd.conf.erb"),mode => 644
}
package { apache2: ensure => present}
file { "/etc/apache2/apache2.conf",content => template("/etc/puppet/config/classes/httpd_server/httpd.conf.erb"),mode => 644
}
service { apache2:ensure => running,subscribe => File["/etc/apache2/apache2.conf"]
}
manifeststypes => manageclasses nodes
manifeststypesclasses => groupnodes
package { apache2: ensure => present }
# ...
class apache2 {
package { apache2: ensure => present }
# ...
}
class passenger inherits apache2 {
package { libapache2-mod-passenger: ensure => present }
# ...
}
define apache2 ($port) {
package { apache2: ensure => present }
# ...
}
manifeststypesclasses => groupnodes
manifeststypes classes nodes => apply
node "srv01.skippy.com" { include passenger}
node "srv01.skippy.com" { include passenger}
node "srv01.skippy.com" { include passenger}
node "server02.skippy.com" { include passenger}
node "server03.skippy.com" { include passenger}
node "merbnode" { include passenger}
node "server01.skippy.com" inherits passenger {}
node "server02.skippy.com" inherits passenger {}
node "server03.skippy.com" { include customisation}
versionable infrastructure
versionable infrastructure
(for free)
$ cd skippy.puppet.lindsay
$ git init
# puppet magic happens
$ git push
# puppetd --test --debug
resources
getting real
seth godin
wiki.merbivore.com
Thank you!(and questions)