greader 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,109 @@
1
+ module GReader
2
+ # A feed is a collection of entries.
3
+ #
4
+ # == Common usage
5
+ #
6
+ # Getting feeds:
7
+ #
8
+ # feed = client.feed('FEED_ID')
9
+ #
10
+ # Common metadata:
11
+ #
12
+ # feed.title #=> "Rico's blog" (or #to_s)
13
+ # feed.url #=> "http://ricostacruz.com"
14
+ # feed.id #=> "feed/http://ricostacruz.com" (from Google)
15
+ #
16
+ # Collections:
17
+ #
18
+ # feed.entries #=> [#<Entry>, ...]
19
+ # feed.tags #=> [#<Tag>, ...]
20
+ #
21
+ # Entry lookup:
22
+ #
23
+ # feed.entries['ENTRY_ID']
24
+ #
25
+ # Other ways of getting feeds:
26
+ #
27
+ # client.feeds.each { |feed| }
28
+ # client.tag('TAG_ID').feeds.each { |feed| }
29
+ #
30
+ # :stopdoc:
31
+ # This is what Google spits out as JSON.
32
+ #
33
+ # id: feed/http://xkcd.com/rss.xml
34
+ # title: xkcd.com
35
+ # categories:
36
+ # - id: user/05185502537486227907/label/Misc | Comics
37
+ # label: Misc | Comics
38
+ # sortid: 6795ABCE
39
+ # firstitemmsec: "1205294559301"
40
+ # htmlUrl: http://xkcd.com/
41
+ #
42
+ class Feed
43
+ include Utilities
44
+
45
+ attr_reader :url
46
+ attr_reader :title
47
+ attr_reader :sortid
48
+ attr_reader :id
49
+ attr_reader :client
50
+
51
+ alias to_s title
52
+
53
+ def initialize(client=Client.new, options)
54
+ @client = client
55
+ @options = options
56
+ @title = options['title']
57
+ @url = options['htmlUrl']
58
+ @sortid = options['sortid']
59
+ @id = options['id']
60
+ @tags_ = options['categories']
61
+ end
62
+
63
+ def tags
64
+ @tags ||= begin
65
+ @tags_.map { |tag| @client.tag(tag['id']) }
66
+ end
67
+ end
68
+
69
+ def to_param
70
+ slug @id
71
+ end
72
+
73
+ def <=>(other)
74
+ sortid <=> other.sortid
75
+ end
76
+
77
+ # List of entries.
78
+ #
79
+ # == Options
80
+ # [limit] The number of items (default +20+)
81
+ # [order] The order of items; +:desc+ is recent first, +:asc+ is
82
+ # earliest first (default +:desc+)
83
+ # [start_time] The time (+Time+ object) from which to start getting
84
+ # items. Only applicable if +order+ is +:asc+.
85
+ #
86
+ # == Quirks
87
+ # The results are cached. If you want to purge the cache, use {#expire!}.
88
+ #
89
+ # @return [Entries] The entries it contains.
90
+ #
91
+ # @example
92
+ #
93
+ # @client.feeds[2].entries
94
+ # @client.feeds[2].entries limit: 10
95
+ # @client.feeds[2].entries order: :asc, start_time: Time.now-86400
96
+ #
97
+ def entries(options={})
98
+ @entries ||= Entries.fetch @client, "stream/contents/#{escape id}"
99
+ end
100
+
101
+ def inspect
102
+ "#<#{self.class.name} \"#{title}\" (#{url})>"
103
+ end
104
+
105
+ def expire!
106
+ @entries = nil
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,71 @@
1
+ module GReader
2
+ # A tag.
3
+ #
4
+ # == Common usage
5
+ #
6
+ # Getting tags:
7
+ #
8
+ # tag = @client.tag('TAG_ID') # see Tag#id below
9
+ #
10
+ # Metadata:
11
+ #
12
+ # tag.to_s #=> "Comics"
13
+ # tag.id #=> "user/1000/tag/Comics"
14
+ #
15
+ # Collections:
16
+ #
17
+ # tag.feeds #=> [#<Feed "xkcd">, #<Feed "Dilbert">, ...]
18
+ #
19
+ class Tag
20
+ include Utilities
21
+
22
+ # The ID. Also used for {Client#tag}.
23
+ # @return [string]
24
+ attr_reader :id
25
+
26
+ # A sortable field.
27
+ # @return [string]
28
+ attr_reader :sortid
29
+
30
+ # A link to {Client}.
31
+ # @return [Client]
32
+ attr_reader :client
33
+
34
+ def initialize(client=Client.new, options)
35
+ @client = client
36
+ @options = options
37
+ @id = options['id']
38
+ @sortid = options['sortid']
39
+ end
40
+
41
+ def to_s
42
+ @id.split('/').last
43
+ end
44
+
45
+ def to_param
46
+ slug @id
47
+ end
48
+
49
+ def <=>(other)
50
+ sortid <=> other.sortid
51
+ end
52
+
53
+ # Returns a list of feeds.
54
+ # @return [Feed[]]
55
+ def feeds
56
+ @client.feeds.select { |feed| feed.tags.include?(self) }
57
+ end
58
+
59
+ # (see Feed#entries)
60
+ def entries(options={})
61
+ @entries ||= Entries.fetch @client, "stream/contents/#{escape id}"
62
+ end
63
+
64
+ # Expires the cache.
65
+ # @return nil
66
+ def expire!
67
+ @entries = nil
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,21 @@
1
+ module GReader
2
+ module Utilities
3
+ extend self
4
+
5
+ def escape(str)
6
+ CGI.escape(str).gsub('+', '%20')
7
+ end
8
+
9
+ def slug(str)
10
+ str.gsub(/[\/\?=&]/, '_')
11
+ end
12
+
13
+ def strip_tags(str)
14
+ str.gsub(%r{</?[^>]+?>}, '')
15
+ end
16
+
17
+ def kv_map(hash)
18
+ hash.map { |k, v| '%s=%s' % [CGI.escape(k.to_s), URI.escape(v.to_s)] }.join('&')
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class ClientTest < Test::Unit::TestCase
4
+ setup do
5
+ @client = GReader.auth credentials
6
+ end
7
+
8
+ test "authenticate" do
9
+ assert @client.logged_in?
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class FeedTest < Test::Unit::TestCase
4
+ setup do
5
+ @client = GReader.auth credentials
6
+ @feeds = @client.feeds
7
+ @feed = @feeds[29]
8
+ end
9
+
10
+ test "feeds" do
11
+ assert @feeds.is_a?(Array)
12
+ assert @feeds.size == 79
13
+
14
+ assert_equal "Badass JavaScript", @feed.title
15
+ assert_equal "http://badassjs.com/", @feed.url
16
+ end
17
+
18
+ test "Feed#tags" do
19
+ tag = @feed.tags.first
20
+ assert tag.is_a?(GReader::Tag)
21
+ assert_equal "Dev | JavaScript", tag.to_s
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ SID=XYZ-a
2
+ LSID=XYZ-b
3
+ Auth=XYZ-c
4
+
@@ -0,0 +1,3 @@
1
+ # Rename this to credentials.yml to use real credentials.
2
+ :email: rico.sc@gmail.com
3
+ :password: dorsetper
@@ -0,0 +1,3 @@
1
+ # Rename this to credentials.yml to use real credentials.
2
+ :email: sean@mcnamara-troy.com
3
+ :password: connor
@@ -0,0 +1,57 @@
1
+ {"direction":"ltr","id":"user/05185502537486227907/label/Dev | Ruby","title":"\"Dev | Ruby\" via RSC in Google Reader","continuation":"CM-VoI7iw6YC","self":[{"href":"http://www.google.com/reader/api/0/stream/contents/user%2F05185502537486227907%2Flabel%2FDev%20%7C%20Ruby?output\u003djson\u0026client\u003dgreader.rb-test"}],"author":"RSC","updated":1300399850,"items":[{"crawlTimeMsec":"1300399850673","id":"tag:google.com,2005:reader/item/7d0db5cc245f5616","categories":["user/05185502537486227907/label/Dev | Ruby",
2
+ "user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh"],"title":"Github reviews as a way to improve code quality?","published":1300399449,"updated":1300399449,"alternate":[{"href":"http://feedproxy.google.com/~r/antirez/~3/XYDicZ1Uf0A/github-review-to-improve-code.html","type":"text/html"}],"summary":{"direction":"ltr","content":"\u003cdiv\u003e\u003cdiv style\u003d\"clear:both\"\u003e\u003c/div\u003e\u003cdiv\u003eToday I tweeted quite a bit about Ruby software quality.\nThe trigger was simply a Ruby/Sinatra application I run for personal use that crashed from time to time due to a GC bug in the Ruby interpreter I was using (the default of a recent Ubuntu install, sadly).\n\u003cbr\u003e\u003cbr\u003e\n\nThis is an interpreter bug, and I hope it is a rare one, but this episode made me thinking about how many bad stories I accumulated since my preferred high level programming language is Ruby: bad documented gems, not working gems, dependencies hell, hard to install things, bad performances, and so forth.\n\u003cbr\u003e\u003cbr\u003e\n\nThe test culture of Ruby helps a bit, but if you are an experienced software developer you know how a few tests can\u0026#39;t guarantee software quality. Honestly I saw testing as part of the problem from time to time, with the attitude \u0026quot;if it passes tests it can be merged\u0026quot;. Testing is useful but not so powerful, unfortunately.\n\u003cbr\u003e\u003cbr\u003e\n\nThere is also a lot of great and documented code, but in general it seems to me that there is a real problem about code quality, and maybe there is something that github.com can do to improve the state of things: to make users aware that some code may not be perfect, and to make the developers aware, too.\n\u003cbr\u003e\u003cbr\u003e\n\nIt is as simple as this: \u003cb\u003egithub, please make us able to rate projects\u003c/b\u003e. If project-wide reviews are too bold or may appear too rude, just do it the old way, with \u0026quot;stars\u0026quot;. If users could rate:\n\u003cbr\u003e\u003cbr\u003e\n\n\u003cul\u003e\u003cli\u003eusability / installation\u003c/li\u003e\n\n\u003cli\u003edocumentation\u003c/li\u003e\n\n\u003cli\u003ecode quality\u003c/li\u003e\n\n\u003cli\u003estability\u003c/li\u003e\n\n\u003cli\u003eperformances\u003c/li\u003e\n\n\u003c/ul\u003e\n\u003cbr\u003e\u003cbr\u003e\n\nAll this anonymously (you could be surprised about how much people in a given community are connected, coworkers, and in general not willing to say the truth about your project).\n\u003cbr\u003e\u003cbr\u003e\n\nwith a vote between 1 and 5 starts, this may help a lot of people to make an initial idea about the quality of a project. The developer can gain very important info about what could be improved, that is a very valuable information, as there are a lot of people that are ready to say your project is cool, but constructive criticism about what is the weak side of the project is hard to obtain.\n\u003cbr\u003e\u003cbr\u003e\n\nThis could be done as a separated web service using the github API, but what's the point in doing this? Having it integrated into github is much better of course. But if github will think that it's not a great match for the site, it can be an interesting week end project to work on.\n\u003cbr\u003e\u003cbr\u003e\n\nPlease leave comments on \u003ca href\u003d\"http://news.ycombinator.com/item?id\u003d2338351\"\u003eHN\u003c/a\u003e instead of using the blog comments.\n\u003cbr\u003e\u003cbr\u003e\n\n\u003cb\u003eEdit:\u003c/b\u003e Arguments from discussions on twitter / comments:\n\u003cul\u003e\u003cli\u003eHow to prevent spam? The only hope I've is that the OSS programming community is less interested into producing some spam vote. But you can restrict the vote to followers of a project or to use other tricks, like allowing to vote only if the user is trusted enough by a given number of parameters.\u003c/li\u003e\n\n\u003cli\u003eUsing number of followers of a project as a meter? I think does not work, I'll follow a project that's not very good if I need it, and there are little or not better alternatives. Also I may love a project that is badly documented, so I'll provide five stars for everything but for doc.\u003c/li\u003e\n\n\u003c/ul\u003e\n\u003cb\u003eEdit2:\u003c/b\u003e\n\u003cul\u003e\u003cli\u003eWhat about the project getting better with time, or the the other way around? Just provide a lot more weight in the average computation to recent reviews. Also put the old ones into the mix but with some math rule so that, the older, the less relevant in the weighted sum.\u003c/li\u003e\n\n\u003c/ul\u003e\u003c/div\u003e\u003cdiv\u003e\u003cdiv\u003epost read 7452 times\u003csup\u003e\u003ca href\u003d\"http://antirez.com/page/uniquevisitors\"\u003e*\u003c/a\u003e\u003c/sup\u003e (average 7452.0 visits/day)\u003c/div\u003ePosted at 22:04:09 \u003ca href\u003d\"http://antirez.com/post/github-review-to-improve-code.html\"\u003epermalink\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/post/github-review-to-improve-code.html\"\u003e11 comments\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/print.php?postid\u003d228\"\u003eprint\u003c/a\u003e | \u003ca href\u003d\"http://postli.com/post?t\u003dGithub+reviews+as+a+way+to+improve+code+quality%3F\u0026amp;u\u003dhttp%3A%2F%2Fantirez.com%2Fpost%2Fgithub-review-to-improve-code.html\"\u003epost it\u003c/a\u003e | \u003ca href\u003d\"http://technorati.com/search/http://antirez.com/post/github-review-to-improve-code.html\"\u003eView blog reactions\u003c/a\u003e\u003c/div\u003e\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/antirez/~4/XYDicZ1Uf0A\" height\u003d\"1\" width\u003d\"1\"\u003e"
3
+ },"likingUsers":[{"userId":"16026351131770821930"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://antirez.com/rss","title":"antirez weblog","htmlUrl":"http://antirez.com"}},{"crawlTimeMsec":"1300227882627","id":"tag:google.com,2005:reader/item/f338b90c09555935","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh","JRuby","News"],"title":"JRuby 1.6 Released: Ruby 1.9.2 Support and More",
4
+ "published":1300227873,"updated":1300269106,"replies":[{"href":"http://www.rubyinside.com/jruby-1-6-released-ruby-1-9-2-support-and-more-4524.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/jruby-1-6-released-ruby-1-9-2-support-and-more-4524.html/feed/atom","type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/UE9Zjkgme2k/jruby-1-6-released-ruby-1-9-2-support-and-more-4524.html","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/03/jruby16.gif\" alt\u003d\"\" title\u003d\"jruby16\" width\u003d\"100\" height\u003d\"100\" style\u003d\"float:left;margin-right:18px;border:1px solid #666\"\u003eIt's a newsflash! \u003cstrong\u003e\u003ca href\u003d\"http://jruby.org/2011/03/15/jruby-1-6-0.html\"\u003eJRuby 1.6.0 has been released\u003c/a\u003e today.\u003c/strong\u003e Congratulations to the \u003ca href\u003d\"http://jruby.org/\"\u003eJRuby\u003c/a\u003e team. 1.6 is a significant and much awaited release and comes after a 9 month push of over 2500 commits.\u003c/p\u003e\n\u003cp\u003eHit up \u003ca href\u003d\"http://jruby.org/2011/03/15/jruby-1-6-0.html\"\u003ethe official release post\u003c/a\u003e for the full run-through but here are some of the highlights of the release:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWindows has been added to the JRuby team's continuous integration system meaning that Windows support is only going to get better\u003c/li\u003e\n\u003cli\u003eRuby 1.9.2 language and API support (with the exception of \u003ccode\u003eEncoding::Converter\u003c/code\u003e and \u003ccode\u003eripper\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003eBuilt in profiler\u003c/li\u003e\n\u003cli\u003eGeneral performance improvements\u003c/li\u003e\n\u003cli\u003eExperimental support for C extensions (with provisos)\u003c/li\u003e\n\u003cli\u003eRSpec is no longer included (worth mentioning in case it catches you out..)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou can \u003ca href\u003d\"http://www.jruby.org/download\"\u003edownload binary and source releases\u003c/a\u003e direct from JRuby.org if you want to get up to date or update \u003ca href\u003d\"http://rvm.beginrescueend.com/\"\u003eRVM\u003c/a\u003e with \u003ccode\u003ervm get head\u003c/code\u003e and \u003ccode\u003ervm reload\u003c/code\u003e before running \u003ccode\u003ervm install jruby-1.6.0\u003c/code\u003e :-)\u003c/p\u003e\n\u003cp\u003eFingers crossed for some great JRuby tutorials and guides coming along in the next couple of months.\u003c/p\u003e\n\u003cp style\u003d\"padding:8px;background-color:#ff9\"\u003e\u003cem\u003e[me!]\u003c/em\u003e My \u003ca href\u003d\"http://rubyweekly.com/\"\u003eRuby Weekly\u003c/a\u003e e-mail newsletter is 7 months old and going great! For the best Ruby news of the week, check it out. However, you might also like \u003ca href\u003d\"http://javascriptweekly.com/\"\u003eJavaScript Weekly\u003c/a\u003e, a newer newsletter of mine dedicated to.. yep, JavaScript, node.js, CoffeeScript, etc. ;-)\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dUE9Zjkgme2k:KR2QfVz96Iw:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dUE9Zjkgme2k:KR2QfVz96Iw:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003dUE9Zjkgme2k:KR2QfVz96Iw:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/UE9Zjkgme2k\" height\u003d\"1\" width\u003d\"1\"\u003e"
5
+ },"author":"Peter Cooper","likingUsers":[{"userId":"12081574251590846293"},{"userId":"07870146926958072805"},{"userId":"15638623306692727372"},{"userId":"08962340293270249083"},{"userId":"13668608591451703896"},{"userId":"18253276447546087020"},{"userId":"18221903143069502680"},{"userId":"12452693818628215717"},{"userId":"03709139029688570344"},{"userId":"05314763687120854629"},{"userId":"08353054291962603524"},{"userId":"01640370281238264014"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/",
6
+ "title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"crawlTimeMsec":"1300123566363","id":"tag:google.com,2005:reader/item/ddb6985d740a7eb7","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh","Miscellaneous"],"title":"A Review of “The RSpec Book” by David Chelimsky","published":1300123508,"updated":1300130667,"replies":[{"href":"http://www.rubyinside.com/a-review-of-the-rspec-book-by-david-chelimsky-4468.html#comments",
7
+ "type":"text/html"},{"href":"http://www.rubyinside.com/a-review-of-the-rspec-book-by-david-chelimsky-4468.html/feed/atom","type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/4axnXaaSX7Y/a-review-of-the-rspec-book-by-david-chelimsky-4468.html","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003e\u003ca href\u003d\"http://www.amazon.com/RSpec-Book-Behaviour-Development-Cucumber/dp/1934356379/?tag\u003drubins-20\"\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/03/the-rspec-book.gif\" alt\u003d\"\" title\u003d\"the-rspec-book\" width\u003d\"230\" height\u003d\"273\" style\u003d\"float:right;margin-left:18px;border:1px solid #666\"\u003e\u003c/a\u003e \u003cstrong\u003e\u003ca href\u003d\"http://www.amazon.com/RSpec-Book-Behaviour-Development-Cucumber/dp/1934356379/?tag\u003drubins-20\"\u003eThe RSpec Book\u003c/a\u003e\u003c/strong\u003e \u003cem\u003e(Amazon.com)\u003c/em\u003e by \u003cstrong\u003eDavid Chelimsky\u003c/strong\u003e (plus a cadre of BDD superstars) is a recent release from \u003ca href\u003d\"http://pragprog.com/\"\u003eThe Pragmatic Programmers\u003c/a\u003e and a handy addition to any TDD-mad or RSpec-using developer's bookshelf. You can buy a copy \u003ca href\u003d\"http://www.amazon.com/RSpec-Book-Behaviour-Development-Cucumber/dp/1934356379/?tag\u003drubins-20\"\u003efrom Amazon.com\u003c/a\u003e, \u003ca href\u003d\"http://www.amazon.co.uk/RSpec-Book-Behaviour-Development-Cucumber/dp/1934356379/?tag\u003dboogsweblog-21\"\u003eAmazon.co.uk\u003c/a\u003e, or \u003ca href\u003d\"http://www.pragprog.com/titles/achbd/the-rspec-book\"\u003edirect from the publisher\u003c/a\u003e (more expensive but a PDF version is available).\u003c/p\u003e\n\u003ch3\u003eWhat is \u003cem\u003eThe RSpec Book\u003c/em\u003e?\u003c/h3\u003e\n\u003cp\u003e\u003cem\u003eThe RSpec Book\u003c/em\u003e is a 400 page book by David Chelimsky (\u003ca href\u003d\"http://rspec.info/\"\u003eRSpec\u003c/a\u003e's primary maintainer), Dave Astels, Zach Dennis, Aslak Hellesøy (of \u003ca href\u003d\"http://cukes.info/\"\u003eCucumber\u003c/a\u003e fame), Bryan Helmkamp and Dan North. \u003cstrong\u003eIts aim is to teach you all about RSpec \u003cem\u003e(RSpec 2.0 - specifically)\u003c/em\u003e and BDD (Behavior Driven Development) from the ground up\u003c/strong\u003e and it promises to \"help you write better code, write better tests, and delver better software to your users.\"\u003c/p\u003e\n\u003cp\u003eRobert C. Martin (a.k.a. \u003cem\u003eUncle Bob\u003c/em\u003e) kicks off the book with a foreword that warns us of what's to come. He says that the book is a \u003cem\u003etrap\u003c/em\u003e and isn't really about RSpec. I won't spoil the whole surprise of his delivery but his general point is that the book is focused on teaching you software \u003cem\u003ecraftsmanship\u003c/em\u003e using BDD (and testing in general) as the framework for putting together well-crafted software. This point is significant because \u003cem\u003eThe RSpec Book\u003c/em\u003e focuses on the \u003cem\u003econcepts\u003c/em\u003e of BDD just as much as it does on the technicalities of RSpec itself.\u003c/p\u003e\n\u003ch3\u003eA Book of Five Parts\u003c/h3\u003e\n\u003cp\u003eThe book starts with an extensive \u003cstrong\u003e\u003cem\u003eGetting Started\u003c/em\u003e\u003c/strong\u003e section headed by a quick chapter summarizing RSpec and Cucumber before moving on to a suite of walkthrough-style chapters dedicated to building a 'code breaker' game. \u003cem\u003eAcceptance Test-Driven Planning\u003c/em\u003e is used which essentially means the acceptance tests are written \u003cem\u003efirst\u003c/em\u003e in the form of Cucumber features so for two chapters you don't get to see any RSpec at all. Once RSpec comes into the mix, though, things move quickly and mocks (doubles) and stubs are introduced quickly. The 'code breaker' game work then continues for a couple of chapters with a brief detour into refactoring.\u003c/p\u003e\n\u003cp\u003eThe second section of the book - \u003cstrong\u003e\u003cem\u003eBehavior Driven Development\u003c/em\u003e\u003c/strong\u003e - is made up of two code-free chapters that look at BDD from a higher level. A lot of this portion is quite opinionated but if you want to get an overall feel for the BDD process and how different concepts interlock with it, it's a great primer.\u003c/p\u003e\n\u003cp\u003eThe third section of the book - \u003cstrong\u003e\u003cem\u003eRSpec\u003c/em\u003e\u003c/strong\u003e - proved to be the real \"meat\" for me. There are several chapters digging solely into the ins and outs of RSpec 2.0 itself. You learn how to use RSpec from the basics up, working through matchers, best practices, mocks, macros, custom formatters, custom matchers, and how the RSpec toolkit can integrate with other tools (such as TextMate). You basically get a 102 page guide to RSpec 2.0 here and that might be worth the price of admission alone.\u003c/p\u003e\n\u003cp\u003eSections dedicated to \u003cstrong\u003e\u003cem\u003eCucumber\u003c/em\u003e\u003c/strong\u003e and \u003cstrong\u003e\u003cem\u003eRails\u003c/em\u003e\u003c/strong\u003e follow on to close the book. I found the Rails section particularly useful having not previously gotten on to the RSpec 2 bandwagon with Rails 3. There are several chapters that each walk through a particular topic, like view specs, controller specs, and model specs. I didn't want to digest the entire set at once and the structure helped me just dig into the parts I was immediately interested in without following each chapter in order. The large number of short and sweet code examples also helps if you're just scanning through looking for some guidance.\u003c/p\u003e\n\u003cp\u003eGiven the significance of \u003cem\u003eThe RSpec Book\u003c/em\u003e and its time already spent out \"in the wild\" I asked my Twitter followers for some second opinions to round things off:\u003c/p\u003e\n\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/03/tw1.png\" alt\u003d\"\" title\u003d\"tw1\" width\u003d\"638\" height\u003d\"140\"\u003e\u003cbr\u003e\n\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/03/tw2.png\" alt\u003d\"\" title\u003d\"tw2\" width\u003d\"634\" height\u003d\"137\"\u003e\u003cbr\u003e\n\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/03/tw3.png\" alt\u003d\"\" title\u003d\"tw3\" width\u003d\"636\" height\u003d\"140\"\u003e\u003c/p\u003e\n\u003cp\u003eIn short, I recommend \u003cem\u003eThe RSpec Book.\u003c/em\u003e The \u003ca href\u003d\"http://www.amazon.com/RSpec-Book-Behaviour-Development-Cucumber/dp/1934356379/?tag\u003drubins-20\"\u003ereviews on Amazon\u003c/a\u003e seem to be rather mixed so you might want to check them out to get the bigger picture, but I've found the book to be rather useful with its direct narrative style, logical structure, and vast number of short code examples from which to descry some handy techniques.\u003c/p\u003e\n\u003ch3\u003eWho Should Buy It?\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eRubyists who want to get up to speed with RSpec 2.0 and, to a lesser extent, Cucumber\u003c/li\u003e\n\u003cli\u003eRails 3.0 developers who want to learn how to do model, controller, view and integration testing with RSpec 2.0\u003c/li\u003e\n\u003cli\u003eAnyone with a high-level interest in BDD, even if they're not Ruby developers.\u003c/li\u003e\n\u003cli\u003eAnyone who thinks a small press like \u003cem\u003eThe Pragmatic Programmers\u003c/em\u003e is worth supporting (yes!)\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3\u003eWho Shouldn't Buy It?\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eAnyone expecting a wide overview of TDD and BDD toolsets and libraries. For example, I didn't find any references to Capybara, though Webrat is mentioned.\u003c/li\u003e\n\u003cli\u003eAny existing BDD, RSpec 2 and Cucumber gurus.\u003c/li\u003e\n\u003cli\u003eDie-hard Test::Unit users.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3\u003eThe Chapters\u003c/h3\u003e\n\u003cp\u003eHere's an overview of the Table of Contents to give you a feel for what's covered:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGetting Started with RSpec and Cucumber\n\u003cul\u003e\n\u003cli\u003eIntroduction\u003c/li\u003e\n\u003cli\u003eHello (and Installation)\u003c/li\u003e\n\u003cli\u003eDescribing Features\u003c/li\u003e\n\u003cli\u003eAutomating Features with Cucumber\u003c/li\u003e\n\u003cli\u003eDescribing Code with RSpec\u003c/li\u003e\n\u003cli\u003eAdding New Features\u003c/li\u003e\n\u003cli\u003eSpecifying an Algorithm\u003c/li\u003e\n\u003cli\u003eRefactoring with Confidence\u003c/li\u003e\n\u003cli\u003eFeeding Back What We’ve Learned\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eBehaviour-Driven Development\n\u003cul\u003e\n\u003cli\u003eThe Case for BDD\u003c/li\u003e\n\u003cli\u003eWriting Software That Matters\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eRSpec\n\u003cul\u003e\n\u003cli\u003eCode Examples\u003c/li\u003e\n\u003cli\u003eRSpec::Expectations\u003c/li\u003e\n\u003cli\u003eRSpec::Mocks\u003c/li\u003e\n\u003cli\u003eTools And Integration\u003c/li\u003e\n\u003cli\u003eExtending RSpec\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eCucumber\n\u003cul\u003e\n\u003cli\u003eIntro to Cucumber\u003c/li\u003e\n\u003cli\u003eCucumber Detail\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eBehaviour-Driven Rails\n\u003cul\u003e\n\u003cli\u003eBDD in Rails\u003c/li\u003e\n\u003cli\u003eCucumber with Rails\u003c/li\u003e\n\u003cli\u003eSimulating the Browser with Webrat\u003c/li\u003e\n\u003cli\u003eAutomating the Browser with Webrat and Selenium\u003c/li\u003e\n\u003cli\u003eRails Views\u003c/li\u003e\n\u003cli\u003eRails Controllers\u003c/li\u003e\n\u003cli\u003eRails Models\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3\u003eHow to Get \u003cem\u003eThe RSpec Book\u003c/em\u003e\u003c/h3\u003e\n\u003cp\u003eIf you want a print copy (no Kindle - sorry!), head to \u003ca href\u003d\"http://www.amazon.com/RSpec-Book-Behaviour-Development-Cucumber/dp/1934356379/?tag\u003drubins-20\"\u003eAmazon.com\u003c/a\u003e, \u003ca href\u003d\"http://www.amazon.co.uk/RSpec-Book-Behaviour-Development-Cucumber/dp/1934356379/?tag\u003dboogsweblog-21\"\u003eAmazon.co.uk\u003c/a\u003e, or your other favorite book retailer. If a PDF, EPUB or Mobi (Kindle) file is more to your taste, \u003ca href\u003d\"http://www.pragprog.com/titles/achbd/the-rspec-book\"\u003ethe publisher\u003c/a\u003e has those for sale directly - super dooplebloops!\u003c/p\u003e\n\u003cp style\u003d\"padding:8px;background-color:#ff9\"\u003e\u003cem\u003e[ad]\u003c/em\u003e \u003ca href\u003d\"http://engine.adzerk.net/redirect/0/3179/3005/1313/3784b5abc6554c0d8e4a05abd6b7472a/19/24/3543/634356797303506203\"\u003eRed Dirt RubyConf\u003c/a\u003e is a hip and happening Ruby conference taking place in Oklahoma City on April 21-22, 2011. Speakers include Aaron 'tenderlove' Patterson, Wayne Seguin, Jeremy McAnally, and Dr Nic Williams (and \u003ca href\u003d\"http://reddirtrubyconf.com/schedule\"\u003eMANY more\u003c/a\u003e great folks).\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003d4axnXaaSX7Y:ENyIXlhKdqw:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003d4axnXaaSX7Y:ENyIXlhKdqw:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003d4axnXaaSX7Y:ENyIXlhKdqw:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/4axnXaaSX7Y\" height\u003d\"1\" width\u003d\"1\"\u003e"
8
+ },"author":"Peter Cooper","likingUsers":[{"userId":"08962340293270249083"},{"userId":"18253276447546087020"},{"userId":"07627850390712298043"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/","title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"crawlTimeMsec":"1298989876327","id":"tag:google.com,2005:reader/item/b3bf555a13520ef2","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list",
9
+ "user/05185502537486227907/state/com.google/fresh"],"title":"Redis Manifesto","published":1298988965,"updated":1298988965,"alternate":[{"href":"http://feedproxy.google.com/~r/antirez/~3/s5SFfrptDok/redis-manifesto.html","type":"text/html"}],"summary":{"direction":"ltr","content":"\u003cdiv\u003e\u003cdiv style\u003d\"clear:both\"\u003e\u003c/div\u003e\u003cdiv\u003eMany times I don't know what to exactly reply to feature requests, or questions about why things in Redis are done in a specific way. Most of the time the questions make a lot of sense, there is not just a way to make things in programming, a lot is about your taste, feeling, and ideas about how software should be written. So I tried to condense my feelings about Redis and software development in general in this short manifest, that I'll include in the Redis distribution. There are an infinite number of ways of doing things, this is just the one I and a good part of the Redis community like.\n\u003cbr\u003e\u003cbr\u003e\n\n\u003ch3\u003eRedis Manifesto\u003c/h3\u003e\n\u003cul\u003e\u003cli\u003e1 - A DSL for Abstract Data Types. Redis is a DSL (Domain Specific Language) that manipulates abstract data types and implemented as a TCP daemon. Commands manipulate a key space where keys are binary-safe strings and values are different kinds of abstract data types. Every data type represents an abstract version of a fundamental data structure. For instance Redis Lists are an abstract representation of linked lists. In Redis, the essence of a data type isn't just the kind of operations that the data types support, but also the space and time complexity of the data type and the operations performed upon it.\u003c/li\u003e\n\n\u003cli\u003e2 - Memory storage is #1. The Redis data set, composed of defined key-value pairs, is primarily stored in the computer's memory. The amount of memory in all kinds of computers, including entry-level servers, is increasing significantly each year. Memory is fast, and allows Redis to have very predictable performance. Datasets composed of 10k or 40 millions keys will perform similarly. Complex data types like Redis Sorted Sets are easy to implement and manipulate in memory with good performance, making Redis very simple. Redis will continue to explore alternative options (where data can be optionally stored on disk, say) but the main goal of the project remains the development of an in-memory database.\u003c/li\u003e\n\n\u003cli\u003e3 - Fundamental data structures for a fundamental API. The Redis API is a direct consequence of fundamental data structures. APIs can often be arbitrary but not an API that resembles the nature of fundamental data structures. If we ever meet intelligent life forms from another part of the universe, they'll likely know, understand and recognize the same basic data structures we have in our computer science books. Redis will avoid intermediate layers in API, so that the complexity is obvious and more complex operations can be performed as the sum of the basic operations.\u003c/li\u003e\n\n\u003cli\u003e4 - Code is like a poem; it\u0026#39;s not just something we write to reach some practical result. Sometimes people that are far from the Redis philosophy suggest using other code written by other authors (frequently in other languages) in order to implement something Redis currently lacks. But to us this is like if Shakespeare decided to end Enrico IV using the Paradiso from the Divina Commedia. Is using any external code a bad idea? Not at all. Like in \u0026quot;One Thousand and One Nights\u0026quot; smaller self contained stories are embedded in a bigger story, we\u0026#39;ll be happy to use beautiful self contained libraries when needed. At the same time, when writing the Redis story we\u0026#39;re trying to write smaller stories that will fit in to other code.\u003c/li\u003e\n\n\u003cli\u003e5 - We're against complexity. We believe designing systems is a fight against complexity. We'll accept to fight the complexity when it's worthwhile but we'll try hard to recognize when a small feature is not worth 1000s of lines of code. Most of the time the best way to fight complexity is by not creating it at all.\u003c/li\u003e\n\n\u003cli\u003e6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits naturally into a distributed version of Redis and 2) a more complex API that supports multi-key operations. Both are useful if used judiciously but there's no way to make the more complex multi-keys API distributed in an opaque way without violating our other principles. We don't want to provide the illusion of something that will work magically when actually it can't in all cases. Instead we'll provide commands to quickly migrate keys from one instance to another to perform multi-key operations and expose the tradeoffs to the user.\u003c/li\u003e\n\n\u003cli\u003e7 - We optimize for joy. We believe writing code is a lot of hard work, and the only way it can be worth is by enjoying it. When there is no longer joy in writing code, the best thing to do is stop. To prevent this, we'll avoid taking paths that will make Redis less of a joy to develop.\u003c/li\u003e\n\n\u003c/ul\u003e\nThanks to Peter Cooper for reading the draft and helping to make it better.\u003c/div\u003e\u003cdiv\u003e\u003cdiv\u003epost read 23650 times\u003csup\u003e\u003ca href\u003d\"http://antirez.com/page/uniquevisitors\"\u003e*\u003c/a\u003e\u003c/sup\u003e (average 1368.0 visits/day)\u003c/div\u003ePosted at 14:16:05 \u003ca href\u003d\"http://antirez.com/post/redis-manifesto.html\"\u003epermalink\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/post/redis-manifesto.html\"\u003e23 comments\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/print.php?postid\u003d227\"\u003eprint\u003c/a\u003e | \u003ca href\u003d\"http://postli.com/post?t\u003dRedis+Manifesto\u0026amp;u\u003dhttp%3A%2F%2Fantirez.com%2Fpost%2Fredis-manifesto.html\"\u003epost it\u003c/a\u003e | \u003ca href\u003d\"http://technorati.com/search/http://antirez.com/post/redis-manifesto.html\"\u003eView blog reactions\u003c/a\u003e\u003c/div\u003e\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/antirez/~4/s5SFfrptDok\" height\u003d\"1\" width\u003d\"1\"\u003e"
10
+ },"likingUsers":[{"userId":"11986012805061343048"},{"userId":"05068451015565066133"},{"userId":"03544891211984531148"},{"userId":"06244526826240331725"},{"userId":"05275469297961036779"},{"userId":"06808395769643365537"},{"userId":"02714205789091616240"},{"userId":"01002900068580443852"},{"userId":"02800178579410032884"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://antirez.com/rss","title":"antirez weblog","htmlUrl":"http://antirez.com"}},{"crawlTimeMsec":"1298869132147","id":"tag:google.com,2005:reader/item/829a35be3c2e04f9",
11
+ "categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh","Miscellaneous"],"title":"16 Ruby and Rails Jobs for February 2011 (including UK and Australia)","published":1298869038,"updated":1298869038,"replies":[{"href":"http://www.rubyinside.com/16-ruby-and-rails-jobs-for-february-2011-including-uk-and-australia-4450.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/16-ruby-and-rails-jobs-for-february-2011-including-uk-and-australia-4450.html/feed/atom",
12
+ "type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/R3f_yH9lRbc/16-ruby-and-rails-jobs-for-february-2011-including-uk-and-australia-4450.html","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/jobsjobspoobumwilly.jpg\" alt\u003d\"\" title\u003d\"jobsjobspoobumwilly\" width\u003d\"100\" height\u003d\"101\" style\u003d\"float:right;margin-left:18px;border:1px solid #555\"\u003eIt's been a killer month for new Ruby and Rails jobs over at the \u003ca href\u003d\"http://jobs.rubyinside.com/\"\u003eRuby Inside Jobs board\u003c/a\u003e so I'm going to cut the filler to a minimum today.. though if you want to learn more about posting one of your own, \u003ca href\u003d\"http://www.rubyinside.com/post-a-job\"\u003echeck our Post A Job page.\u003c/a\u003e The current bonus is you'll get your job ad into the 4000+ subscriber-strong \u003ca href\u003d\"http://rubyweekly.com/\"\u003eRuby Weekly\u003c/a\u003e for free!\u003c/p\u003e\n\u003cp\u003eThere are a couple of really appealing jobs in here (particularly the first one) and they span the US with a few in the United Kingdom and one in Australia for good measure:\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eInnovation Developer - \u003c/span\u003eSan Francisco, California\u003c/h3\u003e\n\u003cp\u003esalesforce.com, inc. is \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/448192\"\u003elooking for a Innovation Developer\u003c/a\u003e - the sort of developer who reads HN and TechCrunch, who\u0026#39;s played with tools like Raphael and CoffeeScript, and who wants to work on interesting prototypes with the latest tools. This sounds like a great position! It\u0026#39;s in downtown SF and you can even get a $100/mo fitness reimbursement — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/448192\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eGreat Ruby Developer Needed - \u003c/span\u003eLondon, UK\u003c/h3\u003e\n\u003cp\u003eEconsultancy is an award-winning online publisher based in London and New York. They're \u003ca href\u003d\"http://econsultancy.com/jobs/great-ruby-developer-2\"\u003elooking for a developer\u003c/a\u003e to work at their London office with general experience of Ruby, Rails and Unix/Linux - salary is up to 60k — \u003ca href\u003d\"http://econsultancy.com/jobs/great-ruby-developer-2\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby on Rails Developer - \u003c/span\u003ePasadena, California\u003c/h3\u003e\n\u003cp\u003eGoldstar is a ticket and entertainment company \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/421372\"\u003elooking for a full-time Rails developer\u003c/a\u003e to join a geographically-dispersed, test-happy, and pair-friendly team of developers building services for 1.2 million members across the US. This job does not demand you be based near Pasadena, CA (though that\u0026#39;s ideal) but at least one visit per year to HQ is required — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/421372\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby/Rails Engineers - \u003c/span\u003eEdinburgh \u0026amp; Cambridge, UK\u003c/h3\u003e\n\u003cp\u003eFreeAgent is a fast growing and hugely popular UK based startup obsessed with building fantastic online accounting software. They're a small team of smart people \u003ca href\u003d\"http://www.freeagentcentral.com/company/jobs\"\u003elooking to hire productive Ruby and Rails developers\u003c/a\u003e in Edinburgh or Cambridge. You\u0026#39;ll be working on adding and maintaining features for our existing applications and making sure their software scales up — \u003ca href\u003d\"http://www.freeagentcentral.com/company/jobs\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby on Rails Developer - \u003c/span\u003eBoston, Massachusetts\u003c/h3\u003e\n\u003cp\u003eThe District Management Council is \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/421573\"\u003elooking for an experienced Web developer\u003c/a\u003e with 2+ years of Rails and JavaScript experience plus MySQL, HTML, and CSS proficiency to work on their Web applications helping to dramatically improve public education in America today — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/421573\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby/Java Web Developers - \u003c/span\u003eArlington, Virginia\u003c/h3\u003e\n\u003cp\u003eHealthcentral is an online health information site looking for, ideally, \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/421583\"\u003ea developer experienced with both Java and Ruby related technologies.\u003c/a\u003e A BS/MS in Computer Science is preferred, along with 2-4 years of industry experience preferably in startup environments — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/421583\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby Developer - \u003c/span\u003ePalo Alto, California\u003c/h3\u003e\n\u003cp\u003eWildfire Interactive is a rapidly expanding, VC-funded tech startup in the social media marketing space. They're using Rails and Sinatra and building many 'pure Ruby' components so \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/429666\"\u003ethey want Ruby developers with full-stack experience.\u003c/a\u003e Oh, and testing is a must — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/429666\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby Developer - \u003c/span\u003eLewisville, Texas\u003c/h3\u003e\n\u003cp\u003eGeoforce Inc provides asset visibility to the oil and gas industry through wireless devices feeding data to a Rails-powered SaaS app. They \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/429705\"\u003ewant a developer with 3 years of Rails experience\u003c/a\u003e, as well as Postgresql, HTML, and JavaScript experience generally — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/429705\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eGraduate Rails Developer - \u003c/span\u003eLondon, UK\u003c/h3\u003e\n\u003cp\u003eAlphaSights are \u003ca href\u003d\"http://alphasights.com/graduate_rails_job\"\u003elooking for a Computer Science graduate (or equivalent)\u003c/a\u003e with a passion for Ruby and Rails to work in a small team at their Covent Garden offices. No commercial Rails experience is necessary and a highly competitive salary is offered — \u003ca href\u003d\"http://alphasights.com/graduate_rails_job\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRails Developer - \u003c/span\u003eCleveland, Ohio\u003c/h3\u003e\n\u003cp\u003eWithin3 is a professional networking site for the top institutions and physicians in the healthcare industry \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/430136\"\u003elooking for a Rails developer.\u003c/a\u003e At least 3-5 years of Ruby experience is needed as well as thorough knowledge of front-end technologies (XHTML, CSS, JavaScript) — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/430136\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eSenior Ruby (Rails/Sinatra) Developer - \u003c/span\u003eSanta Monica, California\u003c/h3\u003e\n\u003cp\u003eTRUECar, Inc. is \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/437410\"\u003elooking for a Ruby developer with Sinatra experience\u003c/a\u003e to work on improving their car price research service. Your TDD and Agile-fu needs to be strong. A highly competitive salary, 100% medical, and equity are offered — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/437410\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eSr. Ruby Developer - \u003c/span\u003eCoronado, California\u003c/h3\u003e\n\u003cp\u003eStockTwits \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/439720\"\u003ewants a senior-level Ruby developer\u003c/a\u003e with Ruby, Rails (with HAML), TDD and HTML5 experience. A Bachelor\u0026#39;s degree or better in Computer Science or a related technical discipline is required — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/439720\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby on Rails Developer - \u003c/span\u003eChicago, Illinois\u003c/h3\u003e\n\u003cp\u003eObtiva is \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/442820\"\u003elooking for developers\u003c/a\u003e for its Chicago and Denver locations - no \u0026#39;rock stars\u0026#39; or \u0026#39;ninjas\u0026#39; but passionate, friendly team players with Ruby, Rails, and Agile experience — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/442820\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eWeb Developer - \u003c/span\u003eSan Francisco, California\u003c/h3\u003e\n\u003cp\u003eDo you have a passion for design as well as code? \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/444317\"\u003eHybrid Design wants you!\u003c/a\u003e They\u0026#39;re working for clients like Apple, Nike, and TED and need a full-time Web developer with both Ruby and design chops — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/444317\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby Developer - \u003c/span\u003eEvanston, Illinois\u003c/h3\u003e\n\u003cp\u003eCelect builds member-management systems for organizations like churches and fraternities. They \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/444473\"\u003eneed a full-time Ruby developer\u003c/a\u003e for their engineering team who has experience with RSpec, Passenger, and PostgreSQL. The job is on-site in Evanston, IL — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/444473\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby on Rails Software Engineer - \u003c/span\u003eSterling, Virginia\u003c/h3\u003e\n\u003cp\u003eGrab Networks is \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/444676\"\u003elooking for analytical individuals with strong computational skills\u003c/a\u003e to work on multiple levels of a complex technology stack. You\u0026#39;ll need 5-6 years\u0026#39; experience developing dynamic, data-driven, commercial web applications — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/444676\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby/JRuby Tools Engineer - \u003c/span\u003eSan Mateo, California\u003c/h3\u003e\n\u003cp\u003eWhere can you work on challenging engineering problems while saving the planet? At eMeter! \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/447029\"\u003eeMeter is seeking an Java/Ruby/JRuby Automation/Tools Engineer\u003c/a\u003e to drive the development of automation tools — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/447029\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby Developer - \u003c/span\u003eMelbourne, Australia\u003c/h3\u003e\n\u003cp\u003eTrikeApps is \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/450042\"\u003elooking for a Ruby Developer\u003c/a\u003e to work in their Melbourne, Australia office. Assistance with relocation and visa sponsorship is available for the right candidate wanting to move to Australia. Two years of experience developing webapps is required — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/450042\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003ch3\u003e\u003cspan style\u003d\"color:#999\"\u003eRuby on Rails Software Engineer - \u003c/span\u003eSan Francisco, California\u003c/h3\u003e\n\u003cp\u003eAKQA (one of the world's most influential creative and technology companies) is \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/453798\"\u003elooking for a Ruby on Rails Software Engineer\u003c/a\u003e with 3 years\u0026#39; experience of Rails and familiarity with Unix/Linux — \u003ca href\u003d\"http://jobs.rubyinside.com/a/jbb/job-details/453798\"\u003eclick here to learn more.\u003c/a\u003e\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dR3f_yH9lRbc:fWZd9A7Ejqc:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dR3f_yH9lRbc:fWZd9A7Ejqc:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003dR3f_yH9lRbc:fWZd9A7Ejqc:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/R3f_yH9lRbc\" height\u003d\"1\" width\u003d\"1\"\u003e"
13
+ },"author":"Peter Cooper","likingUsers":[{"userId":"08367584023452666416"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/","title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"crawlTimeMsec":"1298780661449","id":"tag:google.com,2005:reader/item/1025caaadfbe5397","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh","Miscellaneous"
14
+ ],"title":"A Review of “Eloquent Ruby” by Russ Olsen – It Rocks!","published":1298780620,"updated":1300120220,"replies":[{"href":"http://www.rubyinside.com/a-review-of-eloquent-ruby-by-russ-olsen-it-rocks-4432.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/a-review-of-eloquent-ruby-by-russ-olsen-it-rocks-4432.html/feed/atom","type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/NDAVVkLCFsI/a-review-of-eloquent-ruby-by-russ-olsen-it-rocks-4432.html",
15
+ "type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003e\u003ca href\u003d\"http://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104/?tag\u003drubins-20\"\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/eloquent-ruby-229x300.jpg\" alt\u003d\"\" title\u003d\"eloquent-ruby\" width\u003d\"229\" height\u003d\"300\" style\u003d\"float:right;margin-left:18px;border:1px solid #666\"\u003e\u003c/a\u003e\u003cstrong\u003e\u003ca href\u003d\"http://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104/?tag\u003drubins-20\"\u003eEloquent Ruby\u003c/a\u003e\u003c/strong\u003e \u003cem\u003e(Amazon.com - print \u0026amp; Kindle)\u003c/em\u003e \u003cstrong\u003eby Russ Olsen is the first Ruby book I've read \u003cem\u003ein its entirety\u003c/em\u003e within 24 hours; it's that good.\u003c/strong\u003e That may be all you need to know before you \u003ca href\u003d\"http://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104/?tag\u003drubins-20\"\u003ebuy a copy at Amazon.com\u003c/a\u003e, \u003ca href\u003d\"http://www.amazon.co.uk/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104/?tag\u003dboogsweblog-21\"\u003eAmazon.co.uk\u003c/a\u003e or read it \u003ca href\u003d\"http://my.safaribooksonline.com/book/web-development/ruby/9780321700308\"\u003eon Safari\u003c/a\u003e (if you have an account). If you want to learn more though, keep reading.\u003c/p\u003e\n\u003ch3\u003eWhat Is \"Eloquent Ruby\"?\u003c/h3\u003e\n\u003cp\u003e\u003cem\u003eEloquent Ruby\u003c/em\u003e is a book published by Addison Wesley and written by Russ Olsen (who also wrote \u003ca href\u003d\"http://www.rubyinside.com/design-patterns-in-ruby-by-russ-olsen-695.html\"\u003eDesign Patterns in Ruby\u003c/a\u003e a few years ago). It clocks in at around 400 pages and has 31 chapters clocking in at around a punchy 10 pages each. Each chapter is titled as a guideline you should follow to write \"eloquent\" Ruby - things like \u003cem\u003eCreate Classes That Understand Equality\u003c/em\u003e and \u003cem\u003eWrite Code That Looks Like Ruby\u003c/em\u003e - and typically the claim is explained, some code examples shown and discussed, some real world examples pointed to, and that's it.\u003c/p\u003e\n\u003cp\u003eAs with \u003cem\u003eDesign Patterns in Ruby\u003c/em\u003e, Russ adopts a chatty, familiar tone. Reading this book is like reading a book specifically written for you by a friend. He doesn't shoot off on many unnecessary tangents and he keeps the stories short and sweet but this book certainly couldn't be called \u003cem\u003edry\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eThe book is also notably short of egregious errors or omissions. Even when I don't read something with a fine-toothed comb on standby, I can usually pick out a laundry list of factual and grammatical errors or omissions (as both Obie Fernandez and my wife will attest) but \u003cem\u003eEloquent Ruby\u003c/em\u003e gave me little to chew on. I can only bring to mind a few spacing and formatting issues and only one true \"error\": a \u003ccode\u003e\u0026gt;\u003c/code\u003e instead of a \u003ccode\u003e\u0026lt;\u003c/code\u003e in a class definition on a single example.\u003c/p\u003e\n\u003cp\u003eRuss tries to remain neutral with his choice of Ruby implementations but the book seems to focus primarily on Ruby 1.9 (Ruby 1.9.1 specifically but that's just due to when he wrote it) while providing useful footnotes in the cases where there are differences to Ruby 1.8. No matter what Ruby implementation you're using, there's little to confuse you as most of it is very non-implementation and non-version specific.\u003c/p\u003e\n\u003cp\u003eI wholeheartedly recommend this book to anyone except those who, well, could have written a similar book themselves. The short punchy chapters make it a delight to read and gives the option of reading it merely 10 minutes at a time before bed or similar. The short chapters also make it useful as a reference if you forget how to do a certain thing like, say, use \u003cem\u003emethod_missing\u003c/em\u003e, even though it's not put together as a reference book at all. Lastly, this book is a \u003cem\u003emust read\u003c/em\u003e if you're not confident with Ruby idioms and the best way to structure and lay out your code - Russ's approaches reinforce the current \"standard\" way to write Ruby and this alone is worth the price of admission.\u003c/p\u003e\n\u003ch3\u003eWho Should Buy It?\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eAny Ruby developer who doesn't yet feel like they're at guru level (that's most of us!)\u003c/li\u003e\n\u003cli\u003eAnyone who wants to get a feel for the typically undocumented style, syntax, and structural standards of top Ruby developers.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3\u003eWho Shouldn't Buy It?\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eAnyone without a sense of humor or who doesn't like a chatty, familiar type of writing.\u003c/li\u003e\n\u003cli\u003eMatz, Dave Thomas, Chad Fowler, Russ Olsen himself, and a few others.\u003c/li\u003e\n\u003cli\u003eAnyone who's resistant to change and wants to keep coding Ruby \"their way.\"\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3\u003eThe Chapters\u003c/h3\u003e\n\u003cp\u003eThe chapter titles in \u003cem\u003eEloquent Ruby\u003c/em\u003e are useful enough to give you an indication of the level(s) it aims at and whether it would be interesting to you, so here goes:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eWrite Code That Looks Like Ruby\u003c/li\u003e\n\u003cli\u003eChoose The Right Control Structure\u003c/li\u003e\n\u003cli\u003eTake Advantage Of Ruby's Smart Collections\u003c/li\u003e\n\u003cli\u003eTake Advantage Of Ruby's Smart Strings\u003c/li\u003e\n\u003cli\u003eFind The Right String With Regular Expressions\u003c/li\u003e\n\u003cli\u003eUse Symbols To Stand For Something\u003c/li\u003e\n\u003cli\u003eTreat Everything Like An Object - Because It Is\u003c/li\u003e\n\u003cli\u003eEmbrace Dynamic Typing\u003c/li\u003e\n\u003cli\u003eWrite Specs!\u003c/li\u003e\n\u003cli\u003eConstruct Your Classes From Short, Focused Methods\u003c/li\u003e\n\u003cli\u003eDefine Operators Respectfully\u003c/li\u003e\n\u003cli\u003eCreate Classes That Understand Equality\u003c/li\u003e\n\u003cli\u003eGet The Behavior You Need With Singleton And Class Methods\u003c/li\u003e\n\u003cli\u003eUse Class Instance Variables\u003c/li\u003e\n\u003cli\u003eUse Modules As Name spaces\u003c/li\u003e\n\u003cli\u003eUse Modules As Mixins\u003c/li\u003e\n\u003cli\u003eUse Blocks To Iterate\u003c/li\u003e\n\u003cli\u003eExecute Around With A Block\u003c/li\u003e\n\u003cli\u003eSave Blocks To Execute Later\u003c/li\u003e\n\u003cli\u003eUse Hooks To Keep Your Program Informed\u003c/li\u003e\n\u003cli\u003eUse method_missing For Flexible Error Handling\u003c/li\u003e\n\u003cli\u003eUse method_missing For Delegation\u003c/li\u003e\n\u003cli\u003eUse method_missing To Build Flexible APIs\u003c/li\u003e\n\u003cli\u003eUpdate Existing Classes With Monkey Patching\u003c/li\u003e\n\u003cli\u003eCreate Self Modifying Classes\u003c/li\u003e\n\u003cli\u003eCreate Classes That Modify Their Subclasses\u003c/li\u003e\n\u003cli\u003eInvent Internal DSLs\u003c/li\u003e\n\u003cli\u003eBuild External DSLs For Flexible Syntax\u003c/li\u003e\n\u003cli\u003ePackage Your Programs As Gems\u003c/li\u003e\n\u003cli\u003eKnow Your Ruby Implementation\u003c/li\u003e\n\u003cli\u003eKeep An Open Mind To Go With Those Open Classes\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThere's something for everyone and it gets progressively more advanced.\u003c/p\u003e\n\u003ch3\u003eHow to Get Eloquent Ruby\u003c/h3\u003e\n\u003cp\u003eIf you want a print or Kindle copy, head to \u003ca href\u003d\"http://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104/?tag\u003drubins-20\"\u003eAmazon.com\u003c/a\u003e, \u003ca href\u003d\"http://www.amazon.co.uk/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104/?tag\u003dboogsweblog-21\"\u003eAmazon.co.uk\u003c/a\u003e, or your other favorite book retailer. If a PDF or EPUB file is more to your taste, \u003ca href\u003d\"http://www.informit.com/store/product.aspx?isbn\u003d0321768337\"\u003eInformIT\u003c/a\u003e has those for sale (currently for $28.79).\u003c/p\u003e\n\u003ch3\u003eKindle Preview\u003c/h3\u003e\n\u003cdiv\u003e\u003c/div\u003e\n\u003cp\u003e\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dNDAVVkLCFsI:_P31bo7Ju6g:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dNDAVVkLCFsI:_P31bo7Ju6g:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003dNDAVVkLCFsI:_P31bo7Ju6g:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/NDAVVkLCFsI\" height\u003d\"1\" width\u003d\"1\"\u003e"
16
+ },"author":"Peter Cooper","likingUsers":[{"userId":"05332715160519219400"},{"userId":"09885554720706931232"},{"userId":"01585781993341433047"},{"userId":"08962340293270249083"},{"userId":"00469574939651249697"},{"userId":"12129945746987380079"},{"userId":"18253276447546087020"},{"userId":"12175421065371833333"},{"userId":"02312042911823059098"},{"userId":"11496849258748853027"},{"userId":"02582356324518336815"},{"userId":"06808395769643365537"},{"userId":"13399401669980563715"},{"userId":"16216928680461065086"
17
+ }],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/","title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"crawlTimeMsec":"1298651474775","id":"tag:google.com,2005:reader/item/234a40b1dd553a79","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh"],"title":"Redis Presharding","published":1298650597,"updated":1298650597,"alternate":[
18
+ {"href":"http://feedproxy.google.com/~r/antirez/~3/hMt2BFuCsJc/redis-presharding.html","type":"text/html"}],"summary":{"direction":"ltr","content":"\u003cdiv\u003e\u003cdiv style\u003d\"clear:both\"\u003e\u003c/div\u003e\u003cdiv\u003eRedis cluster is currently under development, and we hope it will be able to solve the problem of partitioning data among different Redis instances in a transparent, fast, and fault tolerant way. The cluster project is currently not ready: even if we have a pretty clear design, and many networking level code for gossip and failure detection, it will take some more month to be released, and more time to be released into a stable release.\n\u003cbr\u003e\u003cbr\u003e\n\nWhile our work continue, people shard regardless of Redis cluster, using their own algorithms and systems. This post describes a strategy for partitioning your data among N instances that is very simple but that works great. We\u0026#39;ll call this strategy \u0026quot;Redis Presharding\u0026quot;.\n\u003ch2\u003eRedis is lightweight\u003c/h2\u003e\nRedis is a very small program, currently consisting of just 30k lines of code.\nThe only dependency is the libc. We use our libraries for everything, with just the minimal code and functionality needed to get the work done.\nThis provides us with a very neat advantage: our memory footprint for a spare instance is minimal. Every Redis instance running consumes little more than one megabyte of RAM. This means that having 32, 64, or 128 instances of Redis all running against the same Linux box does not pose any problem.\n\u003cbr\u003e\u003cbr\u003e\n\nThis is very important for our use case. Let's see why.\n\u003cbr\u003e\u003cbr\u003e\n\nSimple partitioning algorithms are cool because they are, well, simple.\nYou get the key, hash it, and get K bits of the hash (or if you prefer\nperform a modulo operation). So you can map every given key to\nN different Redis nodes.\n\u003cpre\u003e\nNode \u003d Hash(key) MOD N\n\u003c/pre\u003e\nThe limit with this kind of partitioning is that once you need to add or remove nodes from the system to adjust the capacity, it is a real mess. Hashing is easy, rehashing is hard, and involves moving keys form instances to other instances while the system is running. If you tried to design a system like that, you know what I'm talking about. Redis cluster will be able to do things like that, and if you check the design you'll discover is more complex than Hash MOD N :)\n\u003cbr\u003e\u003cbr\u003e\n\nBut wait, maybe we can mount a poor man's cluster. Since the Redis instances are so light to run, what about if we start considering we'll need a lot of capacity? So we start, from day zero, 128 different Redis Instances, using just two not too powerful virtual machines on EC2 (this is just an example, you can do your math to understand how much you want to grow before of changing design).\n\u003cbr\u003e\u003cbr\u003e\n\nOf course your 128 instances will use a small amount of memory each. It is important that you use this design with objects that are more or less all of the same size, and that your hash functions does not have trivial biases (in other words, use SHA1). If your application contains things like long lists or alike, you can use a specialized Redis instance for this data structures.\n\u003ch2\u003eHandling many instances\u003c/h2\u003e\nHandling 128 instances is not like handling a single one, so it is a good idea to write scripts to start all the instances, to stop all the instances, to collect all the .rdb or AOF files to create a single tar.gz with everything that you can use as a backup. It's a good idea to also have a script that takes this tar.gz as input and restore all the dumps into the different Redis instances.\n\u003cbr\u003e\u003cbr\u003e\n\nAlso some monitoring will not hurt at all.\n\u003cbr\u003e\u003cbr\u003e\n\nBasically this set of tools could be a nice open source project, if done well, simply, and the spirit of Redis, and possibly coded in Ruby ;) Ok let's avoid language flame wars...\n\u003cbr\u003e\u003cbr\u003e\n\nThe bottom line is: be prepared to handle hundred of instances.\n\u003cbr\u003e\u003cbr\u003e\n\nBut running many instances also have some neat advantage.\nFor instance, do you want to rewrite the AOF file? Do this one instance after the other, and the memory hint will be small. The same applies to saving with the normal .rdb persistence.\n\u003cbr\u003e\u003cbr\u003e\n\nAlso you are doing a very neat thing: you are working at scale from the start, even if you are still small. This means to be prepared to run a much larger site from day zero. Not a bad idea if you ask me.\n\u003ch2\u003eMoving instances\u003c/h2\u003e\nNow the interesting part is, I need to scale. My 128 instances are using all the memory and resources in my small virtual machines. What to do?\nIt is pretty easy, just fire a third virtual machine and move one third of your instances in this new machine.\n\u003cbr\u003e\u003cbr\u003e\n\nYou can do this without any kind of down time, using a common trick:\n\u003cul\u003e\u003cli\u003eStart the spare Redis instances in the new virtual machine.\u003c/li\u003e\n\n\u003cli\u003eSet this instances are replicas for the old instances you want to move.\u003c/li\u003e\n\n\u003cli\u003eWhen the initial synchronization is done and all the slaves are working, change the configuration of your clients to use the new instances.\u003c/li\u003e\n\n\u003cli\u003eElect the new instances as masters with SLAVEOF NO ONE.\u003c/li\u003e\n\n\u003cli\u003eFinally shut down all the old instances.\u003c/li\u003e\n\n\u003cli\u003eUpgrade your shell scripts configs with the new IP/PORT info for every instance.\u003c/li\u003e\n\n\u003c/ul\u003e\n\u003cbr\u003e\u003cbr\u003e\n\nThis solution will ensure that the down time is zero as the slaves are able to accept writes, so once you change the configuration of your clients all the clients will start writing against the new instances. In a second the old instances will not get any new query at all, so the slaves can be elected to masters, and the old masters can be killed.\n\u003ch2\u003eHash tags\u003c/h2\u003e\nWith this schema, and with Redis Cluster itself, commands taking multiple keys as arguments are harder to use since if the two keys will hash to two different instances the operation can not be performed.\n\u003cbr\u003e\u003cbr\u003e\n\nEither do not use multi key operations but instead try to model this complex ops at application level, or use a technique called Hash Tags.\nBasically if a key contains the {} characters, instead of hashing the whole string to obtain the instance ID, you just hash the string inside {}.\n\u003cbr\u003e\u003cbr\u003e\n\nSo while the key \u0026quot;foo\u0026quot; will be hashed as SHA1(\u0026quot;foo\u0026quot;), the key \u0026quot;bar{zap}\u0026quot; will be hashed just as SHA1(\u0026quot;zap\u0026quot;).\n\u003cbr\u003e\u003cbr\u003e\n\nThis way you can force keys to be stored in the same instance, so that if you want to perform intersections just against user data you can do it using hash tags that are different for every user, but the same for all the keys related to the same user. Sometimes this is enough, sometimes instead this is still not enough and you have application level help to model operations like intersections between sets in different instances, or to rethink your application logic at all.\n\u003ch2\u003eFault tolerance\u003c/h2\u003e\nThe described solution can be made fault tolerant using Redis replication.\nBasically in our example instead of firing two virtual machines, you can instead fire four. Every instance is replicated in another instance in a different virtual machine (make sure they are in a different data center).\n\u003cbr\u003e\u003cbr\u003e\n\nIf something goes bad with a virtual machine it is possible to point the clients to the other virtual machine, changing all the occurrences of the IP address in the configuration table with another one.\n\u003ch2\u003eConclusion\u003c/h2\u003e\nA solution like Redis Cluster handling all this for you is obviously a better long term solution, but what was descried in this post is a simple way to work with what we have currently.\n\u003cbr\u003e\u003cbr\u003e\n\nIf a set of scripts and small programs are developed to help in this kind of setups, including monitoring programs, tools to start and stop many instances, tools to perform backups and to restore all the .rdb or AOF files, it can be much simpler to setup a system like the one described here.\u003c/div\u003e\u003cdiv\u003e\u003cdiv\u003epost read 12997 times\u003csup\u003e\u003ca href\u003d\"http://antirez.com/page/uniquevisitors\"\u003e*\u003c/a\u003e\u003c/sup\u003e (average 612.9 visits/day)\u003c/div\u003ePosted at 16:16:37 \u003ca href\u003d\"http://antirez.com/post/redis-presharding.html\"\u003epermalink\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/post/redis-presharding.html\"\u003e4 comments\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/print.php?postid\u003d226\"\u003eprint\u003c/a\u003e | \u003ca href\u003d\"http://postli.com/post?t\u003dRedis+Presharding\u0026amp;u\u003dhttp%3A%2F%2Fantirez.com%2Fpost%2Fredis-presharding.html\"\u003epost it\u003c/a\u003e | \u003ca href\u003d\"http://technorati.com/search/http://antirez.com/post/redis-presharding.html\"\u003eView blog reactions\u003c/a\u003e\u003c/div\u003e\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/antirez/~4/hMt2BFuCsJc\" height\u003d\"1\" width\u003d\"1\"\u003e"
19
+ },"likingUsers":[{"userId":"01183224162277160914"},{"userId":"17684677394670107977"},{"userId":"12241215647954569484"},{"userId":"02714205789091616240"},{"userId":"01002900068580443852"},{"userId":"08611726946949505648"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://antirez.com/rss","title":"antirez weblog","htmlUrl":"http://antirez.com"}},{"crawlTimeMsec":"1298524173597","id":"tag:google.com,2005:reader/item/16bf17fb9cd9e1a3","categories":["user/05185502537486227907/label/Dev | Ruby",
20
+ "user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh","Miscellaneous"],"title":"Happy 18th Birthday, Ruby!","published":1298524164,"updated":1298528077,"replies":[{"href":"http://www.rubyinside.com/happy-18th-birthday-ruby-4416.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/happy-18th-birthday-ruby-4416.html/feed/atom","type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/TipduGbAvzk/happy-18th-birthday-ruby-4416.html",
21
+ "type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/ruby1.png\" alt\u003d\"\" title\u003d\"ruby\" width\u003d\"110\" height\u003d\"111\" style\u003d\"float:left;margin-right:18px;border:1px solid #666\"\u003eYes, I'm sad enough to have had this in my calendar for some time but.. it's Ruby's 18th \"birthday\" today! \u003cstrong\u003eHappy Birthday Ruby!\u003c/strong\u003e While this means she can drink, vote, and otherwise join her slightly older friends Perl (24) and Python (21) in the nightclubs of Europe, I was surprised to learn that \u003ca href\u003d\"http://en.wikipedia.org/wiki/Coming_of_Age_Day\"\u003ecoming of age in Japan is at 20 years old\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eFrom Wikipedia's \u003ca href\u003d\"http://en.wikipedia.org/wiki/Ruby_(programming_language)\"\u003eRuby entry\u003c/a\u003e:\u003c/p\u003e\n\u003cblockquote\u003e\u003cp\u003eThe name \"Ruby\" was decided on during an online chat session between Matsumoto and Keiju Ishitsuka on February 24, 1993, before any code had been written for the language. Initially two names were proposed: \"Coral\" and \"Ruby\", with the latter being chosen by Matsumoto in a later email to Ishitsuka. Matsumoto has later stated that a factor in choosing the name \"Ruby\" was because it was the birthstone of one of his colleagues.\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e\u003ccite\u003eWikipedia\u003c/cite\u003e\u003c/p\u003e\n\u003cp\u003eIf you're interested in learning more, \u003ca href\u003d\"http://linuxdevcenter.com/pub/a/linux/2001/11/29/ruby.html\"\u003ethis interview with Matz back in 2001\u003c/a\u003e will give you more history and background to the creation of Ruby.\u003c/p\u003e\n\u003cp\u003eWhile Matz has said that February 24, 1993 is Ruby's \"birthday\" (back when \u003cem\u003eI Will Always Love You\u003c/em\u003e by Whitney Houston was topping the charts), the first public release wasn't until December 21, 1995 when Ruby 0.95 was released and the first mailing list established. Ruby 1.0 followed a year later on December 25, 1996, establishing the tradition of \u003ca href\u003d\"http://www.rubyinside.com/the-ruby-communitys-christmas-releases-4118.html\"\u003eChristmas Day Ruby releases.\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eHere's to 18 more, love.\u003c/p\u003e\n\u003cp\u003e\u003csmall\u003eP.S. Before I get any grief for putting a picture of a young woman on this post — it\u0026#39;s happened before *sigh* — this actress played \u003ca href\u003d\"http://en.wikipedia.org/wiki/Ruby_Allen\"\u003ea character called Ruby\u003c/a\u003e in the UK's most popular soap opera and was about 18 at the time. Rock and roll.\u003c/small\u003e\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dTipduGbAvzk:IrzXQvUjHac:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dTipduGbAvzk:IrzXQvUjHac:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003dTipduGbAvzk:IrzXQvUjHac:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/TipduGbAvzk\" height\u003d\"1\" width\u003d\"1\"\u003e"
22
+ },"author":"Peter Cooper","likingUsers":[{"userId":"12050031834138523626"},{"userId":"10452883752421848286"},{"userId":"12129945746987380079"},{"userId":"12608176320090135909"},{"userId":"11132888979039710046"},{"userId":"04561095782341368598"},{"userId":"18253276447546087020"},{"userId":"02312042911823059098"},{"userId":"10344275679473139525"},{"userId":"12733332058644857441"},{"userId":"14258225022780889086"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/",
23
+ "title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"crawlTimeMsec":"1298215301734","id":"tag:google.com,2005:reader/item/2f5ec66100459d38","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh","Ruby on Rails","Tutorials"],"title":"How To Get Rails 3 and RSpec 2 Running Specs Fast (From Scratch)","published":1298215222,"updated":1298774558,"replies":[{"href":"http://www.rubyinside.com/how-to-rails-3-and-rspec-2-4336.html#comments",
24
+ "type":"text/html"},{"href":"http://www.rubyinside.com/how-to-rails-3-and-rspec-2-4336.html/feed/atom","type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/9kQfKhEconY/how-to-rails-3-and-rspec-2-4336.html","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/rarrr.png\" alt\u003d\"\" title\u003d\"rarrr\" width\u003d\"120\" height\u003d\"120\" style\u003d\"float:left;margin-right:16px;margin-bottom:8px;border:1px solid #555\"\u003e\u003ca href\u003d\"http://www.rubyinside.com/rails-3-0-released-and-22-free-videos-to-bring-you-up-to-speed-3733.html\"\u003eRails 3\u003c/a\u003e is great. \u003ca href\u003d\"http://www.rubyinside.com/rspec-2-0-released-rubys-leading-bdd-framework-grows-up-3880.html\"\u003eRSpec 2\u003c/a\u003e is great. And \u003ca href\u003d\"http://www.rubyinside.com/ruby-1-9-2-released-3700.html\"\u003eRuby 1.9.2\u003c/a\u003e is \u003cem\u003ereally\u003c/em\u003e great. Getting them all running together \u003cem\u003eand quickly\u003c/em\u003e, however, isn't entirely straightforward. In this post I demonstrate how to get everything ticking over along with automatically running, super-snappy test runs.\u003c/p\u003e\n\u003cp\u003eThe ultimate outcome is using Ruby 1.9.2 (though much of this is relevant to 1.8 still) to create a Rails 3 app, hook up RSpec 2, and be able to run specs \u003cem\u003equickly.\u003c/em\u003e The first two parts are easy(ish) but the \"quickly\" part requires some tinkering. Grab a coffee and carry on..\u003c/p\u003e\n\u003ch3\u003eCreate a new Rails 3 app\u003c/h3\u003e\n\u003cp\u003eGot Rails 3 installed? If not, \u003ccode\u003egem install rails\u003c/code\u003e will see you good. Then head on down to your favorite project folder with your shell and create a new Rails 3 app like so:\u003c/p\u003e\n\u003cpre\u003erails new myapp --skip-test-unit\u003c/pre\u003e\n\u003cp\u003eYou can retroactively bring RSpec 2 into an existing Rails 3 project, of course, but it's easier for this walkthrough to start afresh in case of application-specific issues.\u003c/p\u003e\n\u003ch3\u003eHooking up RSpec 2 with RSpec-Rails\u003c/h3\u003e\n\u003cp\u003eEdit the \u003ccode\u003eGemfile\u003c/code\u003e file in your new Rails project (\u003ccode\u003emyapp/Gemfile\u003c/code\u003e in this example) and add the following block to the bottom:\u003c/p\u003e\n\u003cdiv\u003e\n\u003cpre\u003e\u003cspan\u003egroup\u003c/span\u003e \u003cspan\u003e:development\u003c/span\u003e\u003cspan\u003e,\u003c/span\u003e \u003cspan\u003e:test\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003egem\u003c/span\u003e \u003cspan\u003e\u0026#39;rspec-rails\u0026#39;\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eThis tells Bundler (a gem management and dependency tool Rails 3 likes to lean on) we want to use the \u003ca href\u003d\"https://github.com/dchelimsky/rspec-rails\"\u003erspec-rails\u003c/a\u003e gem which will get RSpec running with Rails 3.0 for us. Next, we get Bundler to do its thing:\u003c/p\u003e\n\u003cpre\u003ebundle\u003c/pre\u003e\n\u003cp\u003eThis will install all of the gems referenced in \u003ccode\u003eGemfile\u003c/code\u003e, including \u003ccode\u003erspec-rails\u003c/code\u003e. (You can use \u003ccode\u003ebundle install\u003c/code\u003e instead, if you prefer, but \u003ccode\u003ebundle\u003c/code\u003e on its own works too.)\u003c/p\u003e\n\u003cp\u003eLast but not least, we need to run RSpec's 'generator' that rspec-rails has put in place for us:\u003c/p\u003e\n\u003cpre\u003erails generate rspec:install\u003c/pre\u003e\n\u003cp\u003eThe generator creates a few files. Namely:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e.rspec\u003c/code\u003e - a config file where we can store extra command line options for the \u003ccode\u003erspec\u003c/code\u003e command line tool. By default it contains \u003ccode\u003e--colour\u003c/code\u003e which turns on colored output from RSpec.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003espec\u003c/code\u003e - a directory that will store all of the various model, controller, view, acceptance and other specs for your app\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003espec/spec_helper.rb\u003c/code\u003e - a file that's loaded by every spec (not in any automatic way but most have \u003ccode\u003erequire 'spec_helper'\u003c/code\u003e at the top). It sets the test environment, contains app level RSpec configuration items, loads support files, and more.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWe still can't run \u003ccode\u003erake\u003c/code\u003e and see anything interesting yet because we don't have a database or any models initialized.\u003c/p\u003e\n\u003ch3\u003eCreating a model to test\u003c/h3\u003e\n\u003cp\u003eLet's take the easy way out and use the \u003ccode\u003escaffold\u003c/code\u003e generator to flesh out something for us to test (as well as to see what spec files can be generated automatically):\u003c/p\u003e\n\u003cpre\u003erails generate scaffold Person name:string age:integer zipcode:string\u003c/pre\u003e\n\u003cp\u003eIt's worth noting that when you generate the scaffold numerous spec files are also created (thanks to \u003ccode\u003erspec-rails\u003c/code\u003e):\u003c/p\u003e\n\u003cpre\u003espec/models/person_spec.rb\nspec/controllers/people_controller_spec.rb\nspec/views/people/edit.html.erb_spec.rb\nspec/views/people/index.html.erb_spec.rb\nspec/views/people/new.html.erb_spec.rb\nspec/views/people/show.html.erb_spec.rb\nspec/helpers/people_helper_spec.rb\nspec/routing/people_routing_spec.rb\nspec/requests/people_spec.rb\u003c/pre\u003e\n\u003cp\u003eNow bring the database up to speed with the migration for the new model:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003erake db:migrate\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eNow let's run \u003ccode\u003erake\u003c/code\u003e - finally! The result:\u003c/p\u003e\n\u003cpre\u003e...............**............\n\nPending:\n PeopleHelper add some examples to (or delete) /Users/peter/dev/rails/myapp/spec/helpers/people_helper_spec.rb\n # Not Yet Implemented\n # ./spec/helpers/people_helper_spec.rb:14\n Person add some examples to (or delete) /Users/peter/dev/rails/myapp/spec/models/person_spec.rb\n # Not Yet Implemented\n # ./spec/models/person_spec.rb:4\n\nFinished in 0.31043 seconds\n29 examples, 0 failures, 2 pending\u003c/pre\u003e\n\u003cp\u003eRock and roll. We're up and running. Sort of. Let's put in some \"real\" specs to be sure things are working nicely.\u003c/p\u003e\n\u003cp\u003eChange \u003ccode\u003espec/models/person_spec.rb\u003c/code\u003e to the following rather contrived pair of specs:\u003c/p\u003e\n\u003cdiv\u003e\n\u003cpre\u003e\u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003e\u0026#39;spec_helper\u0026#39;\u003c/span\u003e\n\n\u003cspan\u003edescribe\u003c/span\u003e \u003cspan\u003ePerson\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003eit\u003c/span\u003e \u003cspan\u003e\u0026quot;can be instantiated\u0026quot;\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003ePerson\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003enew\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eshould\u003c/span\u003e \u003cspan\u003ebe_an_instance_of\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003ePerson\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e\n \u003cspan\u003eend\u003c/span\u003e\n\n \u003cspan\u003eit\u003c/span\u003e \u003cspan\u003e\u0026quot;can be saved successfully\u0026quot;\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003ePerson\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003ecreate\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eshould\u003c/span\u003e \u003cspan\u003ebe_persisted\u003c/span\u003e\n \u003cspan\u003eend\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eNot the most useful things to spec out, admittedly, but you get a little database action and get rid of a \u003ccode\u003epending\u003c/code\u003e spec we had cluttering things up. We haven't got anything else we can seriously test yet anyway.\u003c/p\u003e\n\u003cp\u003eNow let's run \u003ccode\u003erake spec:models\u003c/code\u003e to focus our efforts on what we've just done:\u003c/p\u003e\n\u003cpre\u003e..\n\nFinished in 0.09378 seconds\n2 examples, 0 failures\u003c/pre\u003e\n\u003ch3\u003eHow to have specs run automatically with Watchr\u003c/h3\u003e\n\u003cp\u003eLet's assume we've progressed with developing our app and we're working on models and controllers, testing along the way. Rather than running \u003ccode\u003erake\u003c/code\u003e or \u003ccode\u003ebundle exec rspec\u003c/code\u003e all of the time, wouldn't it be great to have the relevant spec run \u003cem\u003eautomatically\u003c/em\u003e when we either edit the spec or a model/controller that has a spec? Well, with \u003ca href\u003d\"https://github.com/mynyml/watchr\"\u003ewatchr\u003c/a\u003e, we can. \u003cem\u003e(Note: Some people prefer \u003ca href\u003d\"http://ph7spot.com/musings/getting-started-with-autotest\"\u003eautotest\u003c/a\u003e. I find watchr more flexible and useful for other things beyond just running specs.)\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eBut if you really want to use autotest, Mike Bethany explains \u003ca href\u003d\"http://mikbe.tk/2011/02/10/blazingly-fast-tests/\"\u003ehow to set it up in a similar scenario\u003c/a\u003e in a post of his own, along with autotest-growl for OS X notifications.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eAdd \u003ccode\u003ewatchr\u003c/code\u003e to your \u003ccode\u003eGemfile\u003c/code\u003e's testing and production gem section:\u003c/p\u003e\n\u003cdiv\u003e\n\u003cpre\u003e\u003cspan\u003egroup\u003c/span\u003e \u003cspan\u003e:development\u003c/span\u003e\u003cspan\u003e,\u003c/span\u003e \u003cspan\u003e:test\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003egem\u003c/span\u003e \u003cspan\u003e\u0026#39;rspec-rails\u0026#39;\u003c/span\u003e\n \u003cspan\u003egem\u003c/span\u003e \u003cspan\u003e\u0026#39;watchr\u0026#39;\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eThen run \u003ccode\u003ebundle\u003c/code\u003e to install it.\u003c/p\u003e\n\u003cp\u003eNext, create a file called \u003ccode\u003e.watchr\u003c/code\u003e in your app's root folder and populate it with this code:\u003c/p\u003e\n\u003cdiv\u003e\n\u003cpre\u003e\u003cspan\u003edef\u003c/span\u003e \u003cspan\u003erun_spec\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003efile\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e\n \u003cspan\u003eunless\u003c/span\u003e \u003cspan\u003eFile\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eexist?\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003efile\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e\n \u003cspan\u003eputs\u003c/span\u003e \u003cspan\u003e\u0026quot;\u003c/span\u003e\u003cspan\u003e#{\u003c/span\u003e\u003cspan\u003efile\u003c/span\u003e\u003cspan\u003e}\u003c/span\u003e\u003cspan\u003e does not exist\u0026quot;\u003c/span\u003e\n \u003cspan\u003ereturn\u003c/span\u003e\n \u003cspan\u003eend\u003c/span\u003e\n\n \u003cspan\u003eputs\u003c/span\u003e \u003cspan\u003e\u0026quot;Running \u003c/span\u003e\u003cspan\u003e#{\u003c/span\u003e\u003cspan\u003efile\u003c/span\u003e\u003cspan\u003e}\u003c/span\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\n \u003cspan\u003esystem\u003c/span\u003e \u003cspan\u003e\u0026quot;bundle exec rspec \u003c/span\u003e\u003cspan\u003e#{\u003c/span\u003e\u003cspan\u003efile\u003c/span\u003e\u003cspan\u003e}\u003c/span\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\n \u003cspan\u003eputs\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\n\u003cspan\u003ewatch\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003e\u0026quot;spec/.*/*_spec\\.rb\u0026quot;\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e \u003cspan\u003e|\u003c/span\u003e\u003cspan\u003ematch\u003c/span\u003e\u003cspan\u003e|\u003c/span\u003e\n \u003cspan\u003erun_spec\u003c/span\u003e \u003cspan\u003ematch\u003c/span\u003e\u003cspan\u003e[\u003c/span\u003e\u003cspan\u003e0\u003c/span\u003e\u003cspan\u003e]\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\n\u003cspan\u003ewatch\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003e\u0026quot;app/(.*/.*)\\.rb\u0026quot;\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e \u003cspan\u003e|\u003c/span\u003e\u003cspan\u003ematch\u003c/span\u003e\u003cspan\u003e|\u003c/span\u003e\n \u003cspan\u003erun_spec\u003c/span\u003e \u003cspan\u003e%{spec/\u003c/span\u003e\u003cspan\u003e#{\u003c/span\u003e\u003cspan\u003ematch\u003c/span\u003e\u003cspan\u003e[\u003c/span\u003e\u003cspan\u003e1\u003c/span\u003e\u003cspan\u003e]\u003c/span\u003e\u003cspan\u003e}\u003c/span\u003e\u003cspan\u003e_spec.rb}\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eThis 'watchr script' directs a running watchr process to do a few things:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIf any file ending in \u003ccode\u003e_spec.rb\u003c/code\u003e under the \u003ccode\u003espec/\u003c/code\u003e directory changes, run the \u003ccode\u003erun_spec\u003c/code\u003e method with its filename.\u003c/li\u003e\n\u003cli\u003eIf any \u003ccode\u003e.rb\u003c/code\u003e file under the \u003ccode\u003eapp/\u003c/code\u003e directory changes, call the \u003ccode\u003erun_spec\u003c/code\u003e method with an equivalently named \u003ccode\u003e_spec.rb\u003c/code\u003e file under \u003ccode\u003espec\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erun_file\u003c/code\u003e accepts a filename for a spec file, checks it exists, and tells the system to run it (using \u003ccode\u003esystem\u003c/code\u003e)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIf you now run \u003ccode\u003ewatchr .watchr\u003c/code\u003e to use the \u003ccode\u003e.watchr\u003c/code\u003e script, not much will happen. But if you make any change (or even just re-save) to, say, \u003ccode\u003espec/models/person_spec.rb\u003c/code\u003e, that spec will run automatically. Make a change to \u003ccode\u003eapp/models/person.rb\u003c/code\u003e and it's the same deal. To stop watchr, CTRL+C saves the day.\u003c/p\u003e\n\u003cp\u003eWatchr can be used for a lot more than this but this is just for starters ;-)\u003c/p\u003e\n\u003cp\u003eOptionally, you might also like to create \u003ccode\u003elib/tasks/watchr.rake\u003c/code\u003e and include the following code so you can just remember to run \u003ccode\u003erake watchr\u003c/code\u003e instead (it's nice to have anything you run within a project contained in one place):\u003c/p\u003e\n\u003cdiv\u003e\n\u003cpre\u003e\u003cspan\u003edesc\u003c/span\u003e \u003cspan\u003e\u0026quot;Run watchr\u0026quot;\u003c/span\u003e\n\u003cspan\u003etask\u003c/span\u003e \u003cspan\u003e:watchr\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003esh\u003c/span\u003e \u003cspan\u003e%{bundle exec watchr .watchr}\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003ch3\u003eHow to get faster spec runs with Spork\u003c/h3\u003e\n\u003cp\u003eWe've got Rails 3 running with RSpec 2 and watchr's giving us some automatically-running-spec love. But do you notice how slow it is? Specs run quickly once they're loaded but there are several seconds of waiting beforehand.\u003c/p\u003e\n\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/yslow.png\" alt\u003d\"\" title\u003d\"yslow\" width\u003d\"130\" height\u003d\"106\" style\u003d\"float:right;margin-left:18px\"\u003eIf you run \u003ccode\u003etime rake spec:models\u003c/code\u003e with Ruby 1.9.2, you'll probably see a wallclock time of over 5 seconds (5.204s on my machine and I'm SSDed up) - holy splingledoops! If not, you're lucky, but it's \u003ca href\u003d\"http://groups.google.com/group/rubyonrails-core/browse_thread/thread/88519ef5a53088a1/c01ba447c6dc0de7?lnk\u003draot\"\u003ea commonly reported problem\u003c/a\u003e with some improvements expected in Ruby 1.9.3. We can't wait that long though..\u003c/p\u003e\n\u003cp\u003eEnter \u003ca href\u003d\"https://github.com/timcharper/spork\"\u003eSpork\u003c/a\u003e. Spork is a tool that loads the Rails environment and then \u003cem\u003eforks\u003c/em\u003e each time you want to run some specs (or tests, it can be set up to run with \u003ccode\u003eTest::Unit\u003c/code\u003e too). In this way, the whole Rails initialization process is skipped, shaving valuable seconds off of your spec runs.\u003c/p\u003e\n\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/horriblediagram1.png\" alt\u003d\"\" title\u003d\"horriblediagram\" width\u003d\"250\" height\u003d\"211\" style\u003d\"float:right;margin-left:16px;margin-bottom:8px\"\u003eEdit your \u003ccode\u003eGemfile\u003c/code\u003e again and include Spork:\u003c/p\u003e\n\u003cpre\u003egem \u0026#39;spork\u0026#39;, \u0026#39;~\u0026gt; 0.9.0.rc\u0026#39;\u003c/pre\u003e\n\u003cp\u003eRun \u003ccode\u003ebundle\u003c/code\u003e to install Spork.\u003c/p\u003e\n\u003cp\u003eNext, Spork needs to make some changes to your \u003ccode\u003espec/spec_helper.rb\u003c/code\u003e file. Because it only initializes the Rails environment once and then forks it, you might have initialization features that you \u003cem\u003eneed\u003c/em\u003e to run on each test run. Spork will let you do this but it needs to make those changes first. Run:\u003c/p\u003e\n\u003cpre\u003espork --bootstrap\u003c/pre\u003e\n\u003cp\u003eThe result:\u003c/p\u003e\n\u003cpre\u003eUsing RSpec\nBootstrapping /Users/peter/dev/rails/myapp/spec/spec_helper.rb.\nDone. Edit /Users/peter/dev/rails/myapp/spec/spec_helper.rb now with your favorite text editor and follow the instructions.\u003c/pre\u003e\n\u003cp\u003eBring up \u003ccode\u003espec/spec_helper.rb\u003c/code\u003e. All \u003ccode\u003espork --bootstrap\u003c/code\u003e has done is add some extra code to the top of the file. Read the comments there to get a better feel for what to do and what Spork requires and keep them in mind as we progress (in case you want to do something differently).\u003c/p\u003e\n\u003cp\u003eGet rid of \u003ccode\u003erequire 'rubygems'\u003c/code\u003e from the first line - we're using Bundler so it's not necessary.\u003c/p\u003e\n\u003cp\u003eNext, \u003cem\u003ecut\u003c/em\u003e and paste all of the 'old' contents of \u003ccode\u003espec_helper.rb\u003c/code\u003e into the \u003ccode\u003eSpork.prefork\u003c/code\u003e block. Since we're running an empty(ish) project, there's nothing special we've added that we need to run on each run using the \u003ccode\u003eSpork.each_run\u003c/code\u003e block. We can leave that empty.\u003c/p\u003e\n\u003cp\u003eYou'll end up with a \u003ccode\u003espec_helper.rb\u003c/code\u003e file that looks like this:\u003c/p\u003e\n\u003cdiv\u003e\n\u003cpre\u003e\u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003e\u0026#39;spork\u0026#39;\u003c/span\u003e\n\n\u003cspan\u003eSpork\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eprefork\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003e# Loading more in this block will cause your tests to run faster. However, \u003c/span\u003e\n \u003cspan\u003e# if you change any configuration or code from libraries loaded here, you\u0026#39;ll\u003c/span\u003e\n \u003cspan\u003e# need to restart spork for it take effect.\u003c/span\u003e\n \u003cspan\u003e# This file is copied to spec/ when you run \u0026#39;rails generate rspec:install\u0026#39;\u003c/span\u003e\n \u003cspan\u003eENV\u003c/span\u003e\u003cspan\u003e[\u003c/span\u003e\u003cspan\u003e\u0026quot;RAILS_ENV\u0026quot;\u003c/span\u003e\u003cspan\u003e]\u003c/span\u003e \u003cspan\u003e||\u003d\u003c/span\u003e \u003cspan\u003e\u0026#39;test\u0026#39;\u003c/span\u003e\n \u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003eFile\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eexpand_path\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003e\u0026quot;../../config/environment\u0026quot;\u003c/span\u003e\u003cspan\u003e,\u003c/span\u003e \u003cspan\u003e__FILE__\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e\n \u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003e\u0026#39;rspec/rails\u0026#39;\u003c/span\u003e\n\n \u003cspan\u003e# Requires supporting ruby files with custom matchers and macros, etc,\u003c/span\u003e\n \u003cspan\u003e# in spec/support/ and its subdirectories.\u003c/span\u003e\n \u003cspan\u003eDir\u003c/span\u003e\u003cspan\u003e[\u003c/span\u003e\u003cspan\u003eRails\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eroot\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003ejoin\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003e\u0026quot;spec/support/**/*.rb\u0026quot;\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e\u003cspan\u003e].\u003c/span\u003e\u003cspan\u003eeach\u003c/span\u003e \u003cspan\u003e{\u003c/span\u003e\u003cspan\u003e|\u003c/span\u003e\u003cspan\u003ef\u003c/span\u003e\u003cspan\u003e|\u003c/span\u003e \u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003ef\u003c/span\u003e\u003cspan\u003e}\u003c/span\u003e\n\n \u003cspan\u003eRSpec\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003econfigure\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e \u003cspan\u003e|\u003c/span\u003e\u003cspan\u003econfig\u003c/span\u003e\u003cspan\u003e|\u003c/span\u003e\n \u003cspan\u003e# \u003d\u003d Mock Framework\u003c/span\u003e\n \u003cspan\u003e#\u003c/span\u003e\n \u003cspan\u003e# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:\u003c/span\u003e\n \u003cspan\u003e#\u003c/span\u003e\n \u003cspan\u003e# config.mock_with :mocha\u003c/span\u003e\n \u003cspan\u003e# config.mock_with :flexmock\u003c/span\u003e\n \u003cspan\u003e# config.mock_with :rr\u003c/span\u003e\n \u003cspan\u003econfig\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003emock_with\u003c/span\u003e \u003cspan\u003e:rspec\u003c/span\u003e\n\n \u003cspan\u003e# Remove this line if you\u0026#39;re not using ActiveRecord or ActiveRecord fixtures\u003c/span\u003e\n \u003cspan\u003econfig\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003efixture_path\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u0026quot;\u003c/span\u003e\u003cspan\u003e#{\u003c/span\u003e\u003cspan\u003e::\u003c/span\u003e\u003cspan\u003eRails\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eroot\u003c/span\u003e\u003cspan\u003e}\u003c/span\u003e\u003cspan\u003e/spec/fixtures\u0026quot;\u003c/span\u003e\n\n \u003cspan\u003e# If you\u0026#39;re not using ActiveRecord, or you\u0026#39;d prefer not to run each of your\u003c/span\u003e\n \u003cspan\u003e# examples within a transaction, remove the following line or assign false\u003c/span\u003e\n \u003cspan\u003e# instead of true.\u003c/span\u003e\n \u003cspan\u003econfig\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003euse_transactional_fixtures\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003etrue\u003c/span\u003e\n \u003cspan\u003eend\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\n\u003cspan\u003eSpork\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eeach_run\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003e# This code will be run each time you run your specs.\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eHead back to your shell and the root of your project and run \u003ccode\u003espork\u003c/code\u003e:\u003c/p\u003e\n\u003cpre\u003eUsing RSpec\nLoading Spork.prefork block...\nSpork is ready and listening on 8989!\u003c/pre\u003e\n\u003cp\u003eNow we're cooking with gas. Open another shell, head to the root of your project, and run \u003ccode\u003ewatchr .watchr\u003c/code\u003e too. Then head to \u003ccode\u003espec/models/person_spec.rb\u003c/code\u003e in your text editor and re-save it (or even make a change if you want). Your specs run but.. they're no faster! What's wrong?\u003c/p\u003e\n\u003cp\u003eIt turns out we need to make another change so that RSpec knows we're running Spork. Edit the \u003ccode\u003e.rspec\u003c/code\u003e file (mentioned earlier) and add \u003ccode\u003e--drb\u003c/code\u003e to the line (so it probably reads \u003ccode\u003e--colour --drb\u003c/code\u003e). \u003cem\u003eNow\u003c/em\u003e, edit the spec again, save, and.. fast!\u003c/p\u003e\n\u003cp\u003eYou should note that if you use \u003ccode\u003erake\u003c/code\u003e at this point to run your entire suite, it'll still not be particularly fast because \u003cem\u003erake\u003c/em\u003e itself is initializing Rails in order to do its job. But if you want to run your entire suite quickly, just run:\u003c/p\u003e\n\u003cpre\u003erspec spec\u003c/pre\u003e\n\u003cp\u003eWith our dummy app and on my machine, this runs in a wallclock time of 0.759s - a serious improvement over 5.2 seconds.\u003c/p\u003e\n\u003cp\u003eWe have Rails 3, RSpec 2, watchr, spork, and SUPER-DUPER FAST SPECS all running on Ruby 1.9.2. Score!\u003c/p\u003e\n\u003cp\u003eA minor snafu will remain, though. If you update \u003ccode\u003eapp/models/person.rb\u003c/code\u003e, the change won't take effect in your tests since Spork has the \u003cem\u003eold\u003c/em\u003e \u003ccode\u003ePerson\u003c/code\u003e still in memory. One way around this is to edit \u003ccode\u003econfig/environments/test.rb\u003c/code\u003e and change:\u003c/p\u003e\n\u003cpre\u003econfig.cache_classes \u003d true\u003c/pre\u003e\n\u003cp\u003eTo:\u003c/p\u003e\n\u003cpre\u003econfig.cache_classes \u003d false\u003c/pre\u003e\n\u003cp\u003eNow your app's classes are reloaded when necessary.\u003c/p\u003e\n\u003cp\u003e\u003ccenter\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/awyeah.jpeg\" alt\u003d\"\" title\u003d\"awyeah\" width\u003d\"348\" height\u003d\"232\" style\u003d\"text-align:center;margin-left:auto;margin-right:auto\"\u003e\u003c/center\u003e\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003d9kQfKhEconY:MOU_srPkVJM:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003d9kQfKhEconY:MOU_srPkVJM:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003d9kQfKhEconY:MOU_srPkVJM:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/9kQfKhEconY\" height\u003d\"1\" width\u003d\"1\"\u003e"
25
+ },"author":"Peter Cooper","likingUsers":[{"userId":"15963914873140213203"},{"userId":"15638623306692727372"},{"userId":"02269656737792893975"},{"userId":"04828842218541785042"},{"userId":"11132888979039710046"},{"userId":"18253276447546087020"},{"userId":"18368794134664966015"},{"userId":"11774881710904772251"},{"userId":"12064991305330935676"},{"userId":"05272376215565670505"},{"userId":"06273792160116904845"},{"userId":"07627850390712298043"},{"userId":"00008934257923356678"},{"userId":"06057854811868693294"
26
+ },{"userId":"09193292141477413545"},{"userId":"08353054291962603524"},{"userId":"07573777946365969643"},{"userId":"08200398987966777732"},{"userId":"02428719105696136930"},{"userId":"05874166663540832169"},{"userId":"05370333769261935271"},{"userId":"16216928680461065086"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/","title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"crawlTimeMsec":"1298081161825","id":"tag:google.com,2005:reader/item/3c07ad12e62baa01",
27
+ "categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/fresh","News"],"title":"Two Security Vulnerabilities Force 3 New Releases of MRI Ruby 1.8.7, 1.9.1, and 1.9.2","published":1298081132,"updated":1300120175,"replies":[{"href":"http://www.rubyinside.com/two-security-vulnerabilities-force-3-new-releases-of-mri-ruby-1-8-7-1-9-1-and-1-9-2-4323.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/two-security-vulnerabilities-force-3-new-releases-of-mri-ruby-1-8-7-1-9-1-and-1-9-2-4323.html/feed/atom",
28
+ "type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/FyIShGEDivc/two-security-vulnerabilities-force-3-new-releases-of-mri-ruby-1-8-7-1-9-1-and-1-9-2-4323.html","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/arghhhh-150x150.png\" alt\u003d\"\" title\u003d\"arghhhh\" width\u003d\"100\" style\u003d\"float:left;margin-right:18px\"\u003eIt's been a \u003cem\u003edies horribilis\u003c/em\u003e for MRI Ruby today with two new security vulnerabilities forcing the release of 3 new recommended production versions of the de facto official Ruby interpreter. The first, a vulnerability in \u003ccode\u003eFileUtils.remove_entry_secure\u003c/code\u003e affects both 1.8 and 1.9 branches, while the second, a $SAFE mode vulnerability, affects only 1.8.\u003c/p\u003e\n\u003ch3\u003eThe FileUtils Vulnerability (1.8 and 1.9)\u003c/h3\u003e\n\u003cp\u003eUrabe Shyouhei of the Ruby core team has \u003ca href\u003d\"http://www.ruby-lang.org/en/news/2011/02/18/fileutils-is-vulnerable-to-symlink-race-attacks/\"\u003eannounced that FileUtils is vulnerable to symlink race attacks\u003c/a\u003e and he's not talking about hate crimes. Ruby versions \u003cem\u003eincluding and prior\u003c/em\u003e to Ruby 1.8.6p420, Ruby 1.8.7p330, Ruby 1.9.1p430 and Ruby 1.9.2p136 are affected so you're almost guaranteed to be affected.\u003c/p\u003e\n\u003cp\u003eThe problem is \u003ca href\u003d\"http://www.ruby-doc.org/stdlib/libdoc/fileutils/rdoc/classes/FileUtils.html#M000917\"\u003eFileUtils#remove_entry_secure\u003c/a\u003e, a method that was meant to be more secure than its sibling \u003ccode\u003eFileUtils#remove_entry\u003c/code\u003e. What the \u003ccode\u003eremove_entry*\u003c/code\u003e methods do is to 'remove' an entry in the local file system. The problem, though, is that \u003ccode\u003eremove_entry_secure\u003c/code\u003e's security wasn't quite good enough and it could be used by local users to delete arbitrary files and directories. Not a great feature.\u003c/p\u003e\n\u003ch3\u003eThe $SAFE Vulnerability (1.8 only)\u003c/h3\u003e\n\u003cp\u003eRuby's \u003ca href\u003d\"http://www.ruby-doc.org/docs/ProgrammingRuby/html/taint.html\"\u003e\"safe levels\"\u003c/a\u003e provide a way for you to make the Ruby interpreter more \"paranoid\" about operations it can perform and the data it can process. There are five levels and they progressively lock down what Ruby will accept - particularly useful if it's necessary to consider all data to be tainted, for example.\u003c/p\u003e\n\u003cp\u003eThis recent vulnerability plays on the fact that the \u003ccode\u003eException\u003c/code\u003e class can both accept and return a string. The bad part is that \u003ccode\u003eException\u003c/code\u003e wasn't respecting the safe level in terms of keeping the string \u003cem\u003etainted.\u003c/em\u003e Urabe Shyouhei provides this example:\u003c/p\u003e\n\u003cdiv\u003e\n\u003cpre\u003e\u003cspan\u003e$secret_path\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\"foo\"\u003c/span\u003e\n\n\u003cspan\u003eproc\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003e$SAFE\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e4\u003c/span\u003e\n \u003cspan\u003eException\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003enew\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003e$secret_path\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003eto_s\u003c/span\u003e\n \u003cspan\u003e$secret_path\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003ereplace\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003e\"/etc/passwd\"\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\u003cspan\u003e.\u003c/span\u003e\u003cspan\u003ecall\u003c/span\u003e\n\n\u003cspan\u003eopen\u003c/span\u003e\u003cspan\u003e(\u003c/span\u003e\u003cspan\u003e$secret_path\u003c/span\u003e\u003cspan\u003e)\u003c/span\u003e \u003cspan\u003edo\u003c/span\u003e\n \u003cspan\u003e# do the dirty here\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eThankfully, it only affects Ruby 1.8 (not Ruby 1.9) in the shape of Ruby 1.8.6p420 and earlier and Ruby 1.8.7p330 and earlier. The downside? That covers most Ruby 1.8 installs out there, including the default one with OS X. Get upgrading.\u003c/p\u003e\n\u003ch3\u003eThe Solution - New Releases of Ruby\u003c/h3\u003e\n\u003cp\u003eUndoubtedly you have your own ways and means of doing upgrades (using RVM, for example) but Urabe has provided \u003ca href\u003d\"http://www.ruby-lang.org/en/news/2011/02/18/fileutils-is-vulnerable-to-symlink-race-attacks/\"\u003elinks\u003c/a\u003e to the latest builds on the official MRI Ruby site if you want them. Repeated here:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eRuby 1.8.7-p334:\u003c/strong\u003e \u003ca href\u003d\"http://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p334.tar.gz\"\u003ehttp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p334.tar.gz\u003c/a\u003e\u003cbr\u003e\n\u003cstrong\u003eRuby 1.9.1-p431:\u003c/strong\u003e \u003ca href\u003d\"http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p431.tar.gz\"\u003ehttp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p431.tar.gz\u003c/a\u003e\u003cbr\u003e\n\u003cstrong\u003eRuby 1.9.2-p180:\u003c/strong\u003e \u003ca href\u003d\"http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p180.tar.gz\"\u003ehttp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p180.tar.gz\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eLast but not least, if you've been wondering why Ruby Inside hasn't updated for two weeks, I have some explaining to do and.. it'll be in a post of its own very soon ;-) Meanwhile, check out \u003ca href\u003d\"http://www.rubyflow.com/\"\u003eRubyFlow's snazzy new design!\u003c/a\u003e\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dFyIShGEDivc:f7jVRWDaIbg:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dFyIShGEDivc:f7jVRWDaIbg:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003dFyIShGEDivc:f7jVRWDaIbg:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/FyIShGEDivc\" height\u003d\"1\" width\u003d\"1\"\u003e"
29
+ },"author":"Peter Cooper","likingUsers":[{"userId":"13668608591451703896"},{"userId":"18253276447546087020"},{"userId":"06808395769643365537"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/","title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"isReadStateLocked":true,"crawlTimeMsec":"1297077065354","id":"tag:google.com,2005:reader/item/eee87cab91cf48b2","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list",
30
+ "user/05185502537486227907/state/com.google/read"],"title":"there is something between append only and reusing blocks","published":1297076548,"updated":1297076548,"alternate":[{"href":"http://feedproxy.google.com/~r/antirez/~3/n8A9yW503kw/btree-reuse-blocks-with-delay.html","type":"text/html"}],"summary":{"direction":"ltr","content":"\u003cdiv\u003e\u003cdiv style\u003d\"clear:both\"\u003e\u003c/div\u003e\u003cdiv\u003eOne of the most interesting properties of an append only btree is that it's impossible to corrupt. There are other interesting properties as well like concurrent accesses are trivial, since whatever is the root and all the nodes your reader is going to access, they are all valid, just they may be an old representation of the btree.\n\u003cbr\u003e\u003cbr\u003e\n\nBut there is a price to pay that I'm not happy with, that is, the append only btree requires a compaction process, otherwise it will get bigger and bigger. If your use case is mostly-reads this may be ok, but if you plan to use a lot of writes there is some math to do.\n\u003cbr\u003e\u003cbr\u003e\n\nThink at this: if you have many writes that are near to the max disk I/O that your server is able to deliver, your append only btree file size is going to get big of course, and you need compaction. But you are already near to the I/O limit, what happens once you start writing a new file to rewrite the btree? The additional I/O can affect badly the performance of the btree. Can you slow down writes in order to reduce the impact? Unfortunately not, otherwise the rewrite will never be fast enough to compact the file.\n\u003cbr\u003e\u003cbr\u003e\n\nI'm all for performance characteristics that are as predictable as possible, so I don't like this design. It is for sure a sensible one for many applications, but I can live better with the idea of a single file that is never rewritten (if not for crash recovery or alike) and that is able to reuse the no longer used blocks.\n\u003cbr\u003e\u003cbr\u003e\n\nBut guess what? such a btree is much simpler to corrupt, especially if you don't use fsync() in order to implement write barriers. Disks and operating systems can reorder writes, so even if you do something like:\n\u003cul\u003e\u003cli\u003eCreate a new node\u003c/li\u003e\n\n\u003cli\u003eLink the new node to the existing tree\u003c/li\u003e\n\n\u003c/ul\u003e\nIf there is not an fsync() between the two, it is well possible that writes are reordered, so that the link is created before the node is written on disk. If a crash happens between this two operations you end with a corrupted btree.\nBut are the append only strategy and update in place (and reuse blocks via free lists) so incompatible? Possibly not.\n\u003cbr\u003e\u003cbr\u003e\n\nExperimenting with my btree implementation I discovered what is probably the discovery of hot water, that is, likely common practice, but the kind of common practice that you'll never find in an algorithms book. That is, what about if my free list entries have a timeout so that they can't be used before 60 seconds?\n\u003cbr\u003e\u003cbr\u003e\n\nThis way the btree implementation can always rewrite the modified nodes instead of updating them in place. Even if there is a read currently in progress, we are sure that even if our writes will reuse nodes, no recently freed node will be used, so unless the read will take 60 seconds to complete, everything will go well.\n\u003cbr\u003e\u003cbr\u003e\n\nUnfortunately we still need a fixed offset for our root node pointer, so an fsync is still needed on every write in order to make sure that reordering can't corrupt our btree on crash.\n\u003cbr\u003e\u003cbr\u003e\n\nWith my implementation I'm not still at this stage as I'm starting with the most basic things that can work well, but that's the idea for the next releases.\u003c/div\u003e\u003cdiv\u003e\u003cdiv\u003epost read 6531 times\u003csup\u003e\u003ca href\u003d\"http://antirez.com/page/uniquevisitors\"\u003e*\u003c/a\u003e\u003c/sup\u003e (average 165.7 visits/day)\u003c/div\u003ePosted at 11:02:28 \u003ca href\u003d\"http://antirez.com/post/btree-reuse-blocks-with-delay.html\"\u003epermalink\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/post/btree-reuse-blocks-with-delay.html\"\u003e7 comments\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/print.php?postid\u003d225\"\u003eprint\u003c/a\u003e | \u003ca href\u003d\"http://postli.com/post?t\u003dthere+is+something+between+append+only+and+reusing+blocks\u0026amp;u\u003dhttp%3A%2F%2Fantirez.com%2Fpost%2Fbtree-reuse-blocks-with-delay.html\"\u003epost it\u003c/a\u003e | \u003ca href\u003d\"http://technorati.com/search/http://antirez.com/post/btree-reuse-blocks-with-delay.html\"\u003eView blog reactions\u003c/a\u003e\u003c/div\u003e\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/antirez/~4/n8A9yW503kw\" height\u003d\"1\" width\u003d\"1\"\u003e"
31
+ },"likingUsers":[{"userId":"03256507980052811175"},{"userId":"06404047744720231835"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://antirez.com/rss","title":"antirez weblog","htmlUrl":"http://antirez.com"}},{"isReadStateLocked":true,"crawlTimeMsec":"1297027020640","id":"tag:google.com,2005:reader/item/7efbc907ec2ca33b","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/read"
32
+ ],"title":"Making Ruby Gems","published":1297028181,"updated":1297028181,"alternate":[{"href":"http://timeless.judofyr.net/making-ruby-gems","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003eOne of my favorite aspects of the Ruby community is its strong commitment to Open Source. Thousands of Rubyists have written great libraries and placed the code up on GitHub (or elsewhere on the Internet) for everyone to use. Ruby has a fantastic tool to distribute these libraries: RubyGems. One of the reasons that Open Source runs so strong through Ruby’s veins is how easy it is to share your code.\u003c/p\u003e\n\n\u003cp\u003eWhile making a gem is really easy, there are a few additional concens that you should be aware of when you distribute your code as a gem. Here’s an example of building a simple Gem, with some notes on best practices along the way.\u003c/p\u003e\n\n\u003ch2\u003eThe structure of a gem\u003c/h2\u003e\n\n\u003cp\u003eAt its core, a gem consists of two things: some \u003cem\u003ecode\u003c/em\u003e, and a \u003cem\u003egemspec\u003c/em\u003e. The gemspec file defines a \u003ccode\u003eGem::Specification\u003c/code\u003e object, which is used by \u003ccode\u003erubygems\u003c/code\u003e to handle the management of your code. That’s it! Nothing complicated. However, there are some conventions that virtually all gems follow that will help you out.\u003c/p\u003e\n\n\u003cp\u003eMost gems have a directory structure that looks like this:\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode\u003e$ tree -d mygem\n|-- lib/\n| |-- mygem.rb\n| `-- mygem/\n| |-- base.rb\n| `-- version.rb\n|-- test/\n|-- bin/\n|-- Rakefile\n`-- mygem.gemspec\u003c/code\u003e\u003c/pre\u003e\n\n\u003cp\u003eAll of the code for the gem goes under the \u003ccode\u003elib\u003c/code\u003e directory. This directory is what gets added to your $LOAD_PATH, and so \u003ccode\u003elib\u003c/code\u003e usually contains two things: mygem.rb and a directory named mygem. mygem.rb will look like this:\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode lang\u003d\"ruby\"\u003e\u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026#39;\u003c/span\u003emygem/base\u003cspan\u003e\u0026#39;\u003c/span\u003e\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\n\n\u003cp\u003eIf there were more files in \u003ccode\u003elib/mygem\u003c/code\u003e, they’d be \u003ccode\u003erequire\u003c/code\u003ed here, too. This is done so that you can break up your project into however many files you’d like, and name them whatever you want, and nobody will tramp on each others’ toes. Think about it: if I have two gems installed, and both of them have their \u003ccode\u003elib\u003c/code\u003e directories included, and they both name a file \u003ccode\u003ejson.rb\u003c/code\u003e, which one is going to be loaded? It causes problems. So just follow this structure, and everything will work out.\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003eversion.rb\u003c/code\u003e file looks like this:\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode lang\u003d\"ruby\"\u003e\u003cspan\u003emodule\u003c/span\u003e \u003cspan\u003eMygem\u003c/span\u003e\n \u003cspan\u003eVERSION\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e0.0.1\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\n\n\u003cp\u003eThis constant will be used in our gemspec. It’s nice to have it in a separate file, so that we can easily find and increment it when releasing a new version of the gem.\u003c/p\u003e\n\n\u003cp\u003eI’m sure that you can guess what goes in the \u003ccode\u003etest\u003c/code\u003e directory: your unit tests! You do have those, right? We’ll talk more about this later.\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003ebin\u003c/code\u003e directory holds any binaries that we want to distribute with our gem. Calling them ‘binaries’ is sort of a misnomer, though: these are almost always just Ruby scripts, starting off with a ‘shebang line\u003ccode\u003e:\u003c/code\u003e\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode lang\u003d\"ruby\"\u003e\u003cspan\u003e\u003cspan\u003e#\u003c/span\u003e!/usr/bin/env ruby\u003c/span\u003e\n\n\u003cspan\u003ebegin\u003c/span\u003e\n \u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026#39;\u003c/span\u003emygem\u003cspan\u003e\u0026#39;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003erescue\u003c/span\u003e \u003cspan\u003eLoadError\u003c/span\u003e\n \u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026#39;\u003c/span\u003erubygems\u003cspan\u003e\u0026#39;\u003c/span\u003e\u003c/span\u003e\n \u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026#39;\u003c/span\u003emygem\u003cspan\u003e\u0026#39;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\n\u003cspan\u003e\u003cspan\u003e#\u003c/span\u003emore code goes here\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\n\n\u003cp\u003eThis is a patten you’ll often see in gems that also give you scripts to run. Why the \u003ccode\u003ebegin\u003c/code\u003e/\u003ccode\u003erescue\u003c/code\u003e/\u003ccode\u003eend\u003c/code\u003e? Well, \u003ca href\u003d\"http://tomayko.com/writings/require-rubygems-antipattern\"\u003e‘require “rubygems” is wrong’\u003c/a\u003e. Basically, someone may be using something other than rubygems to manage their path, and so you shouldn’t trample on their toes. But if you can’t find your gem, then giving it a second shot with Rubygems is better than crashing.\u003c/p\u003e\n\n\u003ch2\u003eThe Gemspec\u003c/h2\u003e\n\n\u003cp\u003eHere’s a sample gemspec:\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode lang\u003d\"ruby\"\u003e\u003cspan\u003e\u003cspan\u003e#\u003c/span\u003e -*- encoding: utf-8 -*-\u003c/span\u003e\n\u003cspan\u003e\u003cspan\u003e$\u003c/span\u003e:\u003c/span\u003e.\u003cspan\u003epush\u003c/span\u003e \u003cspan\u003eFile\u003c/span\u003e.\u003cspan\u003eexpand_path\u003c/span\u003e(\u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e../lib\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e, \u003cspan\u003e__FILE__\u003c/span\u003e)\n\u003cspan\u003erequire\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003emygem/version\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e\n\n\u003cspan\u003eGem\u003c/span\u003e::\u003cspan\u003eSpecification\u003c/span\u003e.\u003cspan\u003enew\u003c/span\u003e \u003cspan\u003edo \u003c/span\u003e|\u003cspan\u003es\u003c/span\u003e|\n s.\u003cspan\u003ename\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003emygem\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e\n s.\u003cspan\u003eversion\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003eMygem\u003c/span\u003e::\u003cspan\u003eVERSION\u003c/span\u003e\n s.\u003cspan\u003eplatform\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003eGem\u003c/span\u003e::\u003cspan\u003ePlatform\u003c/span\u003e::\u003cspan\u003eRUBY\u003c/span\u003e\n s.\u003cspan\u003eauthors\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e [\u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003eSteve Klabnik\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e]\n s.\u003cspan\u003eemail\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e [\u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003esteve@steveklabnik.com\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e]\n s.\u003cspan\u003ehomepage\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e\n s.\u003cspan\u003esummary\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e%q{\u003c/span\u003eA sample gem\u003cspan\u003e}\u003c/span\u003e\u003c/span\u003e\n s.\u003cspan\u003edescription\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e%q{\u003c/span\u003eA sample gem. It doesn\u0026#39;t do a whole lot, but it\u0026#39;s still useful.\u003cspan\u003e}\u003c/span\u003e\u003c/span\u003e\n\n s.\u003cspan\u003eadd_dependency\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003elaunchy\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e\n s.\u003cspan\u003eadd_development_dependency\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003erspec\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e, \u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e~\u0026gt;2.5.0\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e\n\n s.\u003cspan\u003efiles\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e`\u003c/span\u003egit ls-files\u003cspan\u003e`\u003c/span\u003e\u003c/span\u003e.\u003cspan\u003esplit\u003c/span\u003e(\u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003cspan\u003e\\n\u003c/span\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e)\n s.\u003cspan\u003etest_files\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e`\u003c/span\u003egit ls-files -- {test,spec,features}/*\u003cspan\u003e`\u003c/span\u003e\u003c/span\u003e.\u003cspan\u003esplit\u003c/span\u003e(\u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003cspan\u003e\\n\u003c/span\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e)\n s.\u003cspan\u003eexecutables\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003e\u003cspan\u003e`\u003c/span\u003egit ls-files -- bin/*\u003cspan\u003e`\u003c/span\u003e\u003c/span\u003e.\u003cspan\u003esplit\u003c/span\u003e(\u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003cspan\u003e\\n\u003c/span\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e).\u003cspan\u003emap\u003c/span\u003e{ |\u003cspan\u003ef\u003c/span\u003e| \u003cspan\u003eFile\u003c/span\u003e.\u003cspan\u003ebasename\u003c/span\u003e(f) }\n s.\u003cspan\u003erequire_paths\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e [\u003cspan\u003e\u003cspan\u003e\u0026quot;\u003c/span\u003elib\u003cspan\u003e\u0026quot;\u003c/span\u003e\u003c/span\u003e]\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\n\n\u003cp\u003eAs you can see, it uses the standard ‘pass a block to new’ DSL convention to build up all of the information about our Gem. Most of it is pretty self-explanatory, but there are a few interesting parts: You can see we used the Mygem::VERSION constant we defined earlier to set our version. We use \u003ccode\u003egit\u003c/code\u003e to list all of the files in our project, as well as our test files and executables. The ‘add dependency’ lines tell \u003ccode\u003erubygems\u003c/code\u003e what other gems we’ll need to install, if the user doesn’t have them already.\u003c/p\u003e\n\n\u003ch2\u003eTools to build tools\u003c/h2\u003e\n\n\u003cp\u003eBecause gems follow these conventions, there are a bunch of different gems that can help you make gems, like \u003ca href\u003d\"http://seattlerb.rubyforge.org/hoe/\"\u003ehoe\u003c/a\u003e or \u003ca href\u003d\"https://github.com/technicalpickles/jeweler\"\u003ejeweler\u003c/a\u003e. My favorite is a one-two punch with \u003ccode\u003ervm\u003c/code\u003e and bundler.\u003c/p\u003e\n\n\u003cp\u003eIf you’re not using \u003ca href\u003d\"http://rvm.beginrescueend.com/\"\u003ervm\u003c/a\u003e already, you should. rvm is wonderful for a few reasons, but when you’re making a gem, rvm’s \u003ca href\u003d\"http://rvm.beginrescueend.com/gemsets/\"\u003egemsets\u003c/a\u003e feature allow you to develop your gems in a cleanroom environment. This is nice for two reasons: you can verify that you have all of your dependencies configured properly, and you won’t pollute your regular Ruby setup with your undoubtedly half-broken under-development versions of your own gems.\u003c/p\u003e\n\n\u003cp\u003eBundler is great for managing depedancies of gems in your applications, but it also includes two cool tools to help you make gems: \u003ccode\u003ebundle gem \u0026lt;gemname\u0026gt;\u003c/code\u003e will create a gem skeleton, and that skeleton is set up with a few \u003ccode\u003erake\u003c/code\u003e tasks to make your development of gems nice and simple. The bundler skeleton sets up all of those directories I showed you above, as well as giving you a Gemfile, a \u003ccode\u003egit\u003c/code\u003e repository, and a .gitignore file. The three \u003ccode\u003erake\u003c/code\u003e tasks bunlder installs are ‘build,’ ‘install,’ and ‘release.’ ‘build’ builds your gem into a .gem, ‘install’ installs that gem into your current Ruby, and ‘release’ will tag your release, push it to GitHub, and then push your gem to rubygems.org. Super simple.\u003c/p\u003e\n\n\u003ch2\u003eAn example\u003c/h2\u003e\n\n\u003cp\u003eI gave a lightning talk at \u003ca href\u003d\"http://pghrb.org/\"\u003epghrb\u003c/a\u003e recently, and actually live-coded a gem while explaining this stuff. The resulting gem, which simply opens my presentation in a web browser, is on GitHub. It’s called \u003ca href\u003d\"https://github.com/steveklabnik/teachmehowtomakearubygem\"\u003eteachmehowtomakearubygem\u003c/a\u003e, and you can get it with \u003ccode\u003egem install teachmehowtomakearubygem\u003c/code\u003e. I’m still revising the presentation to read better; I didn’t want to present a giant wall of text, but this article is much easier to read than the presentation is. Still, all of the example code is there.\u003c/p\u003e\n\n\u003ch2\u003eTesting your Gem\u003c/h2\u003e\n\n\u003cp\u003eIf you set up your Rakefile to run your tests with \u003ccode\u003erake test\u003c/code\u003e, you can take advantage of a really neat new project: \u003ca href\u003d\"http://gem-testers.org/\"\u003egem-testers\u003c/a\u003e. The only other thing you need to do is add an empty \u003ccode\u003e.gemtest\u003c/code\u003e file to your project, and gem-testers will pick it up. Once enabled, your gem’s tests will be run on a variety of machines by a bunch of different people. This project is just getting underway, but similar efforts have provided a great benefit to people who write Perl libraries. Don’t have a Mac, but want to test your gem out on x86_64/darwin? gem-testers to the rescue!\u003c/p\u003e\n\n\u003ch2\u003eA note on versioning\u003c/h2\u003e\n\n\u003cp\u003eTry to follow \u003ca href\u003d\"http://semver.org/\"\u003esemantic versioning\u003c/a\u003e when releasing your gems. This makes it much easier for people using your gem to use things like ‘~\u0026gt;’ when specifying the version they’d like to use, and not have to worry too much about API breakage. A little bit of work by everyone to follow conventions goes a long way.\u003c/p\u003e\n\n\u003ch2\u003eEven further: C extensions\u003c/h2\u003e\n\n\u003cp\u003eI don’t have much experience creating gems with C extensions, so if you have any best practices to share, please \u003ca href\u003d\"http://timeless.judofyr.net/comments\"\u003eget in touch\u003c/a\u003e and share them.\u003c/p\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/MagnusHolm/~4/tuwM2HTR5DE\" height\u003d\"1\" width\u003d\"1\"\u003e"
33
+ },"likingUsers":[{"userId":"12050031834138523626"},{"userId":"02480044899494396240"},{"userId":"09211568646312498198"},{"userId":"12661663256831413124"},{"userId":"16325469533121512015"},{"userId":"11148217089971950025"},{"userId":"08982619185204047523"},{"userId":"10240643020089782013"},{"userId":"17255393626595171300"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://feeds.feedburner.com/MagnusHolm","title":"The timeless repository","htmlUrl":"http://timeless.judofyr.net/"}},{
34
+ "isReadStateLocked":true,"crawlTimeMsec":"1296902500202","id":"tag:google.com,2005:reader/item/c76ed9cff2bd3e4f","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/read"],"title":"Pull requests are not conversations","published":1296902311,"updated":1296902311,"alternate":[{"href":"http://feedproxy.google.com/~r/antirez/~3/AR0fbQF0KgQ/pull-requests-are-not-conversations.html","type":"text/html"
35
+ }],"summary":{"direction":"ltr","content":"\u003cdiv\u003e\u003cdiv style\u003d\"clear:both\"\u003e\u003c/div\u003e\u003cdiv\u003eI don't remember anything that changed my work flow in a so big way as git did in the latest two years.\nI will not argue too much about that, as you probably experimented the effects yourself if you are reading this blog post, or perhaps you are among the guys I should say thanks for the fact I started using git.\n\u003cbr\u003e\u003cbr\u003e\n\nBut if git is cool, \u003ca href\u003d\"http://github.com\"\u003eGithub\u003c/a\u003e makes it cooler for sure. Github is like a \u003cb\u003emarket\u003c/b\u003e for git, where code gets exchanged, shared, publicly hacked. In short I love git and github. Still I think that github \u003cb\u003epull requests\u003c/b\u003e are posing a problem in the open source development world, and this post is all about this.\n\u003ch3\u003eHow it used to work\u003c/h3\u003e\nBefore git and pull requests the obvious way to contribute to a large enough open source project was to write a message in the mailing list of the project, asking about what the feelings are about adding a given feature.\n\u003cbr\u003e\u003cbr\u003e\n\nIt was possible to start the discussion providing \u003ci\u003ealso\u003c/i\u003e a patch, but not required. When I say \u003ci\u003ealso\u003c/i\u003e I mean, along with all the rationale for adding the features, what the problems and complexities are, what the gains, and so forth.\n\u003cbr\u003e\u003cbr\u003e\n\nLike a painting uses colors but is not, after all, \u003ci\u003eabout colors\u003c/i\u003e, so a software project uses code, but is after all, about design. Code can be used to prove your point, to show a way to obtain the effect you are seeking, but the most important point is the design of a new feature. The ability to resist to the temptation of adding things that are not useful but just appear to be useful, because perhaps there is a defect in some other part of the design, or because there is a not so obvious but simple way to get the same effect. And where to discuss this kind of things if not in the project mailing list?\n\u003cbr\u003e\u003cbr\u003e\n\nAs github is a market for code, the project mailing list is a market for the project design (more and more together with Twitter, in my experience).\n\u003cbr\u003e\u003cbr\u003e\n\nSo that is how contributions used to work, and how most of the Linux kernel itself was built.\nBut I've the feeling that github pull requests are bringing a new way of thinking to the table, that is preventing a lot of interesting discussions from happening, and at the same time is wasting a lot of coding resources.\n\u003ch3\u003ePull requests\u003c/h3\u003e\nMy point so far has been that contributing to code should be a conversation about design, with some code in order to support that conversation or to finally implement what seems a good design in a tangible form.\n\u003cbr\u003e\u003cbr\u003e\n\nIn this conversation pull requests are the stage when a developer asks another one: \u0026quot;Hey, let\u0026#39;s merge that\u0026quot;. In github pull requests this happens in a very private way. This should be the very, very final stage of a contribution, but guess what, now most of the time is the \u003cb\u003efirst stage\u003c/b\u003e of a contribution, that directly starts with a pull request, about a feature not publicly discussed, possibly not needed, or that is actually related to something else that is already a work in progress, and so forth. In the end this means that most of pull requests are not going to get merged. At least this is what happens with Redis.\n\u003cbr\u003e\u003cbr\u003e\n\nPull requests used in this way are removing value from a conversation, that should otherwise be very different and public. Like:\n\u003cul\u003e\u003cli\u003eTo mailing list: Hey guys, I've this idea what do you think?\u003c/li\u003e\n\n\u003cli\u003e(Optionally) I've implemented a proof of concept, it's not finished but I think it shows my point: this is my topic branch.\u003c/li\u003e\n\n\u003cli\u003eBla bla bla, arguing about usefulness, alternatives, what can be improved and so forth.\u003c/li\u003e\n\n\u003cli\u003eFinally, if the feature is accepted: code code code.\u003c/li\u003e\n\n\u003cli\u003eAnd a new message arrives in the mailing list: ok, that's my code, let's merge if it seems sane enough.\u003c/li\u003e\n\n\u003c/ul\u003e\nWe still take advantage of github that allows to make our topic branches explorable and testable. But the whole process is now sane and design centric. And since the discussion does not start with a \u0026quot;let\u0026#39;s merge\u0026quot; there is no need to provide a perfectly finished code, or code at all.\n\u003cbr\u003e\u003cbr\u003e\n\nPlease if you have comments use \u003ca href\u003d\"http://news.ycombinator.com/item?id\u003d2182873\"\u003ehacker news\u003c/a\u003e instead of the blog comments.\u003c/div\u003e\u003cdiv\u003e\u003cdiv\u003epost read 17482 times\u003csup\u003e\u003ca href\u003d\"http://antirez.com/page/uniquevisitors\"\u003e*\u003c/a\u003e\u003c/sup\u003e (average 421.9 visits/day)\u003c/div\u003ePosted at 10:38:31 \u003ca href\u003d\"http://antirez.com/post/pull-requests-are-not-conversations.html\"\u003epermalink\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/post/pull-requests-are-not-conversations.html\"\u003e5 comments\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/print.php?postid\u003d224\"\u003eprint\u003c/a\u003e | \u003ca href\u003d\"http://postli.com/post?t\u003dPull+requests+are+not+conversations\u0026amp;u\u003dhttp%3A%2F%2Fantirez.com%2Fpost%2Fpull-requests-are-not-conversations.html\"\u003epost it\u003c/a\u003e | \u003ca href\u003d\"http://technorati.com/search/http://antirez.com/post/pull-requests-are-not-conversations.html\"\u003eView blog reactions\u003c/a\u003e\u003c/div\u003e\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/antirez/~4/AR0fbQF0KgQ\" height\u003d\"1\" width\u003d\"1\"\u003e"
36
+ },"likingUsers":[{"userId":"17436240768195182534"},{"userId":"07381089235220823131"},{"userId":"13908453888579385086"},{"userId":"06808395769643365537"},{"userId":"02714205789091616240"},{"userId":"02821366625847833745"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://antirez.com/rss","title":"antirez weblog","htmlUrl":"http://antirez.com"}},{"isReadStateLocked":true,"crawlTimeMsec":"1296707712222","id":"tag:google.com,2005:reader/item/62394bf054eb9c34","categories":["user/05185502537486227907/label/Dev | Ruby",
37
+ "user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/read","Miscellaneous","notweet"],"title":"Thanking Ruby Inside’s January 2011 Sponsors","published":1296707609,"updated":1296707696,"replies":[{"href":"http://www.rubyinside.com/thanking-ruby-insides-january-2011-sponsors-4246.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/thanking-ruby-insides-january-2011-sponsors-4246.html/feed/atom","type":"application/atom+xml"}],"alternate":[
38
+ {"href":"http://feedproxy.google.com/~r/RubyInside/~3/-q_MXzl6pa8/thanking-ruby-insides-january-2011-sponsors-4246.html","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003eIt's time for us to thank the companies who help to keep Ruby Inside going by kindly sponsoring our work. So.. thank you! (And thank you for reading too, naturally.)\u003c/p\u003e\n\u003ch3\u003eRed Dirt Ruby Conference - April 21-22, 2011 (Oklahoma City)\u003c/h3\u003e\n\u003cp\u003eThe \u003ca href\u003d\"http://engine.adzerk.net/redirect/0/3179/3005/1313/6ceb66850ab2436caccd9d0375aacad3/19/24/3543/634323040639640186?keywords\u003d\"\u003eRed Dirt Ruby Conference\u003c/a\u003e is a Ruby conference taking place in Oklahoma City this April. It's shaping up to be a great event with keynotes from Aaron Patterson (Nokogiri) and Dr Nic Williams (Engine Yard). Expect sunny weather with temperatures in the low 70s..\u003c/p\u003e\n\u003ch3\u003eSpreadable - Viral Marketing Tools for your Apps\u003c/h3\u003e\n\u003cp\u003e\u003ca href\u003d\"http://engine.adzerk.net/redirect/0/3126/2952/1313/85766cb05a5245c0b7b7980b55224ca0/19/24/3398/634312088369843266?keywords\u003d\"\u003eSpreadable\u003c/a\u003e is a powerful 'tell a friend' referral tool you can easily plug into your site. It brings your app powerful viral social tools you and your users can use to spread the word about your work.\u003c/p\u003e\n\u003ch3\u003eJaconda - A Chat System for Project Teams\u003c/h3\u003e\n\u003cp\u003e\u003ca href\u003d\"http://engine.adzerk.net/redirect/0/3128/2954/1313/fc6773af6f384b7186b7ca87733e02fd/19/24/3400/634312088369843266\"\u003eJaconda\u003c/a\u003e is a chat system designed for teams working on projects. It works from the browser, your IM app, or your phone, and lets you chat, share files, and keep up with tickets and project activity you can have sent automatically to your rooms.\u003c/p\u003e\n\u003ch3\u003eNew Relic — On-demand Application Management\u003c/h3\u003e\n\u003cp\u003e\u003ca href\u003d\"http://www.newrelic.com/RPMlite-rails.html?utm_source\u003drubyinside\u0026amp;utm_medium\u003dbanner\u0026amp;utm_content\u003d125x125\u0026amp;utm_campaign\u003drpm\"\u003eNew Relic\u003c/a\u003e is a Java and Ruby (and now PHP too!) application performance and reliability monitoring and management service that started life as a Rails-only service. With New Relic you can monitor your apps, find slow transactions, see specific SQL queries, and even run a code-level thread profile.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eTrivia I Always Love To Point Out: New Relic is an anagram of founder \"Lew Cirne\"'s name!\u003c/em\u003e\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003d-q_MXzl6pa8:dARA9ByJGu8:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003d-q_MXzl6pa8:dARA9ByJGu8:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003d-q_MXzl6pa8:dARA9ByJGu8:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/-q_MXzl6pa8\" height\u003d\"1\" width\u003d\"1\"\u003e"
39
+ },"author":"Peter Cooper","likingUsers":[],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/","title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"isReadStateLocked":true,"crawlTimeMsec":"1296527648669","id":"tag:google.com,2005:reader/item/6340ffe3419c23d5","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/read","News","Tools"],"title":"RubyGems 1.5.0 Released: Now Supports Ruby 1.9.2",
40
+ "published":1296527632,"updated":1297571923,"replies":[{"href":"http://www.rubyinside.com/rubygems-1-5-0-released-now-supports-ruby-1-9-2-4240.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/rubygems-1-5-0-released-now-supports-ruby-1-9-2-4240.html/feed/atom","type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/vE86DE54JM0/rubygems-1-5-0-released-now-supports-ruby-1-9-2-4240.html","type":"text/html"}],"content":{"direction":"ltr",
41
+ "content":"\u003cp\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/02/rubygems.png\" width\u003d\"98\" height\u003d\"98\" alt\u003d\"rubygems.png\" style\u003d\"float:left;margin-right:12px;margin-bottom:12px\"\u003eRyan Davis has \u003ca href\u003d\"http://blog.zenspider.com/2011/01/rubygems-update-150-released.html\"\u003eannounced the release of RubyGems 1.5.0\u003c/a\u003e. It comes just a month after the \u003ca href\u003d\"http://blog.zenspider.com/2010/12/rubygems-version-140-has-been.html\"\u003erelease of 1.4\u003c/a\u003e which, notoriously, didn't work with Ruby 1.9.2. These problems have now all been ironed out and Ruby 1.8 and 1.9 users alike can safely upgrade (fingers crossed).\u003c/p\u003e\n\u003cp\u003eRubyGems is the popular (and official - as of Ruby 1.9) Ruby package manager with which most significant Ruby libraries and tools are distributed. The 1.5 release sees it pick up a few bug fixes and some enhancements, including:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eRuby 1.9 support\u003c/li\u003e\n\u003cli\u003ePost-build hooks that can cancel the gem install\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eGem.find_files\u003c/code\u003e is now 40% faster (on Ruby 1.9)\u003c/li\u003e\n\u003cli\u003eBetter errors for corrupt Gem files, including paths\u003c/li\u003e\n\u003cli\u003eA new \u003ca href\u003d\"https://github.com/rubygems/rubygems/blob/master/UPGRADING.rdoc\"\u003eUPGRADING documentation file\u003c/a\u003e to help with Ruby 1.9-related issues\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003egem update\u003c/code\u003e no longer erroneously tries to update RubyGems itself by default\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eTo upgrade to RubyGems 1.5.0, run:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003egem update --system\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eAlternatively, you can learn more in the new \u003ca href\u003d\"https://github.com/rubygems/rubygems/blob/master/UPGRADING.rdoc\"\u003eUPGRADING documentation\u003c/a\u003e, or if you don't already have RubyGems for some reason, you can \u003ca href\u003d\"https://rubygems.org/pages/download\"\u003edownload it from RubyGems.org\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003eUPDATE:\u003c/strong\u003e \u003cstrike\u003eRubyGems 1.5.0 and Bundler are not the best of friends! If you're depending on Bundler tonight, don't install RubyGems 1.5.0 just yet. However, a 1.5 compatible version of Bundler is due within the next 24 hours.\u003c/strike\u003e A new version of Bundler has been released, supporting RubyGems 1.5.0. Upgrade now :-)\u003c/em\u003e\u003c/p\u003e\n\u003cp style\u003d\"padding:8px;background-color:#ff9\"\u003e\u003cem\u003e[ad]\u003c/em\u003e \u003ca href\u003d\"http://engine.adzerk.net/redirect/0/3126/2952/1313/85766cb05a5245c0b7b7980b55224ca0/19/24/3398/634312088369843266?keywords\u003d\"\u003eSpreadable\u003c/a\u003e is a powerful 'tell a friend' referral tool you can easily plug into your site. It brings your app powerful viral social tools you and your users can use to spread the word about your work.\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dvE86DE54JM0:BzavB8TFU4Q:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dvE86DE54JM0:BzavB8TFU4Q:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003dvE86DE54JM0:BzavB8TFU4Q:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/vE86DE54JM0\" height\u003d\"1\" width\u003d\"1\"\u003e"
42
+ },"author":"Peter Cooper","likingUsers":[{"userId":"02955686121905988538"},{"userId":"11132888979039710046"},{"userId":"18253276447546087020"},{"userId":"05272376215565670505"},{"userId":"06808395769643365537"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/","title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"isReadStateLocked":true,"crawlTimeMsec":"1296425409383","id":"tag:google.com,2005:reader/item/041eb66f0ce770fe","categories":["user/05185502537486227907/label/Dev | Ruby",
43
+ "user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/read"],"title":"On Camping vs Sinatra","published":1296423381,"updated":1296423381,"alternate":[{"href":"http://timeless.judofyr.net/on-camping-vs-sinatra","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003eMany years ago there was a tiny little \u003cstrike\u003eladybug\u003c/strike\u003e Ruby web framework called \u003ca href\u003d\"http://whywentcamping.com/\"\u003eCamping\u003c/a\u003e. Compared Rails’ thousands of lines of code, camping.rb was a tempting alternative for simple applications:\u003c/p\u003e\n\u003ccenter\u003e\u003cimg src\u003d\"https://github.com/camping/camping/raw/master/extras/images/little-wheels.png\"\u003e\u003c/center\u003e\u003chr\u003e\n\u003cp\u003eWelcome to the present, where every test framework ships with its own micro framework. Or was it the other way? Anyway, my point was: With so many alternatives, why would you want to go Camping? Why not Sinatra?\u003c/p\u003e\n\n\u003cp\u003eSome time ago, this discussion started over at the \u003ca href\u003d\"http://hackety-hack.com/\"\u003eHackety Hack\u003c/a\u003e mailing list, so I decided to chime in and present \u003cstrong\u003e\u003cem\u003eSIX (UNIMPRESSIVE) REASONS CAMPING IS BETTER THAN YOU WOULD IMAGINE\u003c/em\u003e\u003c/strong\u003e.\u003c/p\u003e\n\n\u003cp\u003ePlease don’t mistake me, this is not \u003cstrong\u003e\u003cem\u003eSIX (UNIMPRESSIVE) REASONS CAMPING IS BETTER THAN SINATRA\u003c/em\u003e\u003c/strong\u003e or even \u003cstrong\u003e\u003cem\u003eSIX (UNIMPRESSIVE) REASONS YOU SHOULD DROP EVERYTHING YOU HAVE IN YOUR HAND RIGHT NOW AND START USING CAMPING\u003c/em\u003e\u003c/strong\u003e. All I’m saying is that Camping gets so many things \u003cem\u003eright\u003c/em\u003e. Not necessarily in very few lines of code or very fast, but nonetheless: I look at Camping code and nod to myself: “Yeah, this is probably the \u003cem\u003ecorrect\u003c/em\u003e way to do it”.\u003c/p\u003e\n\n\u003cp\u003eOf course, this doesn’t really matter. If we cared about correctness, we would program in Haskell, not some language where monkey patching is acceptable in production code. As long as you’re comfortable in Sinatra (or any other framework), you should continue doing that.\u003c/p\u003e\n\n\u003cp\u003eWithout any more ado:\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode\u003e#!/usr/bin/ruby\nSIX (UNIMPRESSIVE) # Markdown version:\nREASONS CAMPING IS BETTER # 1) Download http://timeless.judofyr.net/camping-vs-sinatra.rb\nTHAN YOU WOULD IMAGINE # 2) ruby camping-vs-sinatra.rb\n\nreasons.push(COMMUNITY) do %%\n Yes, Sinatra has a big community, but Camping definitely has a great\n community too. Size doesn\u0026#39;t always matter. Because there are so few users,\n it means every single issue gets full attention.\n\n If you\u0026#39;re only looking at the GitHub repo or ruby.reddit.com, Camping\n might seem a little dead, but that\u0026#39;s because everything happens on the\n mailing lists so it\u0026#39;s not always visible from the \u0026quot;outside\u0026quot;. (And other\n times, we\u0026#39;re simply restin\u0026#39;, just waiting for a question, suggestion or\n comment.)\n\n Besides, I don\u0026#39;t allow Camping to disappear. Not because I need it in my\n business or something like that, but because the code is so fucking great.\n I simply won\u0026#39;t allow it to die. Therefore I will *always* do my best to\n help people who are camping (just ask Eric Mill on this mailing list).\n%%%%% end\n\nreasons.push(UNPOLLUTED) do %%\n In Sinatra it\u0026#39;s a norm (whether you use Sinatra::Base or not), in Camping\n it\u0026#39;s the law:\n\n Camping.goes :Blog\n module Blog; end\n\n Camping.goes :Wiki\n module Wiki; end\n\n Every application lives under its own namespace. Yes, it requires a few\n more characters, but when you think about it, why *should* we allow are\n apps to run directly under the global namespace? That\u0026#39;s surely not how we\n design our other Ruby code. What makes it so different? Shouldn\u0026#39;t you for\n instance be able to `require \u0026quot;app\u0026quot;` and `include App::Helpers` somewhere\n else?\n\n Think of the environment; reduce your pollution!\n%%%%% end\n\nreasons.push(RESTful) do %%\n A central idea in REST is the concept of a resource, and that you can call\n methods on the resource (in order to get a representation of it). How would\n you apply these ideas in Ruby? What about this?\n\n class Posts\n def get; end\n def post; end\n end\n\n I would say this fits the description perfectly. You can instantiate\n instances of this class (with different parameters etc.) for each request,\n and then call methods on it. Guess how it looks in Camping?\n\n module App::Controllers\n class Posts\n def get; end\n def post; end\n end\n end\n\n The best part: Camping doesn\u0026#39;t care if you use GET, DELETE, PROPFIND or\n HELLOWORD; every method is threated equally. One of the early ideas of HTTP\n was that you could easily extend it with your own methods for your own\n needs, and Camping is a perfect match for these cases!\n%%%%% end\n\nreasons.push(RUBY) do %%\n Ruby has wonderful features such as classes, inheritance, modules and\n methods. Why should every single DSL replace these features by blocks?\n Often, all they do is to hide details, without improving anything else than\n line count. Let me show you an example:\n\n get \u0026#39;/posts\u0026#39; do\n # code\n end\n\n Now answer me:\n\n 1. Where is this code stored?\n 2. How do I override the code?\n 3. What happens if I call `get \u0026#39;/posts\u0026#39;` again?\n\n Not quite sure? Let\u0026#39;s have a look at Camping:\n\n module App::Controllers\n class Posts\n def get\n # code\n end\n end\n end\n\n Since this is just \u0026quot;plain\u0026quot; Ruby, it\u0026#39;s much simpler:\n\n ### 1. Where is this code stored?\n\n The code is stored as a method, and we can easily play with it:\n\n Posts.instance_methods(false) # \u003d\u0026gt; [:get]\n Posts.instance_method(:get) # \u003d\u0026gt; #\u0026lt;UnboundMethod: Posts#get\u0026gt;\n # Given post.is_a?(Posts)\n post.methods(false) # \u003d\u0026gt; [:get]\n post.method(:get) # \u003d\u0026gt; #\u0026lt;Method: Posts#get\u0026gt;\n\n ### 2. How do I override the code?\n\n Just like you would override a method:\n\n class App::Controllers::Posts\n def get\n # override\n end\n end\n\n # or, if post.is_a?(Posts)\n\n def post.get\n # override\n end\n\n ### 3. What happens if I call `class Posts` again?\n\n Because Ruby has open classes, we know that it would have no effect at all.\n\n ------------\n\n Another advantage of having resources as classes (and not as blocks):\n\n module IUseTheseMethodsALot\n def get; end\n end\n\n module App::Controllers\n class Posts\n include IUseTheseMethodsALot\n end\n\n class Users\n include IUseTheseMethodsALot\n end\n end\n%%%%% end\n\nreasons.push(NAMING) do %q%\n In Camping you\u0026#39;ll have to give every resource a name, while in Sinatra\n they\u0026#39;re always anonymous. By giving resources a name you have a way of\n referencing them, which can be very convenient:\n\n post \u0026#39;/issue\u0026#39; do\n issue \u003d Issue.create(params[:issue])\n redirect \u0026quot;/issue/#{issue.id}/overview\u0026quot;\n end\n\n Since every resource is anonymous in Sinatra, you\u0026#39;re forced to hard-code\n the path. Not very elegant, and it can be a pain to update the code if you\n for instance want to move all urls from issue/ to i/. Camping\u0026#39;s solution:\n\n class Issue\n def post\n issue \u003d Issue.create(@input.issue)\n redirect R(IssueOverview, issue)\n end\n end\n\n The R method in Ruby returns the URL to a resource (which takes one\n parameter). Camping automatically calls #to_param on the arguments, so you\n can safely pass in ActiveRecord objects too. If you want to change the\n route to IssueOverview, you can do this in *one* place and you\u0026#39;re done.\n%%%%% end\n\nreasons.push(RELOADING) do %%\n $ camping app.rb\n ** Starting Mongrel on 0.0.0.0:3301\n\n The Camping Server provides code reloading (so you don\u0026#39;t need to restart\n the server while you develop your app) that works out of the box on *all*\n platforms (including Windows). We actually care about our Windows users!\n%%%%% end\n \u0026#39;\u0026#39;\n \u0026#39;\u0026#39;\nBEGIN {def Object.const_missing(m);m.to_s end;def method_missing(*a)a[1]\u003d\n$h.pop if a[1]\u003d\u003d$h;$h.push(a) end;$h \u003d [];def reasons; $reas ||\u003d {};end;\u0026#39;\u0026#39;\ndef reasons.push(r,\u0026amp;b);self[r]\u003db.call;end;END {puts h\u003d$h*\u0026#39; \u0026#39;,\u0026#39;\u003d\u0026#39;*h.size,\u0026#39;\u0026#39;\nreasons.each { |name, val| puts name, \u0026#39;-\u0026#39;*name.size, val.gsub(/^ /,\u0026#39;\u0026#39;),\u0026#39;\u0026#39;\n \u0026#39;\u0026#39;\n}}} # Please keep all my mustaches intact. // Magnus Holm\u003c/code\u003e\u003c/pre\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/MagnusHolm/~4/cxsiXXeEoms\" height\u003d\"1\" width\u003d\"1\"\u003e"
44
+ },"likingUsers":[{"userId":"05275469297961036779"},{"userId":"12739516975816816933"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://feeds.feedburner.com/MagnusHolm","title":"The timeless repository","htmlUrl":"http://timeless.judofyr.net/"}},{"isReadStateLocked":true,"crawlTimeMsec":"1296000981828","id":"tag:google.com,2005:reader/item/2ab9adb6574222c1","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/read",
45
+ "Cool","News"],"title":"Clever Algorithms: A Free Book of Nature-Inspired Ruby Recipes","published":1296000829,"updated":1297576700,"replies":[{"href":"http://www.rubyinside.com/clever-algorithms-a-free-book-of-nature-inspired-ruby-recipes-4227.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/clever-algorithms-a-free-book-of-nature-inspired-ruby-recipes-4227.html/feed/atom","type":"application/atom+xml"}],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/aJ1cgmODpTk/clever-algorithms-a-free-book-of-nature-inspired-ruby-recipes-4227.html",
46
+ "type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003e\u003ca href\u003d\"http://www.cleveralgorithms.com/\"\u003e\u003cimg src\u003d\"http://www.rubyinside.com/wp-content/uploads/2011/01/cleveralgorithms.png\" width\u003d\"177\" height\u003d\"267\" alt\u003d\"cleveralgorithms.png\" style\u003d\"float:right;margin-bottom:12px;margin-left:12px;border:1px #000000 solid\"\u003e\u003c/a\u003e\u003ca href\u003d\"http://www.cleveralgorithms.com/\"\u003eClever Algorithms\u003c/a\u003e is a newly released book by Jason Brownlee PhD that describes 45 algorithms from the Artificial Intelligence (AI) field with Ruby-based examples. It's well produced and, notably, \u003ci\u003efree\u003c/i\u003e in its PDF and online formats. A print copy is available at a small cost.\u003c/p\u003e\n\u003cp\u003eThe book kicks off with a chapter of background regarding AI and its problem domains and moves on to an array of algorithms in the probabilistic, neural networking, stochastic, swarm, and evolutionary spaces.\u003c/p\u003e\n\u003cp\u003eRuby purists will note that even though the demonstrations are in Ruby, they're not very \u003ci\u003eRuby like\u003c/i\u003e. Classes are rarely defined and using methods defined in the \u003ccode\u003emain\u003c/code\u003e context as functions is the order of the day. Nonetheless, the book remains well written and interesting and the Ruby code - as generic as it is - will nonetheless help Rubyists get the idea behind many of the processes demonstrated.\u003c/p\u003e\n\u003cblockquote\u003e\u003cp\u003eThis book provides a handbook of algorithmic recipes from the fields of Metaheuristics, Biologically Inspired Computation and Computational Intelligence that have been described in a complete, consistent, and centralized manner. These standardized descriptions were carefully designed to be accessible, usable, and understandable.\u003c/p\u003e\n\u003cp\u003eMost of the algorithms described in this book were originally inspired by biological and natural systems, such as the adaptive capabilities of genetic evolution and the acquired immune system, and the foraging behaviors of birds, bees, ants and bacteria. An encyclopedic algorithm reference, this book is intended for research scientists, engineers, students, and interested amateurs.\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e\u003ccite\u003eJason Brownlee\u003c/cite\u003e\u003c/p\u003e\n\u003cp\u003eCheck out Jason's book at \u003ca href\u003d\"http://www.cleveralgorithms.com/\"\u003ecleveralgorithms.com\u003c/a\u003e and the \u003ca href\u003d\"https://github.com/jbrownlee/CleverAlgorithms\"\u003econtent and code are in this GitHub repository.\u003c/a\u003e\u003c/p\u003e\n\u003cp style\u003d\"padding:8px;background-color:#ff9\"\u003e\u003cem\u003e[ad]\u003c/em\u003e \u003ca href\u003d\"http://engine.adzerk.net/redirect/0/3128/2954/1313/fc6773af6f384b7186b7ca87733e02fd/19/24/3400/634312088369843266\"\u003eJaconda\u003c/a\u003e is a chat system designed for teams working on projects. It works from the browser, your IM app, or your phone, and lets you chat, share files, and keep up with tickets and project activity you can have sent automatically to your rooms.\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003daJ1cgmODpTk:beNsNUsHPq0:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003daJ1cgmODpTk:beNsNUsHPq0:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003daJ1cgmODpTk:beNsNUsHPq0:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/aJ1cgmODpTk\" height\u003d\"1\" width\u003d\"1\"\u003e"
47
+ },"author":"Peter Cooper","likingUsers":[{"userId":"02955686121905988538"},{"userId":"17767811246110718090"},{"userId":"00983790994293744482"},{"userId":"17267561484850801618"},{"userId":"00655242585945262244"},{"userId":"14508535314649680529"},{"userId":"16067110809823480656"},{"userId":"07870146926958072805"},{"userId":"15638623306692727372"},{"userId":"08962340293270249083"},{"userId":"03647909449507125994"},{"userId":"04828842218541785042"},{"userId":"18253276447546087020"},{"userId":"07699240974340315355"
48
+ },{"userId":"00053104979490566885"},{"userId":"05272376215565670505"},{"userId":"10868561950768813169"},{"userId":"02312042911823059098"},{"userId":"11496849258748853027"},{"userId":"06808395769643365537"},{"userId":"11827983218694997518"},{"userId":"17838902486070354043"},{"userId":"05351348778449756836"},{"userId":"14142161122852127123"},{"userId":"13278723434297751093"},{"userId":"14780213955601263738"},{"userId":"04947588929828696569"},{"userId":"01642209201483437263"},{"userId":"05370333769261935271"
49
+ },{"userId":"02471903306039536256"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/","title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}},{"isReadStateLocked":true,"crawlTimeMsec":"1295883069186","id":"tag:google.com,2005:reader/item/49e222c75e2d504d","categories":["user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/read"],"title":"About the new INFO command, Diskstore and Cluster ETA",
50
+ "published":1295882115,"updated":1295882115,"alternate":[{"href":"http://feedproxy.google.com/~r/antirez/~3/d5G5U_wVL9I/new-info-diskstore-cluster-eta.html","type":"text/html"}],"summary":{"direction":"ltr","content":"\u003cdiv\u003e\u003cdiv style\u003d\"clear:both\"\u003e\u003c/div\u003e\u003cdiv\u003eWith this blog post I'll try to keep you posted on the progresses of Redis, the recent changes to the INFO output, and the ETA for diskstore and cluster.\n\u003cbr\u003e\u003cbr\u003e\n\n\u003ch3\u003eNew INFO output\u003c/h3\u003e\nDuring the week end I managed to improve the INFO output that was rapidly becoming a mess ;)\nINFO is a very interesting command for Redis users, but there are a few issues that need to be addressed:\n\u003cbr\u003e\u003cbr\u003e\n\n\u003cul\u003e\u003cli\u003eToo much output., not separated by section. This has many drawbacks: complex to read for humans. Time consuming to generate as it can take more than a millisecond sometime, for some Redis uses this is not ideal.\u003c/li\u003e\n\n\u003cli\u003eCollisions between the roles of INFO and CONFIG GET.\u003c/li\u003e\n\n\u003cli\u003eMore information needed, sometimes even super verbose. Things that we don't like to show in the default INFO output.\u003c/li\u003e\n\n\u003c/ul\u003e\nTo address these problems what I did was:\n\u003cbr\u003e\u003cbr\u003e\n\n\u003cul\u003e\u003cli\u003eNow the INFO output is divided into sections, even from the point of view of human eyes.\u003c/li\u003e\n\n\u003cli\u003eIt is possible to ask for a specific section, passing one argument to INFO. Example: \u003cb\u003eINFO memory\u003c/b\u003e will only show information about a single section of the output.\u003c/li\u003e\n\n\u003cli\u003eFor default INFO will output only default sections, like if you called \u003cb\u003eINFO default\u003c/b\u003e. Instead \u003cb\u003eINFO all\u003c/b\u003e will output everything. So now it is possible to add a few very verbose sections that simply are not printed by default, but are printed if the INFO argument is \u003cb\u003eall\u003c/b\u003e or when the specific question is requested explicitly. On crashes or failed asserts all the sections are included in the stack trace.\u003c/li\u003e\n\n\u003c/ul\u003e\n\u003cbr\u003e\u003cbr\u003e\n\nAll this will NOT be merged into 2.2, but is already part of Redis unstable.\n\u003cbr\u003e\u003cbr\u003e\n\nAn example of the new output:\n\u003cbr\u003e\u003cbr\u003e\n\n\u003cpre\u003e\nredis\u0026gt; info\n# Server\nredis_version:2.3.0\nredis_git_sha1:9b45592c\nredis_git_dirty:1\narch_bits:64\nmultiplexing_api:kqueue\nprocess_id:17790\ntcp_port:6379\nuptime_in_seconds:1012\nuptime_in_days:0\nlru_clock:1661879\u003cbr\u003e\u003cbr\u003e# Clients\nconnected_clients:1\nclient_longest_output_list:0\nclient_biggest_input_buf:0\nblocked_clients:0\u003cbr\u003e\u003cbr\u003e# Memory\nused_memory:931360\nused_memory_human:909.12K\nused_memory_rss:1085440\nmem_fragmentation_ratio:1.17\nuse_tcmalloc:0\u003cbr\u003e\u003cbr\u003e... more sections ...\u003cbr\u003e\u003cbr\u003e\u003c/pre\u003e\n\u003cbr\u003e\u003cbr\u003e\n\nIt is possible to ask for specific sections. The following output shows one of the most interesting new sections:\n\u003cbr\u003e\u003cbr\u003e\n\n\u003cpre\u003e\nredis\u0026gt; info commandstats\n# Commandstats\ncmdstat_get:calls\u003d10019,usec\u003d62234,usec_per_call\u003d6.21\ncmdstat_set:calls\u003d10049,usec\u003d83995,usec_per_call\u003d8.36\ncmdstat_incr:calls\u003d10029,usec\u003d80895,usec_per_call\u003d8.07\ncmdstat_lpush:calls\u003d20091,usec\u003d138814,usec_per_call\u003d6.91\ncmdstat_lpop:calls\u003d10023,usec\u003d69270,usec_per_call\u003d6.91\ncmdstat_lrange:calls\u003d40126,usec\u003d2607061,usec_per_call\u003d64.97\ncmdstat_sadd:calls\u003d10048,usec\u003d68385,usec_per_call\u003d6.81\ncmdstat_spop:calls\u003d10009,usec\u003d59072,usec_per_call\u003d5.90\ncmdstat_mset:calls\u003d10049,usec\u003d128621,usec_per_call\u003d12.80\ncmdstat_ping:calls\u003d20053,usec\u003d98911,usec_per_call\u003d4.93\ncmdstat_info:calls\u003d3,usec\u003d598,usec_per_call\u003d199.33\n\u003c/pre\u003e\n\u003cbr\u003e\u003cbr\u003e\n\n(against my MBA 11\u0026quot;, so typical servers will have smaller values)\n\u003cbr\u003e\u003cbr\u003e\n\nI hope this will help us debugging better user problems, as for instance big set intersections or calls to KEYS will be easily detected. Note that in fast computers the 1 microsecond resolution provided by gettimeofday() may not be enough, but it\u0026#39;s not a big problem, this is our equivalent of the MySQL \u0026quot;slow log\u0026quot;. As long as slow operations are logged, this is fine. For all the rest there is the count of calls that is good to understand what fast operations are stressing the server.\n\u003cbr\u003e\u003cbr\u003e\n\nAs you can see the new format is almost backward compatible with the old one. There is just to handle empty lines and to filter lines starting with \u0026quot;#\u0026quot;, that are now considered comments.\n\u003ch3\u003eDiskstore and Cluster ETA\u003c/h3\u003e\nAt the end of 2010 I was actively hacking again on Redis Cluster almost full time.\nNow the project is a bit delayed to be more aggressive developing diskstore brach.\n\u003cbr\u003e\u003cbr\u003e\n\nWhy, you may ask?\n\u003cbr\u003e\u003cbr\u003e\n\nBecause our logic was as follow:\n\u003cbr\u003e\u003cbr\u003e\n\n\u003cul\u003e\u003cli\u003eGreat implementation of in memory single instance\u003c/li\u003e\n\n\u003cli\u003eDecent option for datasets bigger than RAM in single instance\u003c/li\u003e\n\n\u003cli\u003eGood cluster support, now that the single node works well\u003c/li\u003e\n\n\u003c/ul\u003e\n\u003cbr\u003e\u003cbr\u003e\n\nThen we discovered that \u003ca href\u003d\"http://antirez.com/post/diskstore-btree-implementation.html\"\u003ethe Virtual Memory implementation is not as good as we want it to be\u003c/a\u003e. So I started working at diskstore, that is a much simpler project, already in alpha stage and working, in a public repository.\n\u003cbr\u003e\u003cbr\u003e\n\nIn a few weeks I'll start against with Redis Cluster, that will be Redis 3.0, while Redis 2.4 will provide a stable implementation of Diskstore.\n\u003cbr\u003e\u003cbr\u003e\n\nIn the meamtime Pieter Noordhuis is doing a awesome work in the area of client libraries. We were focusing too much on the server side, but clients are also a very important part, and a few clients can really benefit from some serious optimization Pieter is working on.\n\u003cbr\u003e\u003cbr\u003e\n\nA final note. It's almost one year at this point that I and Pieter are working at Redis thanks to VMware. I want to say thank you to this awesome company that made all you saw about Redis in the latest year possible.\n\u003cbr\u003e\u003cbr\u003e\n\n\u003cb\u003eEdit:\u003c/b\u003e so what's the ETA? More or less 2 months for beta quality (RC) diskstore, and at least six months for beta quality cluster. This are a bit optimistic forecasts, but after all in a few weeks we'll not have to work too much at 2.2 as it will ship stable, so there will be more time for the unstable branches.\u003c/div\u003e\u003cdiv\u003e\u003cdiv\u003epost read 6728 times\u003csup\u003e\u003ca href\u003d\"http://antirez.com/page/uniquevisitors\"\u003e*\u003c/a\u003e\u003c/sup\u003e (average 126.4 visits/day)\u003c/div\u003ePosted at 15:15:15 \u003ca href\u003d\"http://antirez.com/post/new-info-diskstore-cluster-eta.html\"\u003epermalink\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/post/new-info-diskstore-cluster-eta.html\"\u003e4 comments\u003c/a\u003e | \u003ca href\u003d\"http://antirez.com/print.php?postid\u003d223\"\u003eprint\u003c/a\u003e | \u003ca href\u003d\"http://postli.com/post?t\u003dAbout+the+new+INFO+command%2C+Diskstore+and+Cluster+ETA\u0026amp;u\u003dhttp%3A%2F%2Fantirez.com%2Fpost%2Fnew-info-diskstore-cluster-eta.html\"\u003epost it\u003c/a\u003e | \u003ca href\u003d\"http://technorati.com/search/http://antirez.com/post/new-info-diskstore-cluster-eta.html\"\u003eView blog reactions\u003c/a\u003e\u003c/div\u003e\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/antirez/~4/d5G5U_wVL9I\" height\u003d\"1\" width\u003d\"1\"\u003e"
51
+ },"likingUsers":[{"userId":"06244526826240331725"},{"userId":"02821366625847833745"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://antirez.com/rss","title":"antirez weblog","htmlUrl":"http://antirez.com"}},{"isReadStateLocked":true,"crawlTimeMsec":"1295806455505","id":"tag:google.com,2005:reader/item/c475f8c628b5e090","categories":["user/05185502537486227907/state/com.google/read","user/05185502537486227907/label/Dev | Ruby","user/05185502537486227907/state/com.google/reading-list"
52
+ ],"title":"Haters gonna HATEOAS","published":1264255523,"updated":1264255523,"alternate":[{"href":"http://timeless.judofyr.net/haters-gonna-hateoas","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cp\u003eEvery time someone mentions RESTful web services, there’s always that one person that has to chime in: “That’s not \u003cem\u003ereally\u003c/em\u003e RESTful, it’s just kinda RESTful.” I’d always filed that information away, under ‘things to learn later,’ and let it simmer in the back of my brain. I’ve finally looked into it, and they’re absolutely right: 99.99% of the RESTful APIs out there aren’t fully compliant with Roy Fielding’s conception of REST. Is that bad?\u003c/p\u003e\n\n\u003cp\u003eBefore we answer that question, let’s back up a bit: Why aren’t these web services RESTful? Just what is REST, anyway? REST was created by Roy Fielding in \u003ca href\u003d\"http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm\"\u003ehis dissertation\u003c/a\u003e if you’d like the full lowdown, but we’re more concerned with RESTful API design than we are in the full system. A more useful framework for this discussion is the \u003ca href\u003d\"http://martinfowler.com/articles/richardsonMaturityModel.html\"\u003eRichardson Maturity Model\u003c/a\u003e. Basically, it defines four possible levels of ‘REST support’:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e“The Swamp of POX.” You’re using HTTP to make RPC calls. HTTP is only really used as a tunnel.\u003c/li\u003e\n\n\u003cli\u003eResources. Rather than making every call to a service endpoint, you have multiple endpoints that are used to represent resources, and you’re talking to them. This is the very beginnings of supporting REST.\u003c/li\u003e\n\n\u003cli\u003eHTTP Verbs. This is the level that something like Rails gives you out of the box: You interact with these Resources using HTTP verbs, rather than always using POST.\u003c/li\u003e\n\n\u003cli\u003eHypermedia Controls. HATEOAS. You’re 100% REST compliant.\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2\u003eThe four levels of REST\u003c/h2\u003e\n\n\u003cp\u003eLet’s start at the bottom and work our way up.\u003c/p\u003e\n\n\u003cp\u003eNow, The Swamp of POX means that you’re using HTTP. Technically, REST services can be provided over any application layer protocol as long as they conform to certain properties. In practice, basically everyone uses HTTP. And since we’re discussing the creation of an API that conforms to REST rather than a system architecture based on the principles of REST, HTTP is a solid assumption on our part.\u003c/p\u003e\n\n\u003cp\u003eLevel one is where it starts to get interesting. REST’s ‘resources’ are the core pieces of data that your application acts on. These will often correspond to the Models in your application, if you’re following MVC. A blog, for example, would have Entry resources and Comment resources. API design at Level 1 is all about using different URLs to interact with the different resources in your application. To make a new Entry, you’d use \u003ccode\u003e/entries/make_new\u003c/code\u003e, but with comments, it’d be \u003ccode\u003e/comments/make_new\u003c/code\u003e. So far, so good. This stuff is easy.\u003c/p\u003e\n\n\u003cp\u003eHowever, there are a set of common operations that are performed on resources, and it seems kinda silly to make a new URI for every operation, especially when they’re shared. That’s where Level 2 comes in. We’re always going to need to perform CRUD operations on our resources, so why not find a way to share these operations across resources? We accomplish this using HTTP Verbs. If we want to get a list of Entries, we make a GET request to \u003ccode\u003e/entries\u003c/code\u003e, but if we want to create a new Entry, we \u003ccode\u003ePOST\u003c/code\u003e rather than GET. Pretty simple.\u003c/p\u003e\n\n\u003cp\u003eThe final level, Hypermedia Controls, is the one that everyone falls down on. There’s two parts to this: content negotiation and HATEOAS. Content negotiation is focused on different representations of a particular resource, and HATEOAS is about the discoverability of actions on a resource.\u003c/p\u003e\n\n\u003ch2\u003eContent Negotiation\u003c/h2\u003e\n\n\u003cp\u003eAt its simplest, this is something that Rails does right, too. Check out these lines from running \u003ccode\u003erails scaffold\u003c/code\u003e:\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode lang\u003d\"ruby\"\u003e\u003cspan\u003edef\u003c/span\u003e \u003cspan\u003eindex\u003c/span\u003e\n \u003cspan\u003e\u003cspan\u003e@\u003c/span\u003eentries\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003eEntry\u003c/span\u003e.\u003cspan\u003eall\u003c/span\u003e\n respond_to \u003cspan\u003edo \u003c/span\u003e|\u003cspan\u003eformat\u003c/span\u003e|\n format.\u003cspan\u003ehtml\u003c/span\u003e\n format.\u003cspan\u003exml\u003c/span\u003e { render \u003cspan\u003e\u003cspan\u003e:\u003c/span\u003exml\u003c/span\u003e \u003d\u0026gt; \u003cspan\u003e\u003cspan\u003e@\u003c/span\u003eentries\u003c/span\u003e }\n format.\u003cspan\u003ejson\u003c/span\u003e { render \u003cspan\u003e\u003cspan\u003e:\u003c/span\u003ejson\u003c/span\u003e \u003d\u0026gt; \u003cspan\u003e\u003cspan\u003e@\u003c/span\u003eentries\u003c/span\u003e }\n \u003cspan\u003eend\u003c/span\u003e\n\u003cspan\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\n\n\u003cp\u003eThis is content negotiation. You’re able to use MIME types to request a representation of a resource in different formats. Rails 3 has made this even better, with \u003ccode\u003erespond_to\u003c/code\u003e/\u003ccode\u003erespond_with\u003c/code\u003e:\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode lang\u003d\"ruby\"\u003e\u003cspan\u003eclass\u003c/span\u003e \u003cspan\u003eEntriesController\u003cspan\u003e \u003cspan\u003e\u0026lt;\u003c/span\u003e ApplicationController::Base\u003c/span\u003e\u003c/span\u003e\n\n respond_to \u003cspan\u003e\u003cspan\u003e:\u003c/span\u003ehtml\u003c/span\u003e, \u003cspan\u003e\u003cspan\u003e:\u003c/span\u003exml\u003c/span\u003e, \u003cspan\u003e\u003cspan\u003e:\u003c/span\u003ejson\u003c/span\u003e\n\n \u003cspan\u003edef\u003c/span\u003e \u003cspan\u003eindex\u003c/span\u003e\n \u003cspan\u003erespond_with\u003c/span\u003e(\u003cspan\u003e\u003cspan\u003e@\u003c/span\u003eentries\u003c/span\u003e \u003cspan\u003e\u003d\u003c/span\u003e \u003cspan\u003eEntry\u003c/span\u003e.\u003cspan\u003eall\u003c/span\u003e)\n \u003cspan\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\n\n\u003cp\u003eSuper simple. So why do I say people get this wrong? Well, this is a great usage of content negotiation, but there’s also one that almost everyone gets wrong. Content negotiation is the answer to the question, “How do I version my API?” The proper way to do this is with the \u003ccode\u003eAccepts\u003c/code\u003e header, and use a MIME type like \u003ccode\u003eapplication/yourcompany.v1+json\u003c/code\u003e. There’s a great article about this by Peter Williams \u003ca href\u003d\"http://barelyenough.org/blog/2008/05/versioning-rest-web-services/\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\n\u003ch2\u003eHATEOAS\u003c/h2\u003e\n\n\u003cp\u003eThe last constraint is incredibly simple, but nobody actually does it. It’s named Hypertext As The Engine Of Application State. I still haven’t decided how to pronounce the acronym, I always try to say “Hate ee ohs,” which sounds like a breakfast cereal. Anyway, let’s break this down. We’re using Hypertext, fine, that makes sense. But what’s it mean to be an engine? And application state?\u003c/p\u003e\n\n\u003cp\u003eIt’s all about state transitions. It’s right there in the name: Representational State Transfer. Your application is just a big state machine.\u003c/p\u003e\n\n\u003cp\u003e\u003cimg src\u003d\"http://i.imgur.com/9E28g.gif\" alt\u003d\"fsm\"\u003e\u003c/p\u003e\n\n\u003cp\u003eYour APIs should do this. There should be a single endpoint for the resource, and all of the other actions you’d need to undertake should be able to be discovered by inspecting that resource. Here’s an example of what our \u003ccode\u003eEntry\u003c/code\u003e XML might look like if Rails handled HATEOAS:\u003c/p\u003e\n\n\u003cpre\u003e\u003ccode\u003e\u0026lt;entry\u0026gt;\n \u0026lt;id\u0026gt;1337\u0026lt;/id\u0026gt;\n \u0026lt;author\u0026gt;Steve Klabnik\u0026lt;/author\u0026gt;\n \u0026lt;link rel \u003d \u0026quot;/linkrels/entry/newcomment\u0026quot;\n uri \u003d \u0026quot;/entries/1337/comments\u0026quot; /\u0026gt;\n \u0026lt;link rel \u003d \u0026quot;self\u0026quot;\n uri \u003d \u0026quot;/entries/1337\u0026quot; /\u0026gt;\n\u0026lt;/entry\u0026gt;\u003c/code\u003e\u003c/pre\u003e\n\n\u003cp\u003eWhen we GET a particular Entry, we discover where we can go next: we can make a new comment. It’s a discoverable action. The particular state we’re in shows what other states we can reach from here.\u003c/p\u003e\n\n\u003cp\u003eNow, when I said ‘nobody’ does this, what I meant was ‘for APIs.’ This is exactly how the Web works. Think about it. You start off on the homepage. That’s the only URL you have to know. From there, a bunch of links point you towards each state that you can reach from there. People would consider it ludicrous if they had to remember a dozen URLs to navigate a website, so why do we expect the consumers of our APIs to do so as well?\u003c/p\u003e\n\n\u003cp\u003eThere’s another benefit here as well: we’ve decoupled the URL itself from the action we’re having it perform. Think about it like the web: If we have a link that says “click here to make a new blog entry” and next week, we change it from \u003ccode\u003e/entries/new\u003c/code\u003e to \u003ccode\u003e/somethingelse/whatever\u003c/code\u003e, users of the web site (probably) won’t notice: they’re just clicking on the link that takes them where they need to go. If you changed the text to “click here to do something else” they wouldn’t expect it to be making an Entry anymore. By the same token, we can change the URI in our \u003ccode\u003e\u0026lt;link\u0026gt;\u003c/code\u003e tag, and a proper client will just automatically follow along. Brilliant!\u003c/p\u003e\n\n\u003ch2\u003eWhy aren’t we doing this already?\u003c/h2\u003e\n\n\u003cp\u003eWell, for one thing, the tooling just isn’t there to do this. Think of how web development was before Rails started emphasizing REST: some people got it right, but not many people cared. I know that I had teachers telling me that a \u003ccode\u003e\u0026lt;form\u0026gt;\u003c/code\u003e with \u003ccode\u003emethod\u003d\u0026quot;GET\u0026quot;\u003c/code\u003e was perfectly fine, and that the only real difference between \u003ccode\u003eGET\u003c/code\u003e and \u003ccode\u003ePOST\u003c/code\u003e is if the parameters are in the URL… but I digress. Until we make this kind of development easy to do, people aren’t going to do it. There’s also a serious lack of education on this topic. The web development community has been steadily improving as the web grows and matures, and so I hope that eventually we’ll see more people actually adopting HATEOAS and going ‘full RESTful.’\u003c/p\u003e\n\n\u003cp\u003eThere’s also some discussion about how useful extra constraints actually are. If this is such a big important deal, why aren’t more people doing it? I haven’t yet implemented a 100% RESTful API myself yet, so I can’t really say. I do believe that I’ll be giving it a shot in the future, and I think that as our collective understanding of Fielding’s work improves, we’ll eventually see the value in REST.\u003c/p\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/MagnusHolm/~4/0TTCctAVRns\" height\u003d\"1\" width\u003d\"1\"\u003e"
53
+ },"likingUsers":[{"userId":"05275469297961036779"},{"userId":"08854659178715462472"},{"userId":"02428719105696136930"},{"userId":"01158114289556449363"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://feeds.feedburner.com/MagnusHolm","title":"The timeless repository","htmlUrl":"http://timeless.judofyr.net/"}},{"isReadStateLocked":true,"crawlTimeMsec":"1295641215225","id":"tag:google.com,2005:reader/item/f65c838ec73fcc69","categories":["user/05185502537486227907/label/Dev | Ruby",
54
+ "user/05185502537486227907/state/com.google/reading-list","user/05185502537486227907/state/com.google/read","Linux Specific","Tools"],"title":"Rails Ready: Ruby and Rails on Ubuntu in One Line","published":1295641206,"updated":1296082846,"replies":[{"href":"http://www.rubyinside.com/rails-ready-ruby-and-rails-on-ubuntu-in-one-line-4214.html#comments","type":"text/html"},{"href":"http://www.rubyinside.com/rails-ready-ruby-and-rails-on-ubuntu-in-one-line-4214.html/feed/atom","type":"application/atom+xml"
55
+ }],"alternate":[{"href":"http://feedproxy.google.com/~r/RubyInside/~3/EaxgwBVjQx8/rails-ready-ruby-and-rails-on-ubuntu-in-one-line-4214.html","type":"text/html"}],"content":{"direction":"ltr","content":"\u003cblockquote\u003e\u003cp\u003eHow would you like to get a full Ruby on Rails stack up on Ubuntu with one command?\u003c/p\u003e\n\u003cp\u003eNow you can by running \u003ca href\u003d\"https://github.com/joshfng/railsready\"\u003eRails Ready.\u003c/a\u003e Rails Ready is a setup script that gets Ruby and Rails running on a fresh install of Ubuntu with one command (Tested on Ubuntu server 10.04 LTS (Long-term Support)).\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e\u003ccite\u003eAdam Stacoviak\u003c/cite\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href\u003d\"https://github.com/joshfng/railsready\"\u003eRails Ready\u003c/a\u003e is essentially just a shell script but one you might find useful if you're running Ubuntu (or - update - CentOS) and want to get the installation process done and over as quickly as possible. It follows on rather nicely to our last post: \u003ca href\u003d\"http://www.rubyinside.com/rails-installer-ruby-and-rails-on-windows-in-a-single-install-4201.html\"\u003eRuby Installer: Ruby and Rails on Windows in a Single, Easy Install\u003c/a\u003e!\u003c/p\u003e\n\u003cp\u003eIf you have the time or you're installing this on your main development machine, however, I would recommend following \u003ca href\u003d\"http://ryanbigg.com/2010/12/ubuntu-ruby-rvm-rails-and-you\"\u003eRyan Biggs' RVM based instructions\u003c/a\u003e (or \u003ca href\u003d\"http://www.rubyinside.com/how-to-install-ruby-1-9-2-and-rails-3-0-on-ubuntu-10-10-4148.html\"\u003emy equivalent screencast\u003c/a\u003e) because RVM gives you more developer-level control later on (such as \u003ca href\u003d\"http://rvm.beginrescueend.com/gemsets/\"\u003egem sets\u003c/a\u003e). \u003cem\u003e\u003cstrong\u003eUPDATE -\u003c/strong\u003e Josh has been working hard and says that Rails Ready \"now asks you if you want to build from source or install RVM\" - nice!\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eNonetheless, if you want to get a new Ubuntu (or CentOS) box running Rails as quickly as possible, Rails Ready is worth a try. The short version:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget --no-check-certificate https://github.com/joshfng/railsready/raw/master/railsready.sh \u0026amp;\u0026amp; bash railsready.sh\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eBefore running the above, though, be aware of the ramifications. You should probably take a look at \u003ccode\u003ehttps://github.com/joshfng/railsready/raw/master/railsready.sh\u003c/code\u003e yourself to see if it's suitable for you.\u003c/p\u003e\n\u003cdiv\u003e\n\u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dEaxgwBVjQx8:IbN2hxO1E3A:qj6IDK7rITs\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?d\u003dqj6IDK7rITs\" border\u003d\"0\"\u003e\u003c/a\u003e \u003ca href\u003d\"http://feeds.feedburner.com/~ff/RubyInside?a\u003dEaxgwBVjQx8:IbN2hxO1E3A:3H-1DwQop_U\"\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~ff/RubyInside?i\u003dEaxgwBVjQx8:IbN2hxO1E3A:3H-1DwQop_U\" border\u003d\"0\"\u003e\u003c/a\u003e\n\u003c/div\u003e\u003cimg src\u003d\"http://feeds.feedburner.com/~r/RubyInside/~4/EaxgwBVjQx8\" height\u003d\"1\" width\u003d\"1\"\u003e"
56
+ },"author":"Peter Cooper","likingUsers":[{"userId":"00983790994293744482"},{"userId":"01371570141234954600"},{"userId":"15998619181294633317"},{"userId":"18253276447546087020"},{"userId":"12445051381135499126"},{"userId":"03022308088960936241"},{"userId":"03730916351303545880"},{"userId":"14142161122852127123"},{"userId":"14308864270778157202"},{"userId":"05896016890297028843"},{"userId":"15398205570777958235"}],"comments":[],"annotations":[],"origin":{"streamId":"feed/http://www.rubyinside.com/feed/",
57
+ "title":"Ruby Inside","htmlUrl":"http://www.rubyinside.com/"}}]}
@@ -0,0 +1,40 @@
1
+ {"subscriptions":[{"id":"feed/http://feeds.feedburner.com/2modernDesignTalk","title":"2modern.com","categories":[{"id":"user/05185BEEF/label/Creative | Architecture","label":"Creative | Architecture"}],"sortid":"DAE0E290","firstitemmsec":"1209228548281","htmlUrl":"http://blog.2modern.com"},{"id":"feed/http://feeds.feedburner.com/37signals/beMH","title":"37signals.com/svn","categories":[{"id":"user/05185BEEF/label/Dev | Web","label":"Dev | Web"}],"sortid":"BF2AC838","firstitemmsec":"1206087839716",
2
+ "htmlUrl":"http://37signals.com/svn/posts"},{"id":"feed/http://feeds.feedburner.com/abriefmessage","title":"A Brief Message","categories":[{"id":"user/05185BEEF/label/Dev | Web","label":"Dev | Web"}],"sortid":"D464B681","firstitemmsec":"1196233325780","htmlUrl":"http://abriefmessage.com/"},{"id":"feed/http://adverbatims.blogspot.com/feeds/posts/default","title":"AdVerbatims - Overheard in Advertising","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"
3
+ }],"sortid":"0F06DB6E","firstitemmsec":"1206087839700","htmlUrl":"http://adverbatims.blogspot.com/"},{"id":"feed/http://www.amazingsuperpowers.com/RSS/RSSfeed.xml","title":"AmazingSuperPowers.com","categories":[{"id":"user/05185BEEF/label/Misc | Comics","label":"Misc | Comics"}],"sortid":"F2E0DF6B","firstitemmsec":"1230829439600","htmlUrl":"http://www.amazingsuperpowers.com"},{"id":"feed/http://feeds.feedburner.com/ambiescentblog","title":"Ambiescent","categories":[{"id":"user/05185BEEF/label/Misc | People",
4
+ "label":"Misc | People"}],"sortid":"1F9ED99E","firstitemmsec":"1206087839701","htmlUrl":"http://ambiescent.com"},{"id":"feed/http://antirez.com/rss","title":"Antirez.com","categories":[{"id":"user/05185BEEF/label/Dev | Ruby","label":"Dev | Ruby"}],"sortid":"F750F3A8","firstitemmsec":"1268407867929","htmlUrl":"http://antirez.com"},{"id":"feed/http://feeds2.feedburner.com/MacAppStorm","title":"AppStorm","categories":[{"id":"user/05185BEEF/label/Tech | Mac","label":"Tech | Mac"
5
+ }],"sortid":"B8349EF1","firstitemmsec":"1233256252813","htmlUrl":"http://mac.appstorm.net"},{"id":"feed/http://feeds.feedburner.com/badassjs","title":"Badass JavaScript","categories":[{"id":"user/05185BEEF/label/Dev | JavaScript","label":"Dev | JavaScript"}],"sortid":"6CB14E38","firstitemmsec":"1284403823532","htmlUrl":"http://badassjs.com/"},{"id":"feed/http://belleandariel.wordpress.com/feed/","title":"belle and ariel","categories":[{"id":"user/05185BEEF/label/Misc | People",
6
+ "label":"Misc | People"}],"sortid":"F56576AF","firstitemmsec":"1295834747034","htmlUrl":"http://belleandariel.wordpress.com"},{"id":"feed/http://feeds.feedburner.com/ucllc/brandnew","title":"Brand New","categories":[{"id":"user/05185BEEF/label/Creative | Design 2","label":"Creative | Design 2"}],"sortid":"430BE8BE","firstitemmsec":"1285587546886","htmlUrl":"http://www.underconsideration.com/brandnew/"},{"id":"feed/http://buttersafe.com/feed/","title":"Buttersafe - Updated Tuesdays and Thursdays",
7
+ "categories":[{"id":"user/05185BEEF/label/Misc | Comics","label":"Misc | Comics"}],"sortid":"E70A7DFA","firstitemmsec":"1270737740007","htmlUrl":"http://buttersafe.com"},{"id":"feed/http://camilledelrosario.com/myblog/?feed\u003drss2","title":"camille's inspirations","categories":[{"id":"user/05185BEEF/label/Misc | People","label":"Misc | People"}],"sortid":"C9514DCB","firstitemmsec":"1285062796391","htmlUrl":"http://camilledelrosario.com/myblog"},{"id":"feed/http://cleaneatingmachine.blogspot.com/feeds/posts/default",
8
+ "title":"Clean Eating Machines","categories":[{"id":"user/05185BEEF/label/Food","label":"Food"}],"sortid":"857F761E","firstitemmsec":"1293419334978","htmlUrl":"http://cleaneatingmachine.blogspot.com/"},{"id":"feed/http://feedproxy.google.com/creattica","title":"Creattica Daily","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"B3DFEFAE","firstitemmsec":"1233494616301","htmlUrl":"http://blog.creattica.com"},{"id":"feed/http://feeds.feedburner.com/dailyjs",
9
+ "title":"DailyJS","categories":[{"id":"user/05185BEEF/label/Dev | JavaScript","label":"Dev | JavaScript"}],"sortid":"5FD1D8F4","firstitemmsec":"1288640814519","htmlUrl":"http://dailyjs.com"},{"id":"feed/http://feeds.feedburner.com/dyt","title":"Design You Trust™","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"8166F8C2","firstitemmsec":"1209629053048","htmlUrl":"http://designyoutrust.com"},{"id":"feed/http://design-milk.com/feed/",
10
+ "title":"Design-milk.com","categories":[{"id":"user/05185BEEF/label/Creative | Architecture","label":"Creative | Architecture"}],"sortid":"DD4EAAEA","firstitemmsec":"1206087839712","htmlUrl":"http://design-milk.com"},{"id":"feed/http://designdiary.org/?feed\u003drss2","title":"DesignDiary.org","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"5A78F0AD","firstitemmsec":"1208401921736","htmlUrl":"http://www.designdiary.org"},
11
+ {"id":"feed/http://feeds.feedburner.com/designers-who-blog/UBiH","title":"Designers-who-Blog.com","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"CA06693D","firstitemmsec":"1216931054171","htmlUrl":"http://www.designers-who-blog.com/"},{"id":"feed/http://discodust.blogspot.com/feeds/posts/default","title":"Discodust.bs.com","categories":[{"id":"user/05185BEEF/label/Creative | Music","label":"Creative | Music"}],"sortid":"D325737B",
12
+ "firstitemmsec":"1208213125319","htmlUrl":"http://discodust.blogspot.com/"},{"id":"feed/http://www.dubplatedigest.net/feeds/posts/default","title":"DubplateDigest.net","categories":[{"id":"user/05185BEEF/label/Creative | Music","label":"Creative | Music"}],"sortid":"66AEFB6D","firstitemmsec":"1272115440274","htmlUrl":"http://www.dubplatedigest.net/"},{"id":"feed/http://dubstep.fm/archive.xml","title":"Dubstep.FM podcast","categories":[{"id":"user/05185BEEF/label/Creative | Music",
13
+ "label":"Creative | Music"}],"sortid":"F9FBBF79","firstitemmsec":"1272868596555","htmlUrl":"http://www.dubstep.fm"},{"id":"feed/http://www.eclipsegym.com/feed/","title":"Eclipse 24/7 Fitness Center","categories":[],"sortid":"F8D56981","firstitemmsec":"1298548886542","htmlUrl":"http://www.eclipsegym.com"},{"id":"feed/http://electriczoo.blogspot.com/feeds/posts/default","title":"Electric Zoo","categories":[{"id":"user/05185BEEF/label/Creative | Music","label":"Creative | Music"}],"sortid":"CA1FD320",
14
+ "firstitemmsec":"1280490995969","htmlUrl":"http://electriczoo.blogspot.com/"},{"id":"feed/http://enlightenedcooking.blogspot.com/feeds/posts/default","title":"Enlightened Cooking","categories":[{"id":"user/05185BEEF/label/Food","label":"Food"}],"sortid":"33025637","firstitemmsec":"1287176242232","htmlUrl":"http://enlightenedcooking.blogspot.com/"},{"id":"feed/http://everydaypaleo.com/feed/","title":"Everyday Paleo","categories":[{"id":"user/05185BEEF/label/Food","label":"Food"
15
+ }],"sortid":"C652F2A7","firstitemmsec":"1291013929130","htmlUrl":"http://everydaypaleo.com"},{"id":"feed/http://www.formfiftyfive.com/?feed\u003drss2","title":"FormFiftyFive.com","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"4363C7FB","firstitemmsec":"1229553149906","htmlUrl":"http://www.formfiftyfive.com"},{"id":"feed/http://freelanceswitch.com/feed/","title":"FreelanceSwitch.com","categories":[{"id":"user/05185BEEF/label/Dev | Web",
16
+ "label":"Dev | Web"}],"sortid":"0A5AD2CF","firstitemmsec":"1206087839712","htmlUrl":"http://freelanceswitch.com"},{"id":"user/05185BEEF/state/com.google/alerts/1078067610186869744","title":"Google Alerts - \"rico sta. cruz\"","categories":[{"id":"user/05185BEEF/label/Notifications","label":"Notifications"}],"sortid":"58344ED8","firstitemmsec":"1286564837187"},{"id":"user/05185BEEF/state/com.google/alerts/4061551431853962868","title":"Google Alerts - dvorak ios4",
17
+ "categories":[],"sortid":"FE4992E8","firstitemmsec":"1292781970809"},{"id":"user/05185BEEF/state/com.google/alerts/18290596098111276605","title":"Google Alerts - ipad dvorak","categories":[],"sortid":"CE697037","firstitemmsec":"1292782188536"},{"id":"user/05185BEEF/state/com.google/alerts/3926461893977807870","title":"Google Alerts - rstacruz","categories":[{"id":"user/05185BEEF/label/Notifications","label":"Notifications"}],"sortid":"9719BD5A","firstitemmsec":"1282078875630"
18
+ },{"id":"user/05185BEEF/state/com.google/alerts/12287538649187954165","title":"Google Alerts - ubuntu dust theme","categories":[{"id":"user/05185BEEF/label/Notifications","label":"Notifications"}],"sortid":"A390DC13","firstitemmsec":"1286564831888"},{"id":"feed/http://www.gottadancedirty.com/feeds/posts/default","title":"GottaDanceDirty.com","categories":[{"id":"user/05185BEEF/label/Creative | Music","label":"Creative | Music"}],"sortid":"1AA2DB7A","firstitemmsec":"1283308183811",
19
+ "htmlUrl":"http://gottadancedirty.com"},{"id":"feed/http://grainedit.com/feed/","title":"GrainedIt.com","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"DAC9EDFB","firstitemmsec":"1285312937851","htmlUrl":"http://grainedit.com"},{"id":"feed/http://heretodayheretomorrow.net/blog/?feed\u003drss2","title":"Here Today Here Tomorrow","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"74F7B55E",
20
+ "firstitemmsec":"1206087839701","htmlUrl":"http://heretodayheretomorrow.net/blog"},{"id":"webfeed/16571835450551436163","title":"http://oglaf.com/ Google feed","categories":[{"id":"user/05185BEEF/label/Misc | Comics","label":"Misc | Comics"}],"sortid":"ECBEF6C0","firstitemmsec":"1272169305934"},{"id":"feed/http://ihearthouse.tumblr.com/rss","title":"i ♥ house.","categories":[{"id":"user/05185BEEF/label/Creative | Music","label":"Creative | Music"}],"sortid":"2E6DF451","firstitemmsec":"1272366103438",
21
+ "htmlUrl":"http://ihearthouse.tumblr.com/"},{"id":"feed/http://ilovetypography.com/feed/","title":"ILoveTypography.com","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"08608EBA","firstitemmsec":"1206087839692","htmlUrl":"http://ilovetypography.com"},{"id":"feed/http://www.iphonehacks.com/atom.xml","title":"iPhone Hacks","categories":[{"id":"user/05185BEEF/label/Tech | iPhone","label":"Tech | iPhone"}],"sortid":"638B5DCB",
22
+ "firstitemmsec":"1275348767342","htmlUrl":"http://www.iphonehacks.com"},{"id":"feed/http://iphone.appstorm.net/feed/","title":"iPhone.AppStorm","categories":[{"id":"user/05185BEEF/label/Tech | iPhone","label":"Tech | iPhone"}],"sortid":"9ACFFF10","firstitemmsec":"1275921397122","htmlUrl":"http://iphone.appstorm.net"},{"id":"feed/http://feeds.feedburner.com/ambivalencephoto","title":"Kunst ist schisse","categories":[{"id":"user/05185BEEF/label/Misc | People","label":"Misc | People"
23
+ }],"sortid":"0713B4A7","firstitemmsec":"1210216013476","htmlUrl":"http://ambivalence.aminus3.com/"},{"id":"feed/http://lifeandcoincidences.blogspot.com/feeds/posts/default","title":"LifeAndCoincidences.bs.com","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"5C18D0BF","firstitemmsec":"1191170659496","htmlUrl":"http://lifeandcoincidences.blogspot.com/"},{"id":"feed/http://littlebigdetails.com/rss","title":"Little Big Details","categories":[
24
+ ],"sortid":"BBE6BB92","firstitemmsec":"1294653660506","htmlUrl":"http://littlebigdetails.com/"},{"id":"feed/http://feeds.feedburner.com/logoblog","title":"Logo Design Blog","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"34BFFCA5","firstitemmsec":"1217366664661","htmlUrl":"http://www.logoblog.org/wordpress"},{"id":"feed/http://code.google.com/feeds/p/macvim/downloads/basic","title":"MacVim Downloads","categories":[{"id":"user/05185BEEF/label/Tech | Mac",
25
+ "label":"Tech | Mac"}],"sortid":"74380CF2","firstitemmsec":"1250300546902","htmlUrl":"http://code.google.com/p/macvim/downloads/list"},{"id":"feed/http://feeds.feedburner.com/MarginsAndColumns","title":"MarginsAndColumns.com","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"2AFEB23E","firstitemmsec":"1207410826687","htmlUrl":"http://marginsandcolumns.com"},{"id":"feed/http://www.noupe.com/feed","title":"Noupe","categories":[{"id":"user/05185BEEF/label/Dev | Web Design",
26
+ "label":"Dev | Web Design"}],"sortid":"D196B789","firstitemmsec":"1213573132774","htmlUrl":"http://www.noupe.com/"},{"id":"feed/http://feeds.feedburner.com/d0od","title":"Omg! Ubuntu!","categories":[{"id":"user/05185BEEF/label/Tech | Ubuntu","label":"Tech | Ubuntu"}],"sortid":"5811FF35","firstitemmsec":"1288462712249","htmlUrl":"http://www.omgubuntu.co.uk"},{"id":"feed/http://headrush.typepad.com/creating_passionate_users/atom.xml","title":"PassionateUsers","categories":[{"id":"user/05185BEEF/label/Dev | Web",
27
+ "label":"Dev | Web"}],"sortid":"DC14A865","firstitemmsec":"1206087839713","htmlUrl":"http://headrush.typepad.com/creating_passionate_users/"},{"id":"feed/http://www.peanutbutterrunner.com/feed/","title":"Peanut Butter Runner","categories":[{"id":"user/05185BEEF/label/Fitness","label":"Fitness"}],"sortid":"D5C579FF","firstitemmsec":"1294026019043","htmlUrl":"http://www.peanutbutterrunner.com"},{"id":"feed/http://www.persuasive.net/feed/","title":"Persuasive.net","categories":[{"id":"user/05185BEEF/label/Misc | People",
28
+ "label":"Misc | People"}],"sortid":"9B1026E1","firstitemmsec":"1271444221713","htmlUrl":"http://www.persuasive.net/blog"},{"id":"feed/http://photoshopdisasters.blogspot.com/feeds/posts/default","title":"PhotoshopDisasters","categories":[{"id":"user/05185BEEF/label/Dev | Web Design","label":"Dev | Web Design"}],"sortid":"634E13A5","firstitemmsec":"1225146103356","htmlUrl":"http://www.psdisasters.com/"},{"id":"feed/http://www.pinoyfitness.com/feed/","title":"Pinoy Fitness","categories":[
29
+ {"id":"user/05185BEEF/label/Fitness","label":"Fitness"}],"sortid":"9E1ED283","firstitemmsec":"1293945182864","htmlUrl":"http://www.pinoyfitness.com"},{"id":"feed/http://www.pipetodevnull.com/feed","title":"pipe :to \u003d\u003e /dev/null","categories":[{"id":"user/05185BEEF/label/Dev | Ruby","label":"Dev | Ruby"}],"sortid":"284FD52C","firstitemmsec":"1273545545691"},{"id":"feed/http://feeds2.feedburner.com/Pixelmator","title":"Pixelmator.com","categories":[{"id":"user/05185BEEF/label/Tech | Mac",
30
+ "label":"Tech | Mac"}],"sortid":"6F2E2F53","firstitemmsec":"1240828171889","htmlUrl":"http://www.pixelmator.com/weblog"},{"id":"feed/http://feeds.feedburner.com/popsop_com","title":"PopSop.com","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"CF0B0986","firstitemmsec":"1230150748519","htmlUrl":"http://popsop.com"},{"id":"feed/http://feeds.feedburner.com/ricostacruz","title":"Rico Sta Cruz.com","categories":[],"sortid":"D6305134","firstitemmsec":"1294368654584",
31
+ "htmlUrl":"http://ricostacruz.com"},{"id":"feed/http://www.rubyinside.com/feed/","title":"RubyInside.com","categories":[{"id":"user/05185BEEF/label/Dev | Ruby","label":"Dev | Ruby"}],"sortid":"3C377CE7","firstitemmsec":"1282102662073","htmlUrl":"http://www.rubyinside.com/"},{"id":"feed/http://www.runaddicts.net/feed","title":"RunAddicts","categories":[{"id":"user/05185BEEF/label/Fitness","label":"Fitness"}],"sortid":"91717797","firstitemmsec":"1291749989298","htmlUrl":"http://www.runaddicts.net"
32
+ },{"id":"feed/http://speedendurance.com/feed/","title":"SpeedEndurance.com","categories":[{"id":"user/05185BEEF/label/Fitness","label":"Fitness"}],"sortid":"849709E3","firstitemmsec":"1291915027648","htmlUrl":"http://speedendurance.com"},{"id":"feed/http://www.startdrawing.org/home/?feed\u003drss2","title":"Stardrawing","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"60E9C1BD","firstitemmsec":"1206087839721","htmlUrl":"http://www.startdrawing.org/home"
33
+ },{"id":"feed/http://strengthrunning.com/feed/","title":"Strength Running","categories":[{"id":"user/05185BEEF/label/Fitness","label":"Fitness"}],"sortid":"38D0CF7E","firstitemmsec":"1291217163337","htmlUrl":"http://strengthrunning.com"},{"id":"feed/http://technogra.ph/feed/","title":"Technograph","categories":[{"id":"user/05185BEEF/label/Dev","label":"Dev"}],"sortid":"DD0A5ADF","firstitemmsec":"1206087839708","htmlUrl":"http://technogra.ph"},{"id":"feed/http://feeds2.feedburner.com/TheArtOfManliness",
34
+ "title":"The Art of Manliness","categories":[],"sortid":"7E0B08E0","firstitemmsec":"1289972655107","htmlUrl":"http://artofmanliness.com"},{"id":"feed/http://syndication.thedailywtf.com/TheDailyWtf","title":"The Daily WTF","categories":[{"id":"user/05185BEEF/label/Dev","label":"Dev"}],"sortid":"24A3860F","firstitemmsec":"1206087839731","htmlUrl":"http://thedailywtf.com/"},{"id":"feed/http://g4graphic.wordpress.com/feed/","title":"The Magenta Links","categories":[{"id":"user/05185BEEF/label/Creative | Design",
35
+ "label":"Creative | Design"}],"sortid":"C60F68C3","firstitemmsec":"1208887419348","htmlUrl":"http://www.themagentalinks.com"},{"id":"feed/http://feeds.feedburner.com/MagnusHolm","title":"The timeless repository","categories":[{"id":"user/05185BEEF/label/Dev | Ruby","label":"Dev | Ruby"}],"sortid":"AD18367E","firstitemmsec":"1272911215069","htmlUrl":"http://timeless.judofyr.net/"},{"id":"feed/http://www.thedieline.com/blog/atom.xml","title":"TheDieline.com","categories":[{"id":"user/05185BEEF/label/Creative | Design",
36
+ "label":"Creative | Design"}],"sortid":"91D7D076","firstitemmsec":"1209991814130","htmlUrl":"http://www.thedieline.com/blog/"},{"id":"feed/http://www.theserif.net/?feed\u003drss2","title":"TheSerif.net","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"D59BF567","firstitemmsec":"1219409648704","htmlUrl":"http://www.theserif.net"},{"id":"feed/http://thestrangeattractor.net/?feed\u003drss2","title":"TheStrangeAttractor.net","categories":[
37
+ {"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"D86564C1","firstitemmsec":"1283274491330","htmlUrl":"http://thestrangeattractor.net"},{"id":"feed/http://feeds.feedburner.com/vitaminmasterfeed","title":"ThinkVitamin.com","categories":[{"id":"user/05185BEEF/label/Dev | Web","label":"Dev | Web"}],"sortid":"35A600D0","firstitemmsec":"1206087839712","htmlUrl":"http://thinkvitamin.com"},{"id":"feed/http://feeds.feedburner.com/theiphoneblog",
38
+ "title":"TiPB.com - iPhone Blog","categories":[{"id":"user/05185BEEF/label/Tech | iPhone","label":"Tech | iPhone"}],"sortid":"F4D33A2A","firstitemmsec":"1272457356757","htmlUrl":"http://www.tipb.com"},{"id":"feed/http://www.typeneu.com/feed/","title":"TypeNeu.com","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"7870F422","firstitemmsec":"1208351906779","htmlUrl":"http://typeneu.com"},{"id":"feed/http://feeds.feedburner.com/uxmovement",
39
+ "title":"UXMovement.com","categories":[{"id":"user/05185BEEF/label/Dev | Web","label":"Dev | Web"}],"sortid":"2336C8F3","firstitemmsec":"1283206863295","htmlUrl":"http://uxmovement.com"},{"id":"feed/http://weareanalog.blogspot.com/feeds/posts/default","title":"we are analog","categories":[{"id":"user/05185BEEF/label/Creative | Design","label":"Creative | Design"}],"sortid":"6588B59E","firstitemmsec":"1206087839700","htmlUrl":"http://weareanalog.blogspot.com/"},{"id":"feed/http://www.webupd8.org/feeds/posts/default",
40
+ "title":"Web Upd8 - Ubuntu / Linux blog","categories":[{"id":"user/05185BEEF/label/Tech | Ubuntu","label":"Tech | Ubuntu"}],"sortid":"B0A26919","firstitemmsec":"1289336351585","htmlUrl":"http://www.webupd8.org/"},{"id":"feed/http://xkcd.com/rss.xml","title":"xkcd.com","categories":[{"id":"user/05185BEEF/label/Misc | Comics","label":"Misc | Comics"}],"sortid":"6795ABCE","firstitemmsec":"1205294559301","htmlUrl":"http://xkcd.com/"}]}
@@ -0,0 +1,4 @@
1
+ {"tags":[{"id":"user/05185BEEF/state/com.google/starred","sortid":"F2177F5C"},{"id":"user/05185BEEF/state/com.google/broadcast","sortid":"2F9D8626"},{"id":"user/05185BEEF/label/-—","sortid":"85FB845C"},{"id":"user/05185BEEF/label/Creative | Architecture","sortid":"8B42A144"},{"id":"user/05185BEEF/label/Creative | Design","sortid":"5F0F9219"},{"id":"user/05185BEEF/label/Creative | Design 2","sortid":"C9BF793F"},{"id":"user/05185BEEF/label/Creative | Music",
2
+ "sortid":"8E258CD5"},{"id":"user/05185BEEF/label/Dev","sortid":"18391B4A"},{"id":"user/05185BEEF/label/Dev | JavaScript","sortid":"7C0498F9"},{"id":"user/05185BEEF/label/Dev | Ruby","sortid":"1B105EB8"},{"id":"user/05185BEEF/label/Dev | Web","sortid":"D4F0658C"},{"id":"user/05185BEEF/label/Dev | Web Design","sortid":"13777DD6"},{"id":"user/05185BEEF/label/Fitness","sortid":"920F7049"},{"id":"user/05185BEEF/label/Food","sortid":"427D1099"
3
+ },{"id":"user/05185BEEF/label/Misc | Comics","sortid":"6A54524D"},{"id":"user/05185BEEF/label/Misc | People","sortid":"6E149414"},{"id":"user/05185BEEF/label/Notifications","sortid":"32A74187"},{"id":"user/05185BEEF/label/Tech | iPhone","sortid":"F1E2B5EB"},{"id":"user/05185BEEF/label/Tech | Mac","sortid":"1E13D3B3"},{"id":"user/05185BEEF/label/Tech | Ubuntu","sortid":"D7DA793B"},{"id":"user/05185BEEF/label/—-","sortid":"55507C67"
4
+ },{"id":"user/05185BEEF/label/—-—","sortid":"16F7B01F"},{"id":"user/05185BEEF/label/——-","sortid":"89B974DC"},{"id":"user/05185BEEF/state/com.blogger/blogger-following","sortid":"0E45DEC1"}]}
@@ -0,0 +1,74 @@
1
+ ENV['RESTCLIENT_LOG'] = 'stdout' if ENV['REAL']
2
+
3
+ $:.push File.expand_path('../../lib', __FILE__)
4
+
5
+ begin
6
+ require 'contest'
7
+ require 'fakeweb'
8
+ require 'yaml'
9
+ rescue LoadError => e
10
+ puts "Load error (#{e.message})."
11
+ puts "Try: gem install contest fakeweb"
12
+ exit
13
+ end
14
+
15
+ require 'greader'
16
+
17
+ module TestHelpers
18
+ extend self
19
+
20
+ def fixture_file(*a)
21
+ File.join File.expand_path('../fixtures', __FILE__), *a
22
+ end
23
+
24
+ def fixture(*a)
25
+ File.open(fixture_file(*a), 'r:utf-8') { |f| f.read }
26
+ end
27
+
28
+ def fixture?(*a)
29
+ File.exists? fixture_file(*a)
30
+ end
31
+
32
+ def credentials
33
+ if real?
34
+ YAML::load fixture('credentials.yml')
35
+ else
36
+ { :email => 'christian@mcnamara-troy.com', :password => 'yoplait' }
37
+ end
38
+ end
39
+
40
+ def real?
41
+ ENV['REAL'] && fixture('credentials.yml')
42
+ end
43
+
44
+ # @example
45
+ # fake :post, url, body: '...'
46
+ #
47
+ def fake(*a)
48
+ FakeWeb.register_uri(*a) unless real?
49
+ end
50
+ end
51
+
52
+ class GReader::Client
53
+ def client_name() 'greader.rb-test'; end
54
+ end
55
+
56
+ class Test::Unit::TestCase
57
+ include TestHelpers
58
+
59
+ setup do
60
+ fake :post, "https://www.google.com/accounts/ClientLogin", :body => fixture('auth.txt')
61
+ fake :get, "http://www.google.com/reader/api/0/subscription/list?output=json&client=greader.rb-test", :body => fixture('subscription-list.json')
62
+ fake :get, "http://www.google.com/reader/api/0/tag/list?output=json&client=greader.rb-test", :body => fixture('tag-list.json')
63
+ fake :get, "http://www.google.com/reader/api/0/stream/contents/user%2F05185BEEF%2Flabel%2FDev%20%7C%20Ruby?output=json&client=greader.rb-test", :body => fixture('ruby-entries.json')
64
+ end
65
+ end
66
+
67
+ if ENV['REAL']
68
+ if TestHelpers.real?
69
+ puts "*** Running in real mode."
70
+ else
71
+ puts "*** You need test/fixtures/credentials.yml to run in REAL mode."
72
+ exit
73
+ end
74
+ end
@@ -0,0 +1,46 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class TagTest < Test::Unit::TestCase
4
+ setup do
5
+ @client = GReader.auth credentials
6
+ @tags = @client.tags
7
+ @tag = @tags[4]
8
+ end
9
+
10
+ test "Client#tags" do
11
+ assert_equal 24, @tags.size
12
+ end
13
+
14
+ test "Tag" do
15
+ assert_equal "Dev | Ruby", @tag.to_s
16
+ end
17
+
18
+ test "Tag#feeds" do
19
+ @feeds = @tag.feeds
20
+
21
+ control = ["pipe :to => /dev/null", "RubyInside.com", "The timeless repository", "Antirez.com"]
22
+ assert_equal control, @feeds.map(&:to_s)
23
+ end
24
+
25
+ describe "Entries" do
26
+ setup do
27
+ @entries = @tag.entries
28
+ @entry = @entries.first
29
+ end
30
+
31
+ test "is_a Entries" do
32
+ assert @entries.is_a?(GReader::Entries)
33
+ end
34
+
35
+ test "tag entries" do
36
+ assert @entry.is_a?(GReader::Entry)
37
+
38
+ assert_equal @entry.title, @entry.to_s
39
+ end
40
+
41
+ test "Entry#feed" do
42
+ assert @entry.feed.is_a?(GReader::Feed)
43
+ assert_equal @entry.feed, @client.feed(@entry.feed.id)
44
+ end
45
+ end
46
+ end