moxie-columbus 0.1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/Gemfile.lock +86 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/examples/all.rb +4 -0
- data/examples/primary.rb +4 -0
- data/lib/columbus.rb +47 -0
- data/lib/columbus/feed.rb +17 -0
- data/lib/columbus/link.rb +19 -0
- data/lib/columbus/redirect_follower.rb +46 -0
- data/moxie-columbus.gemspec +71 -0
- data/test/columbus_test.rb +36 -0
- data/test/feed_test.rb +23 -0
- data/test/fixtures/railsquicktips.html +295 -0
- data/test/fixtures/railstips.html +1071 -0
- data/test/fixtures/railstips_feedburner.html +1397 -0
- data/test/fixtures/railstips_redirect +11 -0
- data/test/link_test.rb +40 -0
- data/test/test_helper.rb +17 -0
- metadata +150 -0
@@ -0,0 +1,1397 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds2.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" xml:lang="en-US">
|
3
|
+
<title>RailsTips - Home</title>
|
4
|
+
<id>tag:railstips.org,2009:mephisto/</id>
|
5
|
+
<generator version="0.8.0" uri="http://mephistoblog.com">Mephisto Drax</generator>
|
6
|
+
|
7
|
+
<link href="http://railstips.org/" rel="alternate" type="text/html" />
|
8
|
+
<updated>2009-03-25T06:28:02Z</updated>
|
9
|
+
<geo:lat>41.650672</geo:lat><geo:long>-86.160028</geo:long><link rel="self" href="http://feeds2.feedburner.com/railstips" type="application/atom+xml" /><feedburner:emailServiceId>railstips</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><entry xml:base="http://railstips.org/">
|
10
|
+
<author>
|
11
|
+
<name>john</name>
|
12
|
+
</author>
|
13
|
+
<id>tag:railstips.org,2009-03-25:8938</id>
|
14
|
+
<published>2009-03-25T11:00:00Z</published>
|
15
|
+
<updated>2009-03-25T06:28:02Z</updated>
|
16
|
+
<category term="Gems" />
|
17
|
+
<category term="Testing" />
|
18
|
+
<category term="api" />
|
19
|
+
<category term="gems" />
|
20
|
+
<category term="google" />
|
21
|
+
<category term="testing" />
|
22
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/ajimswwDgJ0/building-api-wrapping-gems-could-not-get-much-easier" rel="alternate" type="text/html" />
|
23
|
+
<title>Building API Wrapping Gems Could Not Get Much Easier</title>
|
24
|
+
<summary type="html"><p>In which I show how easy it is now to create ruby gems that wrap APIs, using Google Weather as an example.</p></summary><content type="html">
|
25
|
+
<p>In which I show how easy it is now to create ruby gems that wrap APIs, using Google Weather as an example.</p>
|
26
|
+
<p>Google has a weather api that is dead simple to use. Just discovered that tonight so I whipped together a wrapper using HTTParty. I decided to try out <a href="http://github.com/technicalpickles/jeweler/tree/master">Jeweler</a>, a project by <a href="http://technicalpickles.com/">Josh Nichols</a>, that makes creating gems a snap and it delivered. I used shoulda and fakeweb for the tests. Holy crap has making a gem that wraps a web service become really easy.</p>
|
27
|
+
|
28
|
+
|
29
|
+
<h2>The New Way</h2>
|
30
|
+
|
31
|
+
|
32
|
+
<ol>
|
33
|
+
<li>jeweler google-weather —shoulda —create-repo</li>
|
34
|
+
<li>%w(matchy fakeweb).each { |x| require x } (in your test_helper)</li>
|
35
|
+
<li>require ‘httparty’</li>
|
36
|
+
<li>Add some code and tests</li>
|
37
|
+
<li>rake version:minor:bump</li>
|
38
|
+
<li>rake gemspec</li>
|
39
|
+
<li>git push origin master</li>
|
40
|
+
<li>blog</li>
|
41
|
+
</ol>
|
42
|
+
|
43
|
+
|
44
|
+
<p>I did all of these in <strong>about an hour or two</strong> tonight.</p>
|
45
|
+
|
46
|
+
|
47
|
+
<h2>The Old Way</h2>
|
48
|
+
|
49
|
+
|
50
|
+
<ol>
|
51
|
+
<li>Create a bunch of files and directories and make a bunch of decisions</li>
|
52
|
+
<li>mock and stub all net/http stuff</li>
|
53
|
+
<li>net/http and rexml (or hpricot once that came along)</li>
|
54
|
+
<li>Add some code and maybe some tests</li>
|
55
|
+
<li>Add a version</li>
|
56
|
+
<li>Figure out how to build a gemspec</li>
|
57
|
+
<li>svn commit your files</li>
|
58
|
+
<li>Request project to be created on rubyforge</li>
|
59
|
+
<li>Wait a few days</li>
|
60
|
+
<li>Project approved, release files, blog</li>
|
61
|
+
</ol>
|
62
|
+
|
63
|
+
|
64
|
+
<p>And it would <strong>take a few days</strong> from first code scratched to gem released. My how times are a changing.</p>
|
65
|
+
|
66
|
+
|
67
|
+
<h2>Stuff You Can Learn From This Gem</h2>
|
68
|
+
|
69
|
+
|
70
|
+
<p>At any rate, the <a href="http://github.com/jnunemaker/google-weather/tree/master">GoogleWeather gem</a> I just created is a really simple example of how to use:</p>
|
71
|
+
|
72
|
+
|
73
|
+
<ul>
|
74
|
+
<li><a href="http://github.com/technicalpickles/jeweler/tree/master">jeweler</a> to create and manage a gem</li>
|
75
|
+
<li><a href="http://github.com/jnunemaker/httparty/tree/master">httparty</a> to pwn an <span class="caps">API</span></li>
|
76
|
+
<li><a href="http://github.com/thoughtbot/shoulda/tree/master">shoulda</a> to test the gem</li>
|
77
|
+
<li><a href="http://fakeweb.rubyforge.org/">fakeweb</a> to make sure your tests aren’t making real web requests</li>
|
78
|
+
<li><a href="http://github.com/jeremymcanally/matchy/tree/master">matchy</a> for some syntactical sugar</li>
|
79
|
+
</ul>
|
80
|
+
|
81
|
+
|
82
|
+
<p>If you want to learn any of those things, <a href="http://github.com/jnunemaker/google-weather/tree/master">poke around in the code</a> a bit and you should be good to go. Also, if you want a really easy way to get weather information, this gem <a href="http://github.com/jnunemaker/google-weather/blob/1affe3d4da62b9cf22b996a6418ead63faeda4da/examples/forecast.rb">makes that possible</a>.</p>
|
83
|
+
|
84
|
+
|
85
|
+
<p>Sorry I didn’t give it some fancy name like <a href="http://railstips.org/2008/7/29/it-s-an-httparty-and-everyone-is-invited">HTTParty</a> or <a href="http://railstips.org/2008/11/17/happymapper-making-xml-fun-again">HappyMapper</a>. Maybe I need to make another gem that spits out fancy names. After all, naming the project is the only thing left that is hard. ;)</p>
|
86
|
+
<div class="feedflare">
|
87
|
+
<a href="http://feeds2.feedburner.com/~ff/railstips?a=ajimswwDgJ0:RaH3HhT_8W0:yIl2AUoC8zA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=ajimswwDgJ0:RaH3HhT_8W0:dnMXMwOfBR0"><img src="http://feeds2.feedburner.com/~ff/railstips?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=ajimswwDgJ0:RaH3HhT_8W0:7Q72WNTAKBA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=7Q72WNTAKBA" border="0"></img></a>
|
88
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/ajimswwDgJ0" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/3/25/building-api-wrapping-gems-could-not-get-much-easier</feedburner:origLink></entry>
|
89
|
+
<entry xml:base="http://railstips.org/">
|
90
|
+
<author>
|
91
|
+
<name>john</name>
|
92
|
+
</author>
|
93
|
+
<id>tag:railstips.org,2009-03-24:8937</id>
|
94
|
+
<published>2009-03-24T19:50:00Z</published>
|
95
|
+
<updated>2009-03-24T19:50:58Z</updated>
|
96
|
+
<category term="Gems" />
|
97
|
+
<category term="Testing" />
|
98
|
+
<category term="matchy" />
|
99
|
+
<category term="testing" />
|
100
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/Rkf-lmi0BXc/custom-matchers-for-matchy" rel="alternate" type="text/html" />
|
101
|
+
<title>Custom Matchers for Matchy</title>
|
102
|
+
<summary type="html"><p>In which I show all the dirty secrets of custom matchers using matchy.</p></summary><content type="html">
|
103
|
+
<p>In which I show all the dirty secrets of custom matchers using matchy.</p>
|
104
|
+
<p>I’ve been using Shoulda for <a href="http://railstips.org/2009/2/21/shoulda-looked-at-it-sooner">around a month now</a> and, thus far, the only thing I have missed is RSpec’s matchers (ie: foo.should == 2). Enter stage right Jeremy McAnally’s project <a href="http://github.com/jeremymcanally/matchy/tree/master">matchy</a>. Matchy provides “RSpec-esque matchers for Test::Unit”.</p>
|
105
|
+
|
106
|
+
|
107
|
+
<p>Matchy met the immediate need of liking the should and should_not RSpec syntax, but, originally, it was not built with support for custom matchers. A little over a month ago, Matthias Hennemeyer abstracted the ability to build matchers and created a method called def_matcher to create your own custom matchers.</p>
|
108
|
+
|
109
|
+
|
110
|
+
<p>Excited, I installed the new version and went to town, only to end up confused. def_matcher only seemed to work from inside an instance method. Most likely I was just too stupid to use what he created, but I figured if I was too stupid there were probably others. Instead of giving up, I hacked a version into my project of what Matthias created that I called custom_matcher and made it work more like a class method.</p>
|
111
|
+
|
112
|
+
|
113
|
+
<p>Normally, hacking what I need into my project is where it stops, but this time I decided to give back and actually <a href="http:/github.com/jnunemaker/matchy/">fork matchy</a> and apply my changes. Today while waiting on some stuff from a client, I took the time to actually add my changes, test them and push them upstream to Github.</p>
|
114
|
+
|
115
|
+
|
116
|
+
<p>So enough boring explanation, eh? How do the changes work I’m sure you are wondering. For each example, I’ll show an example with and without the custom_matcher stuff so you can see the <span class="caps">API</span> difference.</p>
|
117
|
+
|
118
|
+
|
119
|
+
<h2>Testing Nil</h2>
|
120
|
+
|
121
|
+
|
122
|
+
<p>The first custom matcher that I added was straight up ganked from RSpec and is named be_nil. Nothing fancy, but I kind of like it.</p>
|
123
|
+
|
124
|
+
|
125
|
+
<pre><code class="ruby">class ActiveSupport::TestCase
|
126
|
+
custom_matcher :be_nil do |receiver, matcher, args|
|
127
|
+
matcher.positive_failure_message = "Expected #{receiver} to be nil but it wasn't"
|
128
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be nil but it was"
|
129
|
+
receiver.nil?
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class ItemTest &lt; ActiveSupport::TestCase
|
134
|
+
def test_something
|
135
|
+
item = Item.new
|
136
|
+
# without custom matcher
|
137
|
+
item.title.should be(nil)
|
138
|
+
|
139
|
+
# with custom matcher
|
140
|
+
item.title.should be_nil
|
141
|
+
end
|
142
|
+
end</code></pre>
|
143
|
+
|
144
|
+
<p>So the difference is slight, right? I’m all kinds of lazy so the difference of typing be_nil versus be(nil) is worth it. One underscore is easier than two parenthesis. Heck, underscore is even easier to spell as a word, but that is probably irrelevant.</p>
|
145
|
+
|
146
|
+
|
147
|
+
<h2>Custom Matcher Syntax</h2>
|
148
|
+
|
149
|
+
|
150
|
+
<p>Before I go on to more examples, let’s make sure you understand what was happening above. The basic syntax of a custom_matcher is:</p>
|
151
|
+
|
152
|
+
|
153
|
+
<pre><code class="ruby">custom_matcher :matcher_name do |receiver, matcher, args|
|
154
|
+
# matcher body
|
155
|
+
end</code></pre>
|
156
|
+
|
157
|
+
<p>:matcher_name is pretty obvious but what is the purpose of the receiver, matcher and args in the block? <strong>Receiver is the object that the modal (should, should_not) is being called on</strong>. In the example above item.title would be the available as the receiver in the custom_matcher block.</p>
|
158
|
+
|
159
|
+
|
160
|
+
<p>Matcher has a couple purposes. First, it <strong>allows you to change the failure messages</strong>, both positive (should) and negative (should not). If you say item.title.should be_nil and item.title is not nil, the failure message will be equal to the positive_failure_message. Likewise, if you say item.title.should_not be_nil and item.title is nil, the negative_failure_message is what you’ll see. <strong>Matcher also allows some cool chaining of messages onto the matcher</strong> and I’ll show that in a bit.</p>
|
161
|
+
|
162
|
+
|
163
|
+
<p>Finally, <strong>args is equal to the arguments (if any) that are passed into the matcher</strong> method. This allows for some really cool test <span class="caps">API</span> tweaks.</p>
|
164
|
+
|
165
|
+
|
166
|
+
<p>Let’s use that new knowledge of matcher and args to create a matcher that takes advantage of both, and hopefully shows the power of custom matchers. In this example, we’ll create a custom matcher :have that allows testing the size of an array returned by a method on the receiver.</p>
|
167
|
+
|
168
|
+
|
169
|
+
<pre><code class="ruby">class Test::Unit::TestCase
|
170
|
+
custom_matcher :have do |receiver, matcher, args|
|
171
|
+
count = args[0]
|
172
|
+
something = matcher.chained_messages[0].name
|
173
|
+
actual = receiver.send(something).size
|
174
|
+
actual == count
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class MoreAdvancedTest &lt; Test::Unit::TestCase
|
179
|
+
class Item
|
180
|
+
def tags
|
181
|
+
%w(foo bar baz)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_item_has_tags
|
186
|
+
item = Item.new
|
187
|
+
# without custom matcher
|
188
|
+
item.tags.size.should == 3
|
189
|
+
|
190
|
+
# with custom matcher
|
191
|
+
item.should have(3).tags # pass
|
192
|
+
item.should have(2).tags # fail
|
193
|
+
end
|
194
|
+
end</code></pre>
|
195
|
+
|
196
|
+
<p>In the custom_matcher above, matcher.chained_messages<sup><a href="#fn0">0</a></sup>.name is equal to “tags”. So basically the :have custom matcher gets the chained method (tags), calls it on the receiver (item) and then makes sure that the result size (item.tags.size) is equal to the argument passed into :have (3 and 2).</p>
|
197
|
+
|
198
|
+
|
199
|
+
<p>Maybe a more simple example of passing in arguments could be the following have_error_on matcher:</p>
|
200
|
+
|
201
|
+
|
202
|
+
<pre><code class="ruby">class ActiveSupport::TestCase
|
203
|
+
custom_matcher :have_error_on do |receiver, matcher, args|
|
204
|
+
attribute = args[0]
|
205
|
+
|
206
|
+
receiver.valid?
|
207
|
+
receiver.errors.on(attribute).should_not be(nil)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class Item &lt; ActiveRecord::Base
|
212
|
+
validate_presence_of :title
|
213
|
+
end
|
214
|
+
|
215
|
+
class ItemTest &lt; ActiveSupport::TestCase
|
216
|
+
def test_title_is_required
|
217
|
+
item = Item.new
|
218
|
+
|
219
|
+
# without custom matcher
|
220
|
+
item.valid?
|
221
|
+
item.errors.on(:title).should_not be(nil) # or something like this
|
222
|
+
|
223
|
+
# with custom matcher
|
224
|
+
item.should have_error_on(:title)
|
225
|
+
end
|
226
|
+
end</code></pre>
|
227
|
+
|
228
|
+
<p>Hopefully these examples are good enough to get an idea of what is going on. The basic idea is that you can create custom matcher methods, which can call methods on the receiver, customize the error messages, and even accept arguments. Again, the internals of the matcher building were written by Matthias. All I did was put it together in a way that made sense and seemed easier to me (and hopefully others).</p>
|
229
|
+
|
230
|
+
|
231
|
+
<p>Jeremy may or may not pull my changes into his official matchy repository, so if you want to use them now, revel in the power of Github by installing my version gem (sudo gem install jnunemaker-matchy). Then be sure to require the gem in your test_helper. Here is an example <a href="http://gist.github.com/84320">Rails test_helper</a> with a few matchers already in it.</p>
|
232
|
+
<div class="feedflare">
|
233
|
+
<a href="http://feeds2.feedburner.com/~ff/railstips?a=Rkf-lmi0BXc:MCaj7ccFJAQ:yIl2AUoC8zA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=Rkf-lmi0BXc:MCaj7ccFJAQ:dnMXMwOfBR0"><img src="http://feeds2.feedburner.com/~ff/railstips?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=Rkf-lmi0BXc:MCaj7ccFJAQ:7Q72WNTAKBA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=7Q72WNTAKBA" border="0"></img></a>
|
234
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/Rkf-lmi0BXc" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/3/24/custom-matchers-for-matchy</feedburner:origLink></entry>
|
235
|
+
<entry xml:base="http://railstips.org/">
|
236
|
+
<author>
|
237
|
+
<name>john</name>
|
238
|
+
</author>
|
239
|
+
<id>tag:railstips.org,2009-03-23:8934</id>
|
240
|
+
<published>2009-03-23T15:38:00Z</published>
|
241
|
+
<updated>2009-03-23T15:41:31Z</updated>
|
242
|
+
<category term="Gems" />
|
243
|
+
<category term="Specifically Ruby" />
|
244
|
+
<category term="httparty" />
|
245
|
+
<category term="xml" />
|
246
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/pNHW3yphdDI/httparty-example-mymilemarker-com" rel="alternate" type="text/html" />
|
247
|
+
<title>HTTParty Example: MyMileMarker.com</title>
|
248
|
+
<summary type="html"><p>In which I show an example of how to use HTTParty to consume the MyMileMarker undocumented <span class="caps">API</span>.</p></summary><content type="html">
|
249
|
+
<p>In which I show an example of how to use HTTParty to consume the MyMileMarker undocumented <span class="caps">API</span>.</p>
|
250
|
+
<p>One of the apps I help keep tabs on is <a href="http://mymilemarker.com">MyMileMarker.com</a>, which I believe I’ve mentioned here before. MyMileMarker was built with respond_to xml in most parts, so it has an undocumented, but functioning <span class="caps">API</span>.</p>
|
251
|
+
|
252
|
+
|
253
|
+
<p>I took a few minutes last night to use HTTParty to build a simple wrapper for the <span class="caps">API</span>. It is nothing fancy, but I figured I would post it here as it shows how to use HTTParty to consume a pretty stock Rails <span class="caps">API</span>.</p>
|
254
|
+
|
255
|
+
|
256
|
+
<pre><code class="ruby">require 'rubygems'
|
257
|
+
gem 'httparty', '&gt;= 0.3.1'
|
258
|
+
require 'httparty'
|
259
|
+
|
260
|
+
module MyMileMarker
|
261
|
+
class Client
|
262
|
+
include HTTParty
|
263
|
+
|
264
|
+
def initialize(email, password)
|
265
|
+
@email, @password = email, password
|
266
|
+
end
|
267
|
+
|
268
|
+
def get(path, options={})
|
269
|
+
options.merge!({:basic_auth =&gt; {:username =&gt; @email, :password =&gt; @password}})
|
270
|
+
self.class.get("http://mymilemarker.com#{path}", options)
|
271
|
+
end
|
272
|
+
|
273
|
+
def vehicles
|
274
|
+
get('/vehicles.xml')['vehicles']
|
275
|
+
end
|
276
|
+
|
277
|
+
def vehicle(vehicle_id_or_slug)
|
278
|
+
Vehicle.new(self, vehicle_id_or_slug).info
|
279
|
+
end
|
280
|
+
|
281
|
+
def histories(vehicle_id_or_slug)
|
282
|
+
Vehicle.new(self, vehicle_id_or_slug).histories
|
283
|
+
end
|
284
|
+
|
285
|
+
def fuel_economy(vehicle_id_or_slug)
|
286
|
+
Vehicle.new(self, vehicle_id_or_slug).fuel_economy
|
287
|
+
end
|
288
|
+
|
289
|
+
def mileage(vehicle_id_or_slug)
|
290
|
+
Vehicle.new(self, vehicle_id_or_slug).mileage
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
class Vehicle
|
295
|
+
attr_reader :identifier, :client
|
296
|
+
|
297
|
+
def initialize(client, identifier)
|
298
|
+
@client, @identifier = client, identifier
|
299
|
+
end
|
300
|
+
|
301
|
+
def info
|
302
|
+
client.get("/vehicles/#{identifier}.xml")['vehicle']
|
303
|
+
end
|
304
|
+
|
305
|
+
def histories
|
306
|
+
client.get("/vehicles/#{identifier}/histories.xml?all=true")['histories']
|
307
|
+
end
|
308
|
+
|
309
|
+
def fuel_economy
|
310
|
+
client.get("/vehicles/#{identifier}/reports/fuel_economy.xml")['chart']['point']
|
311
|
+
end
|
312
|
+
|
313
|
+
def mileage
|
314
|
+
client.get("/vehicles/#{identifier}/reports/mileage.xml")['chart']['point']
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end</code></pre>
|
318
|
+
|
319
|
+
<p>Basically, you just create a new client and you can request your information like so:</p>
|
320
|
+
|
321
|
+
|
322
|
+
<pre><code class="ruby">client = MyMileMarker::Client.new('email@domain.com', 'secret')
|
323
|
+
|
324
|
+
p client.vehicles
|
325
|
+
p '*'*50
|
326
|
+
p client.vehicle('ford-mustang')
|
327
|
+
p '*'*50
|
328
|
+
p client.histories('ford-mustang')
|
329
|
+
p '*'*50
|
330
|
+
p client.fuel_economy('ford-mustang')
|
331
|
+
p '*'*50
|
332
|
+
p client.mileage('ford-mustang')
|
333
|
+
p '*'*50</code></pre>
|
334
|
+
|
335
|
+
<p>If you don’t want to repeat the vehicle’s slug over and over, you can also use it like this:</p>
|
336
|
+
|
337
|
+
|
338
|
+
<pre><code class="ruby">client = MyMileMarker::Client.new('email@domain.com', 'secret')
|
339
|
+
vehicle = MyMileMarker::Vehicle.new(client, 'ford-mustang')
|
340
|
+
|
341
|
+
p vehicle.histories
|
342
|
+
p '*'*50
|
343
|
+
p vehicle.fuel_economy
|
344
|
+
p '*'*50
|
345
|
+
p vehicle.mileage</code></pre>
|
346
|
+
|
347
|
+
<p>I <a href="http://gist.github.com/83431">gist’d the full file</a> for those interested. I have several things sitting in the HTTParty fork queue that I’m hoping to get to soon. I’ll update here with the new features when I do. If you are using HTTParty for anything, let me know in the comments below (include some code or a link to a gist if you can). I’m always curious how people are using stuff I’ve made.</p>
|
348
|
+
<div class="feedflare">
|
349
|
+
<a href="http://feeds2.feedburner.com/~ff/railstips?a=pNHW3yphdDI:kNY487i2i2U:yIl2AUoC8zA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=pNHW3yphdDI:kNY487i2i2U:dnMXMwOfBR0"><img src="http://feeds2.feedburner.com/~ff/railstips?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=pNHW3yphdDI:kNY487i2i2U:7Q72WNTAKBA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=7Q72WNTAKBA" border="0"></img></a>
|
350
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/pNHW3yphdDI" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/3/23/httparty-example-mymilemarker-com</feedburner:origLink></entry>
|
351
|
+
<entry xml:base="http://railstips.org/">
|
352
|
+
<author>
|
353
|
+
<name>john</name>
|
354
|
+
</author>
|
355
|
+
<id>tag:railstips.org,2009-03-12:8930</id>
|
356
|
+
<published>2009-03-12T18:34:00Z</published>
|
357
|
+
<updated>2009-03-12T18:36:00Z</updated>
|
358
|
+
<category term="contributing" />
|
359
|
+
<category term="thoughts" />
|
360
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/dH0YUzKSDYs/3-simple-guidelines-for-contributing" rel="alternate" type="text/html" />
|
361
|
+
<title>3 Simple Guidelines for Contributing</title>
|
362
|
+
<summary type="html"><p>In which I describe simple guidelines for contributing to other people’s open source projects.</p></summary><content type="html">
|
363
|
+
<p>In which I describe simple guidelines for contributing to other people’s open source projects.</p>
|
364
|
+
<p>So what drives someone to contribute to an open source project? 95% of the contributions I have received on my various projects are from people who are trying to make one specific thing work for something they are doing.</p>
|
365
|
+
|
366
|
+
|
367
|
+
<p><strong>I think it is awesome</strong> that people take the time to contribute, but, to be honest, a lot of the contributions are no help. They are <strong>often very specific and rarely are tested</strong> (other than manually by the contributer). Even worse yet, some change existing functionality but add no tests or explanation as to why and then expect me to just pull it in and roll on with life.</p>
|
368
|
+
|
369
|
+
|
370
|
+
<h2>1. Explain “why” not “how”</h2>
|
371
|
+
|
372
|
+
|
373
|
+
<p>I’m amazed at some of the commit messages I see in contributions. People will add a check to see if something is blank? and then make the commit message “Checking if thing was blank?”. Why do we need to check if something is blank? That is what is important. If I want to know how you did it, I’ll look at the code. Believe it or not, but I can read code. I can’t, however, read your mind like <a href="http://heroeswiki.com/Matt_Parkman">Matt Parkman</a>. <strong>Next time you craft your commit message, explain why you did something, now how.</strong></p>
|
374
|
+
|
375
|
+
|
376
|
+
<h2>2. Test it</h2>
|
377
|
+
|
378
|
+
|
379
|
+
<p>This goes a couple ways. If something was failing during “real” use of the code, but no tests were failing, don’t just fix the problem and send a pull request. Add a test (or spec) that shows what was failing and then add the code to fix the problem. <strong>If you don’t add the regression test, chances are that fix you added will break in a future release.</strong> If the project maintainer has no tests in their project, either don’t use the project or add tests. Adding tests just may inspire the maintainer to learn and fill out the rest of the tests.</p>
|
380
|
+
|
381
|
+
|
382
|
+
<p>If you don’t know how to test the thing you are fixing, <strong>ask around</strong>. I know I’d be more than happy to help anyone who is wanting to contribute to one of my projects, but is unsure how to test something. Much happier than receiving another un-tested fix that could just be someone’s opinion instead of an actual problem.</p>
|
383
|
+
|
384
|
+
|
385
|
+
<h2>3. Make sure it applies cleanly</h2>
|
386
|
+
|
387
|
+
|
388
|
+
<p>While you were working on your well-explained, tested contribution, things may have changed upstream. Be sure to rebase or merge in the latest changes. If your contribution is well-explained, tested and merges cleanly, <strong>it will only take the maintainer a few minutes to review it, accept it, and release a new version</strong>. If it doesn’t apply cleanly, and the maintainer is like me, the commit, no matter how awesome, will most likely sit at the bus stop waiting for a bus that will never come.</p>
|
389
|
+
|
390
|
+
|
391
|
+
<h2>Conclusion</h2>
|
392
|
+
|
393
|
+
|
394
|
+
<p>I nearly jump out of my seat in excitement when I receive a well-explained, tested contribution that will apply cleanly. Usually, it is less than a day before I release a new version. On the contrary, if any of those three are missing, it is usually weeks or even months.</p>
|
395
|
+
|
396
|
+
|
397
|
+
<p>The reason <strong>is not that I didn’t like the addition</strong>, but <strong>that I have to fill in pieces</strong> you missed (and I don’t have a lot of time). If you didn’t explain why, I have to figure that out (if I even can). If you didn’t test it, I have to test it. If it didn’t apply cleanly, I have to fix merge conflicts and copy and paste code around. And, if you didn’t do any of those three, trust me when I say that you will never see me click ignore faster.</p>
|
398
|
+
|
399
|
+
|
400
|
+
<p><strong>I promise you</strong> that if you do these three things each time you contribute to a project, your changes will not only get pulled in faster, but you will become <strong>a more rounded and skilled programmer</strong>.</p>
|
401
|
+
<div class="feedflare">
|
402
|
+
<a href="http://feeds2.feedburner.com/~ff/railstips?a=dH0YUzKSDYs:pK9Fw6ACqiQ:yIl2AUoC8zA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=dH0YUzKSDYs:pK9Fw6ACqiQ:dnMXMwOfBR0"><img src="http://feeds2.feedburner.com/~ff/railstips?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=dH0YUzKSDYs:pK9Fw6ACqiQ:7Q72WNTAKBA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=7Q72WNTAKBA" border="0"></img></a>
|
403
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/dH0YUzKSDYs" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/3/12/3-simple-guidelines-for-contributing</feedburner:origLink></entry>
|
404
|
+
<entry xml:base="http://railstips.org/">
|
405
|
+
<author>
|
406
|
+
<name>john</name>
|
407
|
+
</author>
|
408
|
+
<id>tag:railstips.org,2009-03-04:8919</id>
|
409
|
+
<published>2009-03-04T22:35:00Z</published>
|
410
|
+
<updated>2009-03-08T19:08:42Z</updated>
|
411
|
+
<category term="Specifically Ruby" />
|
412
|
+
<category term="clogging" />
|
413
|
+
<category term="http" />
|
414
|
+
<category term="net" />
|
415
|
+
<category term="redirects" />
|
416
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/BcXkyZgMdO0/following-redirects-with-net-http" rel="alternate" type="text/html" />
|
417
|
+
<title>Following Redirects with Net/HTTP</title>
|
418
|
+
<summary type="html"><p>In which I revive clogging by presenting a simple class that follows a limited number of redirects on its journey to an endpoint.</p></summary><content type="html">
|
419
|
+
<p>In which I revive clogging by presenting a simple class that follows a limited number of redirects on its journey to an endpoint.</p>
|
420
|
+
<p>The web is full of redirects. It isn’t that hard to figure out how to follow them using Ruby, but it always helps to have examples when you are learning. Not too long ago I was hacking on some feed auto discovery code and made a little class that, given a url, will find the endpoint and return the response from that endpoint.</p>
|
421
|
+
|
422
|
+
|
423
|
+
<p>I figured in the <a href="http://railstips.org/2007/7/11/clogging-code-blogging">old spirit of clogging</a>, I would post it here until I have time to package the full feed auto discovery library and release it on Github.</p>
|
424
|
+
|
425
|
+
|
426
|
+
<pre><code class="ruby">require 'logger'
|
427
|
+
require 'net/http'
|
428
|
+
|
429
|
+
class RedirectFollower
|
430
|
+
class TooManyRedirects &lt; StandardError; end
|
431
|
+
|
432
|
+
attr_accessor :url, :body, :redirect_limit, :response
|
433
|
+
|
434
|
+
def initialize(url, limit=5)
|
435
|
+
@url, @redirect_limit = url, limit
|
436
|
+
logger.level = Logger::INFO
|
437
|
+
end
|
438
|
+
|
439
|
+
def logger
|
440
|
+
@logger ||= Logger.new(STDOUT)
|
441
|
+
end
|
442
|
+
|
443
|
+
def resolve
|
444
|
+
raise TooManyRedirects if redirect_limit &lt; 0
|
445
|
+
|
446
|
+
self.response = Net::HTTP.get_response(URI.parse(url))
|
447
|
+
|
448
|
+
logger.info "redirect limit: #{redirect_limit}"
|
449
|
+
logger.info "response code: #{response.code}"
|
450
|
+
logger.debug "response body: #{response.body}"
|
451
|
+
|
452
|
+
if response.kind_of?(Net::HTTPRedirection)
|
453
|
+
self.url = redirect_url
|
454
|
+
self.redirect_limit -= 1
|
455
|
+
|
456
|
+
logger.info "redirect found, headed to #{url}"
|
457
|
+
resolve
|
458
|
+
end
|
459
|
+
|
460
|
+
self.body = response.body
|
461
|
+
self
|
462
|
+
end
|
463
|
+
|
464
|
+
def redirect_url
|
465
|
+
if response['location'].nil?
|
466
|
+
response.body.match(/&lt;a href=\"([^&gt;]+)\"&gt;/i)[1]
|
467
|
+
else
|
468
|
+
response['location']
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end</code></pre>
|
472
|
+
|
473
|
+
<p>You can then follow redirects as easily as this:</p>
|
474
|
+
|
475
|
+
|
476
|
+
<pre><code class="ruby">google = RedirectFollower.new('http://google.com').resolve
|
477
|
+
puts google.body</code></pre>
|
478
|
+
|
479
|
+
<p>Which when run will output something like this:</p>
|
480
|
+
|
481
|
+
|
482
|
+
<pre><code>I, [2009-03-04T17:16:58.879672 #69272] INFO -- : redirect limit: 5
|
483
|
+
I, [2009-03-04T17:16:58.880669 #69272] INFO -- : redirect found, headed to http://www.google.com/
|
484
|
+
I, [2009-03-04T17:16:58.987963 #69272] INFO -- : redirect limit: 4</code></pre>
|
485
|
+
|
486
|
+
<p>Followed by the html from www.google.com which I did not include. The logger method comes in ridiculously handy when following redirects as sometimes it is kind of hard to figure out what is going on. You can also optionally pass in a limit for how many times you would like to redirect.</p>
|
487
|
+
|
488
|
+
|
489
|
+
<pre><code class="ruby">RedirectFollower.new('http://foobar.com', 3).resolve</code></pre>
|
490
|
+
|
491
|
+
<p>This would set the number of redirects to follow to 3, instead of the default 5. You always want to put some kind of limit on the number of redirects to follow or you could end up in infinite redirection.</p>
|
492
|
+
|
493
|
+
|
494
|
+
<p>Nothing fancy but it gets the job done. Maybe if I get a chance I’ll post on how to test this by stubbing responses.</p>
|
495
|
+
<div class="feedflare">
|
496
|
+
<a href="http://feeds2.feedburner.com/~ff/railstips?a=BcXkyZgMdO0:VHCEFBru5JU:yIl2AUoC8zA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=BcXkyZgMdO0:VHCEFBru5JU:dnMXMwOfBR0"><img src="http://feeds2.feedburner.com/~ff/railstips?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=BcXkyZgMdO0:VHCEFBru5JU:7Q72WNTAKBA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=7Q72WNTAKBA" border="0"></img></a>
|
497
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/BcXkyZgMdO0" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/3/4/following-redirects-with-net-http</feedburner:origLink></entry>
|
498
|
+
<entry xml:base="http://railstips.org/">
|
499
|
+
<author>
|
500
|
+
<name>john</name>
|
501
|
+
</author>
|
502
|
+
<id>tag:railstips.org,2009-03-01:8914</id>
|
503
|
+
<published>2009-03-01T23:56:00Z</published>
|
504
|
+
<updated>2009-03-24T16:47:40Z</updated>
|
505
|
+
<category term="Site News" />
|
506
|
+
<category term="magazine" />
|
507
|
+
<category term="web hooks" />
|
508
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/1gl2IlUQhlA/first-time-in-print" rel="alternate" type="text/html" />
|
509
|
+
<title>First Time in Print</title>
|
510
|
+
<summary type="html"><p>In which I mention my first time in print, an article for the first issue of Rails magazine.</p></summary><content type="html">
|
511
|
+
<p>In which I mention my first time in print, an article for the first issue of Rails magazine.</p>
|
512
|
+
<p>The first issue of <a href="http://railsmagazine.com/">Rails Magazine</a> just came out and I wrote an article for it titled “Playing Hooky a.k.a. Web Hooks.” Below is the opening paragraph of the article to give you an idea of what it entails.</p>
|
513
|
+
|
514
|
+
|
515
|
+
<blockquote><p>From everything that I have read and experienced, web hooks are awesome! They let developers easily extend and integrate web applications and allow users to receive events and data in real-time. Yep, real-time. No polling here folks. So what are web hooks? Lets start with examples, followed by theory, and then cap it off with code.</p></blockquote>
|
516
|
+
|
517
|
+
<p><a href="http://railsmagazine.com/articles/7"><img class="image right" src="http://static.railstips.org/images/articles/rails_magazine_playing_hooky.jpg" alt="Playing Hooky Rails Magazine Screenshot" /></a></p>
|
518
|
+
|
519
|
+
|
520
|
+
<p>I really like the idea of Ruby and Rails related magazines. As much time as I spend on my computer, I rather enjoy small doses of other forms of media, such as thumbing through a magazine. I already purchased a few copies (had to get one for mom) and the process was pretty easy. The print version is $8 and I believe the <span class="caps">PDF</span> version will be available for download for free in 2 weeks (someone correct me if that is wrong).</p>
|
521
|
+
|
522
|
+
|
523
|
+
<p>One way or another, if you check out my article, comment or twitter me feedback. I took kind of a different topic than usual for me so I’m curious what others think.</p>
|
524
|
+
|
525
|
+
|
526
|
+
<p><strong>Update</strong>: The digital version of the magazine can now be <a href="http://pdf.railsmagazine.com/rails-magazine-issue1.pdf">downloaded for free</a>.</p>
|
527
|
+
<div class="feedflare">
|
528
|
+
<a href="http://feeds2.feedburner.com/~ff/railstips?a=1gl2IlUQhlA:XeGgLwODmoM:yIl2AUoC8zA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=1gl2IlUQhlA:XeGgLwODmoM:dnMXMwOfBR0"><img src="http://feeds2.feedburner.com/~ff/railstips?d=dnMXMwOfBR0" border="0"></img></a> <a href="http://feeds2.feedburner.com/~ff/railstips?a=1gl2IlUQhlA:XeGgLwODmoM:7Q72WNTAKBA"><img src="http://feeds2.feedburner.com/~ff/railstips?d=7Q72WNTAKBA" border="0"></img></a>
|
529
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/1gl2IlUQhlA" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/3/1/first-time-in-print</feedburner:origLink></entry>
|
530
|
+
<entry xml:base="http://railstips.org/">
|
531
|
+
<author>
|
532
|
+
<name>john</name>
|
533
|
+
</author>
|
534
|
+
<id>tag:railstips.org,2009-02-21:8778</id>
|
535
|
+
<published>2009-02-21T22:37:00Z</published>
|
536
|
+
<updated>2009-02-21T22:38:52Z</updated>
|
537
|
+
<category term="Testing" />
|
538
|
+
<category term="shoulda" />
|
539
|
+
<category term="testing" />
|
540
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/WBgCxrYveQU/shoulda-looked-at-it-sooner" rel="alternate" type="text/html" />
|
541
|
+
<title>Shoulda Looked At It Sooner</title>
|
542
|
+
<summary type="html"><p>In which I explain what I like about shoulda after using it for a few hours.</p></summary><content type="html">
|
543
|
+
<p>In which I explain what I like about shoulda after using it for a few hours.</p>
|
544
|
+
<p>Just a little bit ago I <a href="http://twitter.com/jnunemaker/status/1235022548">twittered</a>:</p>
|
545
|
+
|
546
|
+
|
547
|
+
<blockquote><p>I’ve been using shoulda with rspec for past week. Now trying it on fresh rails project and liking it ok thus far.</p></blockquote>
|
548
|
+
|
549
|
+
<p>To which <a href="http://opensoul.org/">Brandon Keepers</a> <a href="http://twitter.com/bkeepers/status/1235069983">replied with</a>:</p>
|
550
|
+
|
551
|
+
|
552
|
+
<blockquote><p>@jnunemaker what do you like about it?</p></blockquote>
|
553
|
+
|
554
|
+
<p>I started to send a tweet back and realized it would make an ok post here.</p>
|
555
|
+
|
556
|
+
|
557
|
+
<h2>History</h2>
|
558
|
+
|
559
|
+
|
560
|
+
<p>I remember asking Brandon at RailsConf last year why he liked RSpec so much (I was using test/spec at the time) and his answer was, “I don’t know, just because.”</p>
|
561
|
+
|
562
|
+
|
563
|
+
<p>Of course after that response, he laughed and tried to explain. One thing I’ve noticed is that it is sometimes hard to explain <em>why</em> you prefer a certain tool over another. That said, I am going to give it a shot.</p>
|
564
|
+
|
565
|
+
|
566
|
+
<h2>Shoulda with RSpec</h2>
|
567
|
+
|
568
|
+
|
569
|
+
<p>When Joe Ferris <a href="http://giantrobots.thoughtbot.com/2009/2/3/speculating-with-shoulda">announced</a> that shoulda macros could now be used with RSpec, I switched away from rspec-on-rails-matchers pretty quickly. I’ve been using shoulda’s macros with RSpec for about a little while and today when I started a new side project, because of my little bit of familiarity and interest due to using it the past while, I thought what the heck, and went all or nothing with <a href="http://www.thoughtbot.com/projects/shoulda/">shoulda</a>.</p>
|
570
|
+
|
571
|
+
|
572
|
+
<h2>Toe Dipping</h2>
|
573
|
+
|
574
|
+
|
575
|
+
<p>What is funny, is that I was completely anti-shoulda until they announced RSpec compatibility. I remember thinking, oh great, here comes another stupid testing framework. I looked it over several times and couldn’t find enough coolness to interest me in switching. When they announced that I could <strong>dip my toe in</strong> while still using RSpec, I gave it a shot. Honestly, without the toe dipping, it would have been a long time, if ever, before I even gave shoulda a fair shot.</p>
|
576
|
+
|
577
|
+
|
578
|
+
<p>The dipping of the toe method reminds me of git-svn. My first git experience was working with an svn repository. The same thing happened. I figured I had nothing to lose by dipping my toe in and a few hours later, I was hooked.</p>
|
579
|
+
|
580
|
+
|
581
|
+
<p>At first, as usual with new things, I was frustrated, followed by excited, followed by frustrated. After an hour or two of adding tests to the project, with the shoulda source code right by my side, things started to kind of click.</p>
|
582
|
+
|
583
|
+
|
584
|
+
<h2>My Two Favorite Shoulda Things</h2>
|
585
|
+
|
586
|
+
|
587
|
+
<p>1. As I look at my tests from various projects using test/unit, test/spec, rspec and this new project with shoulda, <strong>the shoulda tests just seem more readable</strong>. I don’t know if it is the context/should verbiage that I like better than describe/it or what, but my test files <strong>seem easier to scan</strong> and aesthetically more pretty (if that makes sense).</p>
|
588
|
+
|
589
|
+
|
590
|
+
<p>2. <strong>I love the shoulda controller macros</strong>. They put the few matchers that I created and used with RSpec to shame. That is not RSpec’s fault, I just love shoulda’s. I’m usually most interested in code and syntax, so here is a sample from the project I’m playing with that tests a basic sessions controller.</p>
|
591
|
+
|
592
|
+
|
593
|
+
<pre><code class="ruby">class SessionsControllerTest &lt; ActionController::TestCase
|
594
|
+
context "on GET to :new" do
|
595
|
+
setup { get :new }
|
596
|
+
|
597
|
+
should_render_a_form
|
598
|
+
should_respond_with :success
|
599
|
+
should_render_template :new
|
600
|
+
end
|
601
|
+
|
602
|
+
context "on POST to :create with valid credentials" do
|
603
|
+
setup do
|
604
|
+
User.stub!(:authenticate, :return =&gt; users(:jnunemaker))
|
605
|
+
post :create, :username =&gt; 'jnunemaker', :password =&gt; 'secret'
|
606
|
+
end
|
607
|
+
|
608
|
+
should_return_from_session :user_id, "users(:jnunemaker).id"
|
609
|
+
should_redirect_to 'root_url'
|
610
|
+
should_filter_params :username, :password
|
611
|
+
end
|
612
|
+
|
613
|
+
context "on POST to :create with invalid credentials" do
|
614
|
+
setup do
|
615
|
+
User.stub!(:authenticate, :return =&gt; nil)
|
616
|
+
post :create, :username =&gt; 'jnunemaker', :password =&gt; 'fake'
|
617
|
+
end
|
618
|
+
|
619
|
+
should_respond_with :success
|
620
|
+
should_render_template :new
|
621
|
+
should_set_the_flash_to /Could not authenticate/
|
622
|
+
end
|
623
|
+
|
624
|
+
logged_in_as :jnunemaker do
|
625
|
+
context "on DELETE to :destroy" do
|
626
|
+
setup { delete :destroy }
|
627
|
+
|
628
|
+
should 'log user out' do
|
629
|
+
session[:user_id].should be(nil)
|
630
|
+
end
|
631
|
+
|
632
|
+
should_redirect_to 'login_url'
|
633
|
+
end
|
634
|
+
end
|
635
|
+
end</code></pre>
|
636
|
+
|
637
|
+
<p><strong>Note:</strong> I’m also using Jeremy McAnally’s <a href="http://github.com/jeremymcanally/stump/tree/master">stump</a> for stubbing the User#authenticate method which makes an external web service call, his <a href="http://github.com/jeremymcanally/matchy/tree/master">matchy</a> library for the fancy session[:user_id].should assertion, and a macro I <a href="http://programblings.com/2008/10/31/two-shoulda-best-practices/">stole</a> (logged_in_as) to easily setup authentication for controller tests. Nothing fancy, but I just like the way it flows.</p>
|
638
|
+
|
639
|
+
|
640
|
+
<h2>Simple Model Test</h2>
|
641
|
+
|
642
|
+
|
643
|
+
<p>If you are in the mood for more code, here are some of the tests from my user model. Yes, I’m making a twitter client. I’m dissatisfied with pretty much all the twitter clients out there (and I’ve used them all) so I decided to whip one together. I’ll probably open source it at some point.</p>
|
644
|
+
|
645
|
+
|
646
|
+
<pre><code class="ruby">class UserTest &lt; ActiveSupport::TestCase
|
647
|
+
should_have_many :user_statuses
|
648
|
+
should_have_many :statuses, :through =&gt; :user_statuses
|
649
|
+
|
650
|
+
context "#sync_with_twitter" do
|
651
|
+
should "not assign ignored attributes" do
|
652
|
+
tweeter = new_tweeter(:id =&gt; '1234', :name =&gt; 'Shaq', :screen_name =&gt; 'THE_REAL_SHAQ', :created_at =&gt; '2006-08-13 22:56:06')
|
653
|
+
User.sync_with_twitter(tweeter, 'secret')
|
654
|
+
|
655
|
+
user = User.find_by_twitter_id('1234')
|
656
|
+
user.created_at.should_not == user.twitter_created_at
|
657
|
+
end
|
658
|
+
|
659
|
+
should "create non-existant user" do
|
660
|
+
assert_difference 'User.count' do
|
661
|
+
tweeter = new_tweeter(:id =&gt; '1234', :name =&gt; 'Shaq', :screen_name =&gt; 'THE_REAL_SHAQ')
|
662
|
+
User.sync_with_twitter(tweeter, 'secret')
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
should "update existing user" do
|
667
|
+
user = users(:jnunemaker)
|
668
|
+
tweeter = new_tweeter(:id =&gt; user.twitter_id, :name =&gt; 'New Name')
|
669
|
+
|
670
|
+
assert_no_difference 'User.count' do
|
671
|
+
User.sync_with_twitter(tweeter, 'secret')
|
672
|
+
end
|
673
|
+
|
674
|
+
user.reload
|
675
|
+
user.name.should == 'New Name'
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
def new_tweeter(attrs)
|
680
|
+
tweeter = Twitter::User.new
|
681
|
+
attrs.each { |k,v| tweeter.send("#{k}=", v) }
|
682
|
+
tweeter
|
683
|
+
end
|
684
|
+
end</code></pre>
|
685
|
+
|
686
|
+
<p>Again, I’m using Jeremy’s matchy (mentioned above) to get the <code>.should ==</code> syntax. I still enjoy RSpec, but I’m pretty impressed with Shoulda. It feels <strong>very scannable/readable and the macros are really handy</strong>, both for testing models and controllers.</p>
|
687
|
+
|
688
|
+
|
689
|
+
<p>Oh, and for those who are wondering, all I did to set things up is install the gems and add the following to my config/environments/test.rb file.</p>
|
690
|
+
|
691
|
+
|
692
|
+
<pre><code class="ruby">config.gem 'thoughtbot-shoulda', :lib =&gt; 'shoulda', :source =&gt; 'http://gems.github.com'
|
693
|
+
config.gem 'jeremymcanally-stump', :lib =&gt; 'stump', :source =&gt; 'http://gems.github.com'
|
694
|
+
config.gem 'jeremymcanally-matchy', :lib =&gt; 'matchy', :source =&gt; 'http://gems.github.com'</code></pre>
|
695
|
+
|
696
|
+
<p>Shoulda thoughts and reactions? Maybe the shoulda users out there could chime in with what they like best. I am also curious what is holding back others who haven’t tried shoulda out yet.</p>
|
697
|
+
<div class="feedflare">
|
698
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=2jZ9FoyY"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=1ZfYaCaq"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=JGwWW9wR"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
699
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/WBgCxrYveQU" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/2/21/shoulda-looked-at-it-sooner</feedburner:origLink></entry>
|
700
|
+
<entry xml:base="http://railstips.org/">
|
701
|
+
<author>
|
702
|
+
<name>john</name>
|
703
|
+
</author>
|
704
|
+
<id>tag:railstips.org,2009-02-02:8752</id>
|
705
|
+
<published>2009-02-02T19:43:00Z</published>
|
706
|
+
<updated>2009-02-02T19:46:31Z</updated>
|
707
|
+
<category term="Externals" />
|
708
|
+
<category term="bash" />
|
709
|
+
<category term="command line" />
|
710
|
+
<category term="git" />
|
711
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/iRCdTAXVtQ8/bedazzle-your-bash-prompt-with-git-info" rel="alternate" type="text/html" />
|
712
|
+
<title>Bedazzle Your Bash Prompt with Git Info</title>
|
713
|
+
<summary type="html"><p>In which I show how to bedazzle your bash prompt with color and the current git branch.</p></summary><content type="html">
|
714
|
+
<p>In which I show how to bedazzle your bash prompt with color and the current git branch.</p>
|
715
|
+
<p>I have seen this around and this morning finally decided to try it out. Thus far I am finding it surprisingly helpful. If you put the following in your bash profile, it will show the current git branch in your terminal prompt.</p>
|
716
|
+
|
717
|
+
|
718
|
+
<h2>bash profile addition</h2>
|
719
|
+
|
720
|
+
|
721
|
+
<pre>function parse_git_branch {
|
722
|
+
ref=$(git-symbolic-ref HEAD 2&gt; /dev/null) || return
|
723
|
+
echo "("${ref#refs/heads/}")"
|
724
|
+
}
|
725
|
+
|
726
|
+
PS1="\w \$(parse_git_branch)\$ "</pre>
|
727
|
+
|
728
|
+
<p>If you are not in a directory that is a git repository it will just provide a normal prompt but if you are in a directory with a git repo, you’ll get a prompt like the following, even when you switch branches:</p>
|
729
|
+
|
730
|
+
|
731
|
+
<pre>~/dev/projects/httparty (master)$ gb
|
732
|
+
integration
|
733
|
+
* master
|
734
|
+
~/dev/projects/httparty (master)$ git co integration
|
735
|
+
Switched to branch "integration"
|
736
|
+
~/dev/projects/httparty (integration)$</pre>
|
737
|
+
|
738
|
+
<p>Pretty handy, eh? I actually bedazzled mine a bit more with color and the current time like this:</p>
|
739
|
+
|
740
|
+
|
741
|
+
<h2>bedazzled bash profile addition</h2>
|
742
|
+
|
743
|
+
|
744
|
+
<pre>function parse_git_branch {
|
745
|
+
ref=$(git-symbolic-ref HEAD 2&gt; /dev/null) || return
|
746
|
+
echo "("${ref#refs/heads/}")"
|
747
|
+
}
|
748
|
+
|
749
|
+
RED="\[\033[0;31m\]"
|
750
|
+
YELLOW="\[\033[0;33m\]"
|
751
|
+
GREEN="\[\033[0;32m\]"
|
752
|
+
|
753
|
+
PS1="$RED\$(date +%H:%M) \w$YELLOW \$(parse_git_branch)$GREEN\$ "</pre>
|
754
|
+
|
755
|
+
<p>Here is a screenshot of my prompt.</p>
|
756
|
+
|
757
|
+
|
758
|
+
<p><img class="image full" src="http://static.railstips.org/images/articles/terminal_with_git_branch.jpg" alt="Terminal with git branch in prompt" /></p>
|
759
|
+
|
760
|
+
|
761
|
+
<p>Hawt. Light red blends into the black so as not to stand out. The yellow does standout, but that is because the current branch is more important to me than the directory I am in. The green color and black background is from the Homebrew theme in Terminal (requires Leopard). The reason I added the time is that I do not include the time in my menu bar. If you don’t like the colors I chose, you can <a href="http://systhread.net/texts/200703bashish.php">pick from a list</a>.</p>
|
762
|
+
|
763
|
+
|
764
|
+
<p><strong>Try this out if you haven’t yet, I guarantee you will dig it.</strong> For those of you out there that use a different shell (zsh, etc.), feel free to post how to do the same in the comments.</p>
|
765
|
+
<div class="feedflare">
|
766
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=oGnUzZoU"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=wgIRnVlT"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=QOmjnaJ0"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
767
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/iRCdTAXVtQ8" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/2/2/bedazzle-your-bash-prompt-with-git-info</feedburner:origLink></entry>
|
768
|
+
<entry xml:base="http://railstips.org/">
|
769
|
+
<author>
|
770
|
+
<name>john</name>
|
771
|
+
</author>
|
772
|
+
<id>tag:railstips.org,2009-01-31:8739</id>
|
773
|
+
<published>2009-01-31T07:26:00Z</published>
|
774
|
+
<updated>2009-03-01T22:57:26Z</updated>
|
775
|
+
<category term="Gems" />
|
776
|
+
<category term="httparty" />
|
777
|
+
<category term="json" />
|
778
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/UPm_szMCSAs/httparty-divorces-json" rel="alternate" type="text/html" />
|
779
|
+
<title>HTTParty Divorces JSON</title>
|
780
|
+
<summary type="html"><p>In which I describe the slaying of HTTParty’s lone remaining dependency, the formidable <span class="caps">JSON</span> gem.</p></summary><content type="html">
|
781
|
+
<p>In which I describe the slaying of HTTParty’s lone remaining dependency, the formidable <span class="caps">JSON</span> gem.</p>
|
782
|
+
<p>So I guess <a href="http://railstips.org/2008/12/7/httparty-divorces-activesupport">dropping ActiveSupport</a> just wasn’t enough for you people. You demanded more! Almost right away the windows and jruby people started whining (I kid, I kid). Oh, and then Pratik got a <a href="http://twitter.com/lifo/status/1152461252">little bothered</a> by the <span class="caps">JSON</span> gem’s interference with Rails. Let me just say that the HTTParty is not an exclusive one, so I figured I’d look into making life easier for the aforementioned party poopers.</p>
|
783
|
+
|
784
|
+
|
785
|
+
<p>Tonight, after watching a couple movies, I felt like hacking, so I decided to remove the <span class="caps">JSON</span> gem as a dependency. I remembered that ActiveSupport actually uses <span class="caps">YAML</span> (a core library) with some tweaks to parse <span class="caps">JSON</span>, so I pulled in that bit of code and HTTParty was officially free of its only remaining chaperone. Yep, now when you install HTTParty, no other gems install.</p>
|
786
|
+
|
787
|
+
|
788
|
+
<p>I also did some refactoring, fixed a few bugs, added a ton of specs and started to document the code base a bit. Nothing super new other than the lack of the <span class="caps">JSON</span> gem, but I figured I would officially invite windows, jruby and Pratik to the party.</p>
|
789
|
+
|
790
|
+
|
791
|
+
<h2>What next?</h2>
|
792
|
+
|
793
|
+
|
794
|
+
<p>So what is next for HTTParty? I’ve been thinking about that a lot and I have a few ideas. I’d love to see integration of <a href="http://railstips.org/2008/11/17/happymapper-making-xml-fun-again">HappyMapper</a> and HTTParty. I’m thinking this would be pretty easy with a :parser option in the request methods. Maybe something like this:</p>
|
795
|
+
|
796
|
+
|
797
|
+
<pre><code class="ruby">HTTParty.get('http://foo.com', :parser =&gt; Foo)</code></pre>
|
798
|
+
|
799
|
+
<p>Any thoughts or preferences on something like this? Then when HTTParty attempts to parse the response, it would call Foo.parse, so you could make your own parsing classes or use HappyMapper objects. Any better ideas? Also, do you have any pain points with HTTParty? Things you find monotonous when consuming web services that would be well suited for the party? Let me know.</p>
|
800
|
+
<div class="feedflare">
|
801
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=aXpJCsED"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=OsJPhHa6"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=6Cva3TRw"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
802
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/UPm_szMCSAs" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/1/31/httparty-divorces-json</feedburner:origLink></entry>
|
803
|
+
<entry xml:base="http://railstips.org/">
|
804
|
+
<author>
|
805
|
+
<name>john</name>
|
806
|
+
</author>
|
807
|
+
<id>tag:railstips.org,2009-01-28:8725</id>
|
808
|
+
<published>2009-01-28T21:57:00Z</published>
|
809
|
+
<updated>2009-01-29T09:32:36Z</updated>
|
810
|
+
<category term="Externals" />
|
811
|
+
<category term="Gems" />
|
812
|
+
<category term="httparty" />
|
813
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/ZB7DPlh4UW4/httparty-meet-mr-response" rel="alternate" type="text/html" />
|
814
|
+
<title>HTTParty Meet Mr. Response</title>
|
815
|
+
<summary type="html"><p>In which HTTParty gets a first class response object, thus exposing the original body and response code.</p></summary><content type="html">
|
816
|
+
<p>In which HTTParty gets a first class response object, thus exposing the original body and response code.</p>
|
817
|
+
<p>Whew! Been a while, eh? I declare <a href="http://railstips.org/2009/1/6/test-or-die">test awareness month</a> and then drop off the face of the earth. No fears, I’ll have more testing articles coming soon. I’ve been swamped, then sick, then swamped again and finally sick again. That is my excuse. Worthy, eh?. Lets kick the blogging train off again with a small post on some HTTParty updates released today in 0.2.8.</p>
|
818
|
+
|
819
|
+
|
820
|
+
<p><a href="http://alexvollmer.com/">Alex Vollmer</a>, in his infinite awesomeness, spearheaded a few new features, such as more <a href="http://github.com/jnunemaker/httparty/commit/a2686cf97bcdb15ca0c4af0fb57679eaf4eaf3c4">robust handling of mime types</a>, <a href="http://github.com/jnunemaker/httparty/commit/79d9ed6677da253207c6ae8db18acd8e17dab76f">authentication support</a> for the httparty <abbr title="command line interface"><span class="caps">CLI</span></abbr> and even a first class <a href="http://github.com/jnunemaker/httparty/blob/2bb92d8e862c4e5eadd2822e7c103e8bde78254c/lib/httparty/response.rb">response object</a>.</p>
|
821
|
+
|
822
|
+
|
823
|
+
<h2>HTTParty bin authentication</h2>
|
824
|
+
|
825
|
+
|
826
|
+
<p>Let’s say you want to print your timeline from twitter. With the new <span class="caps">CLI</span> basic auth support, you can do the following:</p>
|
827
|
+
|
828
|
+
|
829
|
+
<pre><code>httparty "http://twitter.com/statuses/user_timeline.json" -u username:secretpassword</code></pre>
|
830
|
+
|
831
|
+
<h2>First class response object</h2>
|
832
|
+
|
833
|
+
|
834
|
+
<p>So why should we have a first class response object? Well, sometimes it is handy to get the response code, response headers or the original body. The way HTTParty was originally created did not allow for this. Now you can do the following:</p>
|
835
|
+
|
836
|
+
|
837
|
+
<pre><code class="ruby">require 'rubygems'
|
838
|
+
require 'httparty'
|
839
|
+
|
840
|
+
response = HTTParty.get('http://twitter.com/statuses/public_timeline.json')
|
841
|
+
# the following was not possible
|
842
|
+
# with httparty in older versions
|
843
|
+
puts response.code, response.body, response.headers.inspect
|
844
|
+
|
845
|
+
# backwards compatibility should "just work"
|
846
|
+
response.each do |status|
|
847
|
+
puts status['user']['screen_name']
|
848
|
+
puts status['text']
|
849
|
+
puts ''
|
850
|
+
end</code></pre>
|
851
|
+
|
852
|
+
<p>As I said, previously the response code, headers and body were completely hidden to you. Now, <strong>instead of raising the net/http exceptions</strong>, we just return a response object with the code and body and delegate anything else to the parsed response. This was driven by a great point that Alex made: sometimes non-2xx responses have xml/json in the body that would be helpful to parse.</p>
|
853
|
+
|
854
|
+
|
855
|
+
<p>I’m pretty stoked about the updates and hope you find them helpful. Thanks to others who have committed bug fixes and such as well.</p>
|
856
|
+
|
857
|
+
|
858
|
+
<p><strong>Update</strong>: As mentioned in the comments below, Don Peterson added headers to the response object, refactored the specs and added a full suite of cucumber features. I pulled his changes in and released 0.2.9. I also updated the example above to include the headers. Thanks Don!</p>
|
859
|
+
<div class="feedflare">
|
860
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=473ZoOJk"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=bWWYry3u"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=xXb2H53E"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
861
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/ZB7DPlh4UW4" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/1/28/httparty-meet-mr-response</feedburner:origLink></entry>
|
862
|
+
<entry xml:base="http://railstips.org/">
|
863
|
+
<author>
|
864
|
+
<name>john</name>
|
865
|
+
</author>
|
866
|
+
<id>tag:railstips.org,2009-01-08:8673</id>
|
867
|
+
<published>2009-01-08T04:59:00Z</published>
|
868
|
+
<updated>2009-01-13T19:49:24Z</updated>
|
869
|
+
<category term="Testing" />
|
870
|
+
<category term="test unit" />
|
871
|
+
<category term="testing" />
|
872
|
+
<category term="validations" />
|
873
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/o_Yk8zfZqzE/test-or-die-validates-uniqueness-of" rel="alternate" type="text/html" />
|
874
|
+
<title>Test Or Die: Validates Uniqueness Of</title>
|
875
|
+
<summary type="html"><p>In which I show how to test validates_uniqueness_of with a few different options.</p></summary><content type="html">
|
876
|
+
<p>In which I show how to test validates_uniqueness_of with a few different options.</p>
|
877
|
+
<p>In the <a href="http://railstips.org/2009/1/6/test-or-die">test or die</a>, I showed a simple example of how to test validates_presence_of. Let’s build on that by adding categories and then ensure that categories have a unique name that is not case sensitive. If you haven’t been following along and want to, go <a href="http://railstips.org/2009/1/6/test-or-die">back to the beginning</a> and create your app and first test. Let’s start by running the following commands to create the category model and migration, migrate your development database and prepare your test one.</p>
|
878
|
+
|
879
|
+
|
880
|
+
<pre><code>script/generate model Category name:string
|
881
|
+
rake db:migrate
|
882
|
+
rake db:test:prepare
|
883
|
+
</code></pre>
|
884
|
+
|
885
|
+
<p>The important thing to remember when testing uniqueness of is that it does a check with the database to see if the record is unique or not. This means you need to have a record in the database to verify that the validation does in fact get triggered. You can do this several ways but since we are staying simple, we’ll do this in test/unit/category_test.rb:</p>
|
886
|
+
|
887
|
+
|
888
|
+
<pre><code class="ruby">require 'test_helper'
|
889
|
+
|
890
|
+
class CategoryTest &lt; ActiveSupport::TestCase
|
891
|
+
test 'should have unique name' do
|
892
|
+
cat1 = Category.create(:name =&gt; 'Ruby')
|
893
|
+
assert cat1.valid?, "cat1 was not valid #{cat1.errors.inspect}"
|
894
|
+
|
895
|
+
cat2 = Category.new(:name =&gt; cat1.name)
|
896
|
+
cat2.valid?
|
897
|
+
assert_not_nil cat2.errors.on(:name)
|
898
|
+
end
|
899
|
+
end
|
900
|
+
</code></pre>
|
901
|
+
|
902
|
+
<p>This breaks the one assertion per test that some people hold dear, but we are just learning right now. As you do this more and more, you will run into gotchas but by then you will know where to look for solutions. Now run rake to see if your test is failing.</p>
|
903
|
+
|
904
|
+
|
905
|
+
<pre><code>$ rake
|
906
|
+
/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/unit/category_test.rb" "test/unit/post_test.rb"
|
907
|
+
Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
|
908
|
+
Started
|
909
|
+
F.
|
910
|
+
Finished in 0.280179 seconds.
|
911
|
+
|
912
|
+
1) Failure:
|
913
|
+
test_should_have_unique_name(CategoryTest)
|
914
|
+
[./test/unit/category_test.rb:10:in `test_should_have_unique_name'
|
915
|
+
/opt/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:94:in `__send__'
|
916
|
+
/opt/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:94:in `run']:
|
917
|
+
&lt;nil&gt; expected to not be nil.
|
918
|
+
|
919
|
+
2 tests, 3 assertions, 1 failures, 0 errors</code></pre>
|
920
|
+
|
921
|
+
<p>Now that we have failed, let’s add the validation to our Category model.</p>
|
922
|
+
|
923
|
+
|
924
|
+
<pre><code class="ruby">class Category &lt; ActiveRecord::Base
|
925
|
+
validates_uniqueness_of :name
|
926
|
+
end
|
927
|
+
</code></pre>
|
928
|
+
|
929
|
+
<p>Run rake again and you will see happiness! Another way you could test the same thing above is by creating a fixture with a name of Ruby and then just use that fixture in place of cat1. The fixture file (test/fixtures/categories.yml) would look like this:</p>
|
930
|
+
|
931
|
+
|
932
|
+
<pre><code class="yaml"># Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
|
933
|
+
|
934
|
+
ruby:
|
935
|
+
name: Ruby
|
936
|
+
</code></pre>
|
937
|
+
|
938
|
+
<p>Fixtures use yaml (YAML Ain’t Markup Language) to format the data. Each fixture has a name. In this case, we named our fixture ‘ruby’. In our test, we will access it using the same name.</p>
|
939
|
+
|
940
|
+
|
941
|
+
<pre><code class="ruby">require 'test_helper'
|
942
|
+
|
943
|
+
class CategoryTest &lt; ActiveSupport::TestCase
|
944
|
+
test 'should have unique name' do
|
945
|
+
</code><strong><code>ruby = categories(:ruby)</code></strong><code class="ruby">
|
946
|
+
|
947
|
+
category = Category.new(:name =&gt; ruby.name)
|
948
|
+
category.valid?
|
949
|
+
assert_not_nil category.errors.on(:name)
|
950
|
+
end
|
951
|
+
end
|
952
|
+
</code></pre>
|
953
|
+
|
954
|
+
<p>Note the bold line above. It uses the <code>categories</code> method to access the category fixture named ruby. It returns a category just like Category.find with an id would. The difference is that we don’t define id’s in our fixtures, so we don’t know what ruby’s id would be and, more importantly, names are often more intent revealing (think fixture names like active, inactive, published, not_published).</p>
|
955
|
+
|
956
|
+
|
957
|
+
<p>That was easy but we didn’t actually verify case insensitivity. Let’s add a bit more to make sure that is in fact the case.</p>
|
958
|
+
|
959
|
+
|
960
|
+
<pre><code class="ruby">require 'test_helper'
|
961
|
+
|
962
|
+
class CategoryTest &lt; ActiveSupport::TestCase
|
963
|
+
test 'should have unique name' do
|
964
|
+
ruby = categories(:ruby)
|
965
|
+
|
966
|
+
category = Category.new(:name =&gt; ruby.name)
|
967
|
+
category.valid?
|
968
|
+
assert_not_nil category.errors.on(:name)
|
969
|
+
|
970
|
+
</code><strong><code>category.name = ruby.name.downcase
|
971
|
+
category.valid?
|
972
|
+
assert_not_nil category.errors.on(:name)</code></strong><code class="ruby">
|
973
|
+
end
|
974
|
+
end</code></pre>
|
975
|
+
|
976
|
+
<p>Run rake again and <span class="caps">FAIL</span>! OH <span class="caps">NOEZ</span>! No worries, we just forgot to add the case sensitive part to the validation.</p>
|
977
|
+
|
978
|
+
|
979
|
+
<pre><code class="ruby">class Category &lt; ActiveRecord::Base
|
980
|
+
validates_uniqueness_of :name</code><strong><code>, :case_sensitive =&gt; false</code></strong><code class="ruby">
|
981
|
+
end
|
982
|
+
</code></pre>
|
983
|
+
|
984
|
+
<p>Let’s run rake one last time and make sure that things are passing.</p>
|
985
|
+
|
986
|
+
|
987
|
+
<pre><code>$ rake
|
988
|
+
/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/unit/category_test.rb" "test/unit/post_test.rb"
|
989
|
+
Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
|
990
|
+
Started
|
991
|
+
..
|
992
|
+
Finished in 0.396569 seconds.
|
993
|
+
|
994
|
+
2 tests, 3 assertions, 0 failures, 0 errors</code></pre>
|
995
|
+
|
996
|
+
<p>Yep, we are good to go. So how would you test this with a scope on the uniqueness validation? Well, I’m not going to add a column and all that but it would look something like this if the column you were scoping uniqueness to was site_id.</p>
|
997
|
+
|
998
|
+
|
999
|
+
<pre><code class="ruby">require 'test_helper'
|
1000
|
+
|
1001
|
+
class CategoryTest &lt; ActiveSupport::TestCase
|
1002
|
+
# ... same stuff as above
|
1003
|
+
|
1004
|
+
test 'should not allow the same name for the same site' do
|
1005
|
+
ruby = categories(:ruby)
|
1006
|
+
|
1007
|
+
category = Category.new(:name =&gt; ruby.name, :site_id =&gt; ruby.site_id)
|
1008
|
+
category.valid?
|
1009
|
+
assert_not_nil category.errors.on(:name)
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
test 'should allow the same name for different sites' do
|
1013
|
+
ruby = categories(:ruby)
|
1014
|
+
site = sites(:not_ruby_site)
|
1015
|
+
|
1016
|
+
category = Category.new(:name =&gt; ruby.name, :site_id =&gt; site.id)
|
1017
|
+
category.valid?
|
1018
|
+
assert_nil category.errors.on(:name)
|
1019
|
+
end
|
1020
|
+
end</code></pre>
|
1021
|
+
|
1022
|
+
<p>That was pretty much off the top of my head, but it should give you the idea. The important thing is to test both sides. Don’t simply test that it is invalid for the same site, also test that it is valid for different sites. It may not be super important in this instance, but <strong>it is important to get into this mindset when testing</strong>.</p>
|
1023
|
+
|
1024
|
+
|
1025
|
+
<p>You always want to think about the bounds. <strong>Test out of bounds on both sides and then in bounds to make sure that all cases are covered</strong>. Again, this is just the basics. There are other ways to do this, but I just thought I would get you pointed in the right direction.</p>
|
1026
|
+
<div class="feedflare">
|
1027
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=XXmJFIML"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=1lyHTw0g"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=ALk98H91"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
1028
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/o_Yk8zfZqzE" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/1/8/test-or-die-validates-uniqueness-of</feedburner:origLink></entry>
|
1029
|
+
<entry xml:base="http://railstips.org/">
|
1030
|
+
<author>
|
1031
|
+
<name>john</name>
|
1032
|
+
</author>
|
1033
|
+
<id>tag:railstips.org,2009-01-07:8654</id>
|
1034
|
+
<published>2009-01-07T05:48:00Z</published>
|
1035
|
+
<updated>2009-01-07T05:54:49Z</updated>
|
1036
|
+
<category term="Testing" />
|
1037
|
+
<category term="testing" />
|
1038
|
+
<category term="theory" />
|
1039
|
+
<category term="thoughts" />
|
1040
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/gNn-ukDNKIs/my-testing-theory" rel="alternate" type="text/html" />
|
1041
|
+
<title>My Testing Theory</title>
|
1042
|
+
<summary type="html"><p>In which I discuss my theories when testing and what I would like to cover this month.</p></summary><content type="html">
|
1043
|
+
<p>In which I discuss my theories when testing and what I would like to cover this month.</p>
|
1044
|
+
<p>I figured I would start of this <a href="http://railstips.org/2009/1/6/test-or-die">Test or Die</a> month of January with the theories behind why and how I test. I am far from a “purist”, meaning what I care about most is that my butt is covered and that I can refactor until my little heart is content. I could care less about whether my unit tests hit the database and I really don’t want this emphasis on testing to become that.</p>
|
1045
|
+
|
1046
|
+
|
1047
|
+
<h2>Vision</h2>
|
1048
|
+
|
1049
|
+
|
1050
|
+
<p>In fact, my previous statement is really important and I’m going to say it again. <strong>I don’t want this emphasis on testing to turn into philosophical discussions</strong> on what is the true way. I want it to be down and dirty, in the mud, at the beginner level and then step slowly but surely up to intermediate and end where I am actually at (most likely not expert). I want it to be practical. I want people to learn the basics and not worry if they are doing it the “best” way. <strong>This month is all about starting</strong>.</p>
|
1051
|
+
|
1052
|
+
|
1053
|
+
<h2>Mission Statement</h2>
|
1054
|
+
|
1055
|
+
|
1056
|
+
<p>That said, what are my beliefs? Well, like I said above, I like to cover my butt. <strong>I want to know if something breaks when I change something.</strong> If I update to the latest released version of Rails or I dare to ride on the edge, I want to know if the update broke anything in my application. I have heard some speak of testing as a contract. It is an agreement with your code that it will behave under certain terms and if it doesn’t, someone call the lawyer (or turn red in autotest).</p>
|
1057
|
+
|
1058
|
+
|
1059
|
+
<h2>History</h2>
|
1060
|
+
|
1061
|
+
|
1062
|
+
<p>Just for the heck of it, I’ll give you a little look at where I am at tool-wise. I started with good old <a href="http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html">test/unit</a>. Then, a long time ago, in a windy city (literally, the windy city, Chicago), I attended a a <a href="http://chirb.org/event/show/2">chirb.org</a> meeting on <span class="caps">BDD</span> with <a href="http://rspec.info/">RSpec</a>. I thought it was cool and loved the syntax, but wasn’t ready for a switch. <a href="http://chneukirchen.org/repos/testspec/README">test/spec</a> came along and I greeted it with open arms. I used it for all of 2007 and part of 2008.</p>
|
1063
|
+
|
1064
|
+
|
1065
|
+
<p>I don’t even remember what my first project was that used RSpec, but it was after RailsConf so it must have been June or July. I’ve used pretty much only RSpec since then. I drank the kool-aide and it went down smooth. Then, I started running into errors that my tests weren’t catching, I think, in part, because I was isolating too much and not testing enough. I realized that I needed to write more tests in RSpec to get the coverage I wanted but I didn’t feel it was worth all the extra effort. Again, just my opinion, you are free to disagree, but you are wrong. Just kidding. :)</p>
|
1066
|
+
|
1067
|
+
|
1068
|
+
<p>Then, in December, after reading about <a href="http://giantrobots.thoughtbot.com/2008/11/7/a-critical-look-at-the-current-state-of-ruby-testing">the current state of ruby testing</a>, I felt swung back to good old test/unit, so I gave <a href="http://www.railstips.org/2008/12/17/using-context-and-stump-to-httparty-like-a-wufoo">context and stump</a> a try and really liked it. They provide just enough of what I need from RSpec by adding it onto vanilla test unit. I haven’t started a new Rails app since trying them out, but my next one will most likely have them both included in my test helper. Enough with the history lesson.</p>
|
1069
|
+
|
1070
|
+
|
1071
|
+
<h2>Coverage</h2>
|
1072
|
+
|
1073
|
+
|
1074
|
+
<p>Speaking of test coverage, what all do I think I’ll be covering in the next month? Well, truth is, <strong>I didn’t really think that far ahead</strong>, I just felt like testing was something I needed to push and hoped that the rest of the community would step up too. I’m actually amazed at the response thus far to be honest.</p>
|
1075
|
+
|
1076
|
+
|
1077
|
+
<p>I would like to <strong>show the different options</strong> out there in a post or two and maybe elaborate a bit on my thoughts of them. I would like to run through <strong>all the basic contracts</strong> that one would want to have in their app. Simple tests like validations, associations, controllers, etc. From there, I will probably jump out of Rails and into plain old Ruby and show how I have begun to <strong>test my gems</strong>, specifically those that interact with other services and such.</p>
|
1078
|
+
|
1079
|
+
|
1080
|
+
<p>That is really as far as I have thought ahead. I hope the discussions on each post are positive. I hope those in the know of testing <strong>help and encourage those that aren’t</strong>. I hope that this spurs those in the know that disagree with me to post their own articles and explain why I am wrong (and I will gladly link to them if you notify me). I just want to get all the opinions out there. <strong>I am fine if this takes longer than a month</strong>. Also, don’t expect every post for the next month or two to be about testing. I have some other stuff up my sleeve too.</p>
|
1081
|
+
<div class="feedflare">
|
1082
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=MCfS608l"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=S7y1TzuK"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=3Px1OoT6"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
1083
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/gNn-ukDNKIs" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/1/7/my-testing-theory</feedburner:origLink></entry>
|
1084
|
+
<entry xml:base="http://railstips.org/">
|
1085
|
+
<author>
|
1086
|
+
<name>john</name>
|
1087
|
+
</author>
|
1088
|
+
<id>tag:railstips.org,2009-01-06:8626</id>
|
1089
|
+
<published>2009-01-06T20:11:00Z</published>
|
1090
|
+
<updated>2009-01-13T19:49:15Z</updated>
|
1091
|
+
<category term="Testing" />
|
1092
|
+
<category term="test unit" />
|
1093
|
+
<category term="testing" />
|
1094
|
+
<category term="validations" />
|
1095
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/sV9v2HvQkGw/test-or-die" rel="alternate" type="text/html" />
|
1096
|
+
<title>Test Or Die</title>
|
1097
|
+
<summary type="html"><p>In which I beg the community to start testing if you aren’t or start helping others if you are.</p></summary><content type="html">
|
1098
|
+
<p>In which I beg the community to start testing if you aren’t or start helping others if you are.</p>
|
1099
|
+
<p>As you might have noticed, approximately 6 days ago, we experienced what is known as a new year. With new years come a feeling of starting over and the ridiculous idea that new habits form overnight. Resolutions are made, gyms are filled, and a month later they clear out for the disciplined to enjoy in peace once again.</p>
|
1100
|
+
|
1101
|
+
|
1102
|
+
<p>Back in the old days, you know, like 2006, I was occasionally rogue and didn’t write tests. <strong>Tests? Isn’t that what the browser is for?</strong> Right? Right? Yeah, you know. I mean I only used the browser for tests when I wrote <span class="caps">PHP</span>. Wrong. You are sorely wrong my friend. In 2007, I pushed myself to learn testing and in 2008 I pushed myself to learn RSpec. You know what? <strong>I don’t regret a minute I spent learning how to write automated tests</strong> and now I actually find writing tests enjoyable. Crazy, I know.</p>
|
1103
|
+
|
1104
|
+
|
1105
|
+
<p>I kind of assumed everyone was on the testing bandwagon. I mean lots of people seem to talk about <span class="caps">TDD</span> and <span class="caps">BDD</span> so everyone must be doing it right? Wrong. I’m in no way perfect, but I am still <strong>shocked and abhorred</strong> at the number of people out there in Ruby and Rails land that don’t test.</p>
|
1106
|
+
|
1107
|
+
|
1108
|
+
<h2>Test Awareness Month</h2>
|
1109
|
+
|
1110
|
+
|
1111
|
+
<p>Test first, test last, <span class="caps">TDD</span>, BDD, whatever. I don’t care how you do it, I just care <strong>that</strong> you do it. My mission for the month of January (and maybe longer) is to get all of you who don’t know where to start or don’t think you have time to learn or think you don’t need to learn, <strong>starting, learning and testing</strong>. Period. I’m going to start with the basics and post like a superhuman fiend until everyone is testing and the excuses are gone like donkey kong.</p>
|
1112
|
+
|
1113
|
+
|
1114
|
+
<h2>Your First Test</h2>
|
1115
|
+
|
1116
|
+
|
1117
|
+
<p>So where do we start? It all starts small and you build. You can’t download it like Neo in the Matrix unfortunately. To show you how easy it is to get started, lets run a few commands. Open up terminal and make sure you have Rails 2.2.2+ installed. Onward!</p>
|
1118
|
+
|
1119
|
+
|
1120
|
+
<pre><code>$ rails first_test
|
1121
|
+
cd first_test
|
1122
|
+
rake db:create
|
1123
|
+
rake db:create RAILS_ENV=test
|
1124
|
+
script/generate model Post title:string
|
1125
|
+
rake db:migrate
|
1126
|
+
rake db:test:prepare
|
1127
|
+
rake
|
1128
|
+
</code></pre>
|
1129
|
+
|
1130
|
+
<p>At this point, you have created an app, created both your development and test databases, added a model named post, migrated your database, ensured that your test database is equivalent to your development database and run your test suite for the first time. Wow, that was easy, right?</p>
|
1131
|
+
|
1132
|
+
|
1133
|
+
<p>Let’s go one step further for this post. We added a title to the post, wouldn’t it be nice to ensure the presence of that title? <strong>Who has used validates_presence_of before? Go ahead, raise your hand</strong>. Uh huh, everyone. Why don’t we add a test quick to make sure that it is a requirement in our app. Open up your favorite editor and view the file test/unit/post_test.rb. It should look like this:</p>
|
1134
|
+
|
1135
|
+
|
1136
|
+
<pre><code class="ruby">require 'test_helper'
|
1137
|
+
|
1138
|
+
class PostTest &lt; ActiveSupport::TestCase
|
1139
|
+
# Replace this with your real tests.
|
1140
|
+
test "the truth" do
|
1141
|
+
assert true
|
1142
|
+
end
|
1143
|
+
end</code></pre>
|
1144
|
+
|
1145
|
+
<p>Let’s replace the truth test with a check that a post is not valid without a title. Our post_test.rb file should now look like this.</p>
|
1146
|
+
|
1147
|
+
|
1148
|
+
<pre><code class="ruby">require 'test_helper'
|
1149
|
+
|
1150
|
+
class PostTest &lt; ActiveSupport::TestCase
|
1151
|
+
test "should require a title" do
|
1152
|
+
post = Post.new
|
1153
|
+
post.valid?
|
1154
|
+
assert_not_nil post.errors.on(:title)
|
1155
|
+
end
|
1156
|
+
end</code></pre>
|
1157
|
+
|
1158
|
+
<p>Now that we have that in place, let’s run our test suite and see if it worked. Go back to your terminal and run rake like this:</p>
|
1159
|
+
|
1160
|
+
|
1161
|
+
<pre><code>$ rake
|
1162
|
+
/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/unit/post_test.rb"
|
1163
|
+
Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
|
1164
|
+
Started
|
1165
|
+
F
|
1166
|
+
Finished in 0.235756 seconds.
|
1167
|
+
|
1168
|
+
1) Failure:
|
1169
|
+
test_should_require_a_title(PostTest)
|
1170
|
+
[./test/unit/post_test.rb:7:in `test_should_require_a_title'
|
1171
|
+
/opt/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:94:in `__send__'
|
1172
|
+
/opt/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:94:in `run']:
|
1173
|
+
&lt;nil&gt; expected to not be nil.
|
1174
|
+
|
1175
|
+
1 tests, 1 assertions, 1 failures, 0 errors
|
1176
|
+
</code></pre>
|
1177
|
+
|
1178
|
+
<p><strong><span class="caps">FAIL</span>! You are a failure. Give up</strong>. You can’t even write one test. I guess it is too hard. I guess it is not worth learning! Oh, wait, we didn’t add it to the post model, did we? Open up app/models/post.rb and add the validation rule.</p>
|
1179
|
+
|
1180
|
+
|
1181
|
+
<pre><code class="ruby">class Post &lt; ActiveRecord::Base
|
1182
|
+
validates_presence_of :title
|
1183
|
+
end
|
1184
|
+
</code></pre>
|
1185
|
+
|
1186
|
+
<p>Now run rake again. You should get something like this:</p>
|
1187
|
+
|
1188
|
+
|
1189
|
+
<pre><code>$ rake
|
1190
|
+
/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/unit/post_test.rb"
|
1191
|
+
Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
|
1192
|
+
Started
|
1193
|
+
.
|
1194
|
+
Finished in 0.253822 seconds.
|
1195
|
+
|
1196
|
+
1 tests, 1 assertions, 0 failures, 0 errors</code></pre>
|
1197
|
+
|
1198
|
+
<p>Yay! Our only assertion passed. That is it! Literally. You now know how to test validates_presence_of. There are some things that are hard to test, but most of it is simple stuff like this. I can promise you, testing will save your butt and eventually you’ll be able to actually code faster and better when you test, than when you don’t.</p>
|
1199
|
+
|
1200
|
+
|
1201
|
+
<h2>How Can You Help?</h2>
|
1202
|
+
|
1203
|
+
|
1204
|
+
<p>So here is the deal, I’ll try to think of a lot of the basics of testing and I’ll also try to show how to do things with different testing frameworks, but I’ll probably need some help from you. I see two ways you can help out.</p>
|
1205
|
+
|
1206
|
+
|
1207
|
+
<ol>
|
1208
|
+
<li>If you haven’t tested at all or have very little and you have something you want to test, but don’t know how, let me know? <a href="http://railstips.uservoice.com">Suggest it</a>. <a href="mailto:nunemaker@gmail.com">Email me</a>. Send a pigeon or a fax. If I have time and the ability I will try to help out and post the results on RailsTips. </li>
|
1209
|
+
<li>If you know how to test, be it in test/unit, test/spec, context, shoulda, RSpec or whatever, start thinking about what it took to get you started. If you have a blog, post articles. If you don’t, I’ll help you get them posted here. I seriously want to fill up all the Ruby and Rails blogs with testing articles from simple to complex (ie: from validates_presence_of to stubbing Net::HTTP).</li>
|
1210
|
+
</ol>
|
1211
|
+
|
1212
|
+
|
1213
|
+
<p>Friends don’t let friends code without tests. Together lets make 2009 <strong>the year of practicing best practices</strong> in Ruby and Rails and lets start it by hopping on the test bandwagon.</p>
|
1214
|
+
<div class="feedflare">
|
1215
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=NpM4l8PX"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=2eOv7quk"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=o6Xz7xbF"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
1216
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/sV9v2HvQkGw" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/1/6/test-or-die</feedburner:origLink></entry>
|
1217
|
+
<entry xml:base="http://railstips.org/">
|
1218
|
+
<author>
|
1219
|
+
<name>john</name>
|
1220
|
+
</author>
|
1221
|
+
<id>tag:railstips.org,2009-01-05:8608</id>
|
1222
|
+
<published>2009-01-05T21:18:00Z</published>
|
1223
|
+
<updated>2009-01-29T09:02:55Z</updated>
|
1224
|
+
<category term="Externals" />
|
1225
|
+
<category term="hardware" />
|
1226
|
+
<category term="software" />
|
1227
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/WToiCYR_NNs/my-setup-and-software" rel="alternate" type="text/html" />
|
1228
|
+
<title>My Setup and Software</title>
|
1229
|
+
<summary type="html"><p>In which I divulge the hardware and software that I use to get the job done.</p></summary><content type="html">
|
1230
|
+
<p>In which I divulge the hardware and software that I use to get the job done.</p>
|
1231
|
+
<p>The other day I read <a href="http://al3x.net/2009/01/02/waferbaby-interview.html">Alex Payne’s interview</a> on what hardware and software he uses and thought it was interesting. I typically find posts like this intriguing because everyone is a bit different and you can learn so much from those differences. I decided I would take the time to post on my setup and how I get things done.</p>
|
1232
|
+
|
1233
|
+
|
1234
|
+
<h2>What hardware?</h2>
|
1235
|
+
|
1236
|
+
|
1237
|
+
<p>Below is a picture of my desk. I try to keep it as minimal as possible and everything that goes on it has a purpose. If you head <a href="http://www.flickr.com/photos/johnnunemaker/3170605851/">over to flickr</a>, you can see all the annotations I added explaining the various items.</p>
|
1238
|
+
|
1239
|
+
|
1240
|
+
<p><a href="http://www.flickr.com/photos/johnnunemaker/3170605851/" title="My Desk by jnunemaker, on Flickr"><img class="image full" src="http://farm4.static.flickr.com/3094/3170605851_cce65d45b2.jpg" alt="My Desk" /></a></p>
|
1241
|
+
|
1242
|
+
|
1243
|
+
<p>I use a Macbook Air as my only machine. It has a 1.6GHz Core 2 Duo processor and 2GB of <span class="caps">RAM</span>. Occasionally, I wish for more power but it is such a sexy machine that I just overlook the deficiencies. Previously, I had a 17” Macbook Pro with 4GB of <span class="caps">RAM</span> so it was quite a switch going to a 2GB and a 13” screen. Thankfully I have a 24” Dell monitor that I work on most of the time so screen real estate is not an issue.</p>
|
1244
|
+
|
1245
|
+
|
1246
|
+
<p>One of my favorite pieces on the desk above (other than my iPhone) is the Apple Wireless keyboard. I’ve always used ergonomic keyboards so i was hesitant to switch to something flat but I am in love. When I type on it, I feel like I’m in a beautiful meadow with gazelle’s dancing past me. It’s true.</p>
|
1247
|
+
|
1248
|
+
|
1249
|
+
<p>I have a pair of Logitech speakers, nothing fancy but they sound good and allow me to occasionally <a href="http://www.last.fm/user/jnunemaker">crank up the music</a>. I currently also use a Logitech corded mouse as I am having issues with the responsiveness of my Apple Wireless Mighty Mouse. I would be lost without my iPhone 3G and it truly follows me everywhere I go in my left front pocket. I feel naked when it is not there.</p>
|
1250
|
+
|
1251
|
+
|
1252
|
+
<p>When I am on the go, I have a pair of <a href="http://addictedtonew.com/archives/254/ears-covered-in-bose/">Bose around ear headphones</a>. They are truly the best pair of headphones I have ever owned. Music sounds magical in them.</p>
|
1253
|
+
|
1254
|
+
|
1255
|
+
<p>For backups, I have a <a href="http://paulstamatiou.com/2007/08/19/review-wd-passport-portable-hard-drive">Western Digital Passport 120GB</a>. I use Time Machine and try to backup every couple days.</p>
|
1256
|
+
|
1257
|
+
|
1258
|
+
<h2>And What Software?</h2>
|
1259
|
+
|
1260
|
+
|
1261
|
+
<p>Like Alex, I spend most of my time in <a href="http://macromates.com">Textmate</a>, <a href="http://www.apple.com/macosx/technology/unix.html">Terminal</a> and <a href="http://www.apple.com/safari/">Safari</a>. No, I am not jumping on the <a href="http://nubyonrails.com/articles/emacs-emacs">Emacs bandwagon</a>. Aesthetic is too important to me. Yes, I said it. Emacs looks ugly to me. Firefox is also ugly. That is all.</p>
|
1262
|
+
|
1263
|
+
|
1264
|
+
<p>I have a gmail address that I’ve used for several years. It now forwards to my MobileMe account. I use Mail.app and my iPhone to check the MobileMe account and I use Gmail for <span class="caps">SMTP</span> so that it backs up all my sent messages as well. I use the inbox for things I need to do as soon as I get time. I have an archive folder that I archive all messages worth keeping and a waiting folder for items that I need to do someday but not right now. All the rest of my mail gets deleted. My inbox rarely gets over 5-10 emails.</p>
|
1265
|
+
|
1266
|
+
|
1267
|
+
<p>I use <a href="http://www.apple.com/macosx/features/300.html#ical">iCal</a> even though I hate it simply because it syncs with my iPhone beautifully. Syncing is also the reason I use Apple’s Address Book. I use <a href="http://www.apple.com/macosx/features/ichat.html">iChat</a> for IM (AIM and Jabber) and mostly my iPhone and occasionally the <a href="http://twitter.rubyforge.org/">command line</a> for Twitter. To keep up with tweets on the iPhone, I use text messages, <a href="http://www.atebits.com/software/tweetie/">Tweetie</a> and <a href="http://www.mustacheinc.com/summizer">Summizer</a> (one of my favorite apps), occasionally opening up Twitter’s <a href="http://m.twitter.com">mobile</a> version. I use <a href="http://www.apple.com/itunes/">iTunes</a> for playing music and <a href="http://www.last.fm/user/jnunemaker">last.fm</a> for tracking it.</p>
|
1268
|
+
|
1269
|
+
|
1270
|
+
<p>I love <a href="http://www.smileonmymac.com/TextExpander/">TextPander</a> for email signatures and phone numbers. I use <a href="http://growl.info/">growl</a> for iTunes, autotest notifications and various other things. We use <a href="http://www.getdropbox.com/">DropBox</a> for sharing files at Ordered List and I often keep current working files there just to have them automatically backed up offsite. I use <a href="http://skitch.com/">Skitch</a> for screenshots and <a href="http://store.shinywhitebox.com/home/home.html">iShowU</a> for screencasts.</p>
|
1271
|
+
|
1272
|
+
|
1273
|
+
<p>For tickets, we (Ordered List) use <a href="http://lighthouseapp.com/">Lighthouse</a> and are keeping our eye on <a href="http://sifterapp.com/">Sifter</a> (even have a paid account because we think it is going to be great, just waiting on an <span class="caps">API</span>). For code, I use <a href="http://github.com/jnunemaker">GitHub</a> and love it. I use <a href="hoptoadapp.com/">Hoptoad</a> for exceptions and <a href="http://railstips.org/2008/9/17/rails-app-monitoring">New Relic</a> for performance monitoring. For invoicing, we (Ordered List) use <a href="http://www.freshbooks.com/">Freshbooks</a>. It gets the job done. I also like <a href="http://blinksale.com">Blinksale</a> for invoicing.</p>
|
1274
|
+
|
1275
|
+
|
1276
|
+
<p>Probably my most coveted web app is a simple note application that I created. My wife and I use it a lot but it is not really public as of yet. She uses it for recipes and I use it as my digital brain. I keep track of all the millions of ideas that I have in it and use it almost hourly.</p>
|
1277
|
+
|
1278
|
+
|
1279
|
+
<p>As far as the social internet is concerned, I use <a href="http://twitter.com/jnunemaker">Twitter</a>, <a href="http://flickr.com/photos/johnnunemaker/">Flickr</a>, and <a href="http://delicious.com/jnunemaker">Delicious</a> almost every day.</p>
|
1280
|
+
|
1281
|
+
|
1282
|
+
<p>Enough about me. What software, hardware and web apps do you use? If you have a blog, post an article and link to it in the comments. If not, a comment with some details will do. I’m curious to see how others go about their business.</p>
|
1283
|
+
<div class="feedflare">
|
1284
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=lLcRWEw8"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=wD3xhnid"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=GEYJm1HW"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
1285
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/WToiCYR_NNs" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/1/5/my-setup-and-software</feedburner:origLink></entry>
|
1286
|
+
<entry xml:base="http://railstips.org/">
|
1287
|
+
<author>
|
1288
|
+
<name>john</name>
|
1289
|
+
</author>
|
1290
|
+
<id>tag:railstips.org,2009-01-05:8602</id>
|
1291
|
+
<published>2009-01-05T07:38:00Z</published>
|
1292
|
+
<updated>2009-01-05T07:38:55Z</updated>
|
1293
|
+
<category term="Gems" />
|
1294
|
+
<category term="command line" />
|
1295
|
+
<category term="httparty" />
|
1296
|
+
<link href="http://feedproxy.google.com/~r/railstips/~3/P_11y3L9KNM/httparty-goes-commando" rel="alternate" type="text/html" />
|
1297
|
+
<title>HTTParty Goes Commando</title>
|
1298
|
+
<summary type="html"><p>In which I describe the new hot command line interface for HTTParty.</p></summary><content type="html">
|
1299
|
+
<p>In which I describe the new hot command line interface for HTTParty.</p>
|
1300
|
+
<p>Hot of the press is a new feature for <a href="http://railstips.org/2008/7/29/it-s-an-httparty-and-everyone-is-invited">HTTParty</a>, everyone’s favorite way of consuming sane web services. Thanks to <a href="http://alexvollmer.com/">Alex Vollmer</a>, the party has been extended to your terminal with a sexy command line interface!</p>
|
1301
|
+
|
1302
|
+
|
1303
|
+
<p>The <span class="caps">CLI</span> is available in 0.2.5, which should be on GitHub and RubyForge by the time you read this. If you like the addition, be sure to send Alex (<a href="http://twitter.com/alexvollmer">@alexvollmer</a>) a tweet to let him know and thank him. Enough with the gratitude, give me some examples you say!</p>
|
1304
|
+
|
1305
|
+
|
1306
|
+
<h2>Installation</h2>
|
1307
|
+
|
1308
|
+
|
1309
|
+
<pre><code># GitHub
|
1310
|
+
$ sudo gem install jnunemaker-httparty --source http://gems.github.com
|
1311
|
+
|
1312
|
+
# RubyForge
|
1313
|
+
$ sudo gem install httparty</code></pre>
|
1314
|
+
|
1315
|
+
<h2>Examples</h2>
|
1316
|
+
|
1317
|
+
|
1318
|
+
<p>I use Twitter for a lot of examples, so how about we start with the Twitter public timeline.</p>
|
1319
|
+
|
1320
|
+
|
1321
|
+
<pre><code>$ httparty http://twitter.com/statuses/public_timeline.json
|
1322
|
+
[{"user"=&gt;
|
1323
|
+
{"name"=&gt;"sfwineboy",
|
1324
|
+
"url"=&gt;"http://www.new.facebook.com/profile.php?id=1401240105",
|
1325
|
+
"id"=&gt;"15651761",
|
1326
|
+
"description"=&gt;"Chill - Axin",
|
1327
|
+
"protected"=&gt;false,
|
1328
|
+
"screen_name"=&gt;"sfwineboy",
|
1329
|
+
"followers_count"=&gt;8,
|
1330
|
+
"location"=&gt;"Los Angeles, CA",
|
1331
|
+
"profile_image_url"=&gt;
|
1332
|
+
"http://s3.amazonaws.com/twitter_production/profile_images/57533829/a_281078039762fd87b263a939b652626f_normal.jpg"},
|
1333
|
+
"favorited"=&gt;false,
|
1334
|
+
"truncated"=&gt;false,
|
1335
|
+
"text"=&gt;"Chillin in LA in Mar Vista http://loopt.us/VVQu2Q",
|
1336
|
+
"id"=&gt;"1096656558",
|
1337
|
+
"in_reply_to_status_id"=&gt;nil,
|
1338
|
+
"in_reply_to_user_id"=&gt;nil,
|
1339
|
+
"source"=&gt;"&lt;a href=\"https://loopt.com\"&gt;Loopt&lt;/a&gt;",
|
1340
|
+
"created_at"=&gt;"Mon Jan 05 07:10:43 +0000 2009"},
|
1341
|
+
...
|
1342
|
+
}]</code></pre>
|
1343
|
+
|
1344
|
+
<p>By default, the command line interface to HTTParty gives you a ruby pretty printed object. It doesn’t stop there though. If you want, you can format the output using the <span class="caps">JSON</span> gem’s pretty_generate version of <span class="caps">JSON</span> or <span class="caps">REXML</span>’s attempt at pretty indented xml.</p>
|
1345
|
+
|
1346
|
+
|
1347
|
+
<pre><code>$ httparty -f json http://twitter.com/statuses/public_timeline.json
|
1348
|
+
[
|
1349
|
+
{
|
1350
|
+
"user": {
|
1351
|
+
"name": "Katrin Verclas",
|
1352
|
+
"url": "http:\/\/www.mobileactive.org",
|
1353
|
+
"id": "3125721",
|
1354
|
+
"description": "chica. curious.",
|
1355
|
+
"protected": false,
|
1356
|
+
"screen_name": "Katrinskaya",
|
1357
|
+
"followers_count": 935,
|
1358
|
+
"location": "",
|
1359
|
+
"profile_image_url": "http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/65202240\/Photo_95_normal.jpg"
|
1360
|
+
},
|
1361
|
+
"favorited": false,
|
1362
|
+
"truncated": false,
|
1363
|
+
"text": "It is 2 am and insomnia girl is trying hard not to get completely irritated at this shit. Drugs, baby, drugs.",
|
1364
|
+
"id": "1096661586",
|
1365
|
+
"in_reply_to_status_id": null,
|
1366
|
+
"in_reply_to_user_id": null,
|
1367
|
+
"source": "&lt;a href=\"http:\/\/www.naan.net\/trac\/wiki\/TwitterFon\"&gt;TwitterFon&lt;\/a&gt;",
|
1368
|
+
"created_at": "Mon Jan 05 07:15:38 +0000 2009"
|
1369
|
+
},
|
1370
|
+
...
|
1371
|
+
}
|
1372
|
+
]
|
1373
|
+
|
1374
|
+
$ httparty -f xml "http://whoismyrepresentative.com/whoismyrep.php?zip=46544&output=xml"
|
1375
|
+
&lt;result n='1'&gt;
|
1376
|
+
&lt;rep name='Joe Donnelly' district='2' office='1218 Longworth' phone='(202) 225-3915' link='http://donnelly.house.gov/' state='IN'/&gt;
|
1377
|
+
&lt;/result&gt;
|
1378
|
+
</code></pre>
|
1379
|
+
|
1380
|
+
<p>You can also use different request methods, such as post and set headers and junk like that. See the help for more information.</p>
|
1381
|
+
|
1382
|
+
|
1383
|
+
<pre><code>$ httparty -h
|
1384
|
+
USAGE: /opt/local/bin/httparty [options] [url]
|
1385
|
+
-f, --format [FORMAT] Body format: plain, json or xml
|
1386
|
+
-r, --ruby Dump output in Ruby pretty-print format
|
1387
|
+
-a, --action [ACTION] HTTP action: get (default), post, put or delete
|
1388
|
+
-d, --data [BODY] Data to put in request body (prefix with '@' for file)
|
1389
|
+
-H, --header [NAME=VALUE] Additional HTTP headers in NAME=VALUE form
|
1390
|
+
-v, --verbose If set, print verbose output
|
1391
|
+
-h, --help Show help documentation</code></pre>
|
1392
|
+
|
1393
|
+
<p>I think this addition is pretty hot and I’m excited to actually need to try it out. This is, I’m sure, just the first pass of what will be more updates to the <span class="caps">CLI</span> of HTTParty, but I can see this completely replacing curl and your browser for testing <span class="caps">API</span>’s that you are building (or wrapping). Very cool stuff and much thanks to Alex.</p>
|
1394
|
+
<div class="feedflare">
|
1395
|
+
<a href="http://feeds2.feedburner.com/~f/railstips?a=HY81qWKB"><img src="http://feeds2.feedburner.com/~f/railstips?d=41" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=NSeREbDs"><img src="http://feeds2.feedburner.com/~f/railstips?d=43" border="0"></img></a> <a href="http://feeds2.feedburner.com/~f/railstips?a=NurFu1Qj"><img src="http://feeds2.feedburner.com/~f/railstips?d=50" border="0"></img></a>
|
1396
|
+
</div><img src="http://feeds2.feedburner.com/~r/railstips/~4/P_11y3L9KNM" height="1" width="1"/></content> <feedburner:origLink>http://railstips.org/2009/1/5/httparty-goes-commando</feedburner:origLink></entry>
|
1397
|
+
</feed>
|