<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;D0ADRHg8fCp7ImA9WxNaGEU.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779</id><updated>2009-12-04T00:16:15.674Z</updated><title>Andrzej on Software</title><subtitle type="html">Thoughts on software</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>48</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/andrzejkrzywda" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry gd:etag="W/&quot;CkAARXw-eSp7ImA9WxRXGEk.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-317375765686377519</id><published>2008-10-24T09:32:00.001Z</published><updated>2008-10-24T09:32:24.251Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-24T09:32:24.251Z</app:edited><title>REST: Some tips and implementing "Forgot your password?"</title><content type="html">&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;If someone asked me what was the best thing I had learnt over the last 12 months, I would say: REST.&lt;br/&gt;&lt;br/&gt;I know that the RESTful approach requires changing our way of thinking, and it took me a while to "switch". The benefits are huge. From my experience the main advantages of REST are:&lt;br/&gt;&lt;ul&gt;&lt;li&gt;the &lt;b&gt;consistency&lt;/b&gt; it brings to the app,&lt;/li&gt;&lt;li&gt;a standardized way of creating controllers,&lt;/li&gt;&lt;li&gt;thinking always in terms of &lt;b&gt;resources&lt;/b&gt; - what is the 'thing' that the user wants to create/update/destroy?&lt;/li&gt;&lt;li&gt;a &lt;b&gt;higher-lever abstraction&lt;/b&gt; that simplifies communication with other people in the team&lt;/li&gt;&lt;li&gt;very often there's only one RESTful way of implementing a certain feature&lt;/li&gt;&lt;li&gt;consistent API, in one project we use Flex for the UI, and being RESTful makes it very easy to work with Rails controllers.&lt;br/&gt;&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;"Forget your password" as an example&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;One of the examples that is hard/not worth (according to some people) to implement in a RESTful way is "Forgot your password" functionality. Some people feel like it could be just "/users/4/reset_password" and that we could use a custom action for the users controller. I know that it can work, but I see no reason to break the REST rules here (by adding a non-REST action to the controller).&lt;br/&gt;&lt;br/&gt;&lt;b&gt;My RESTful solution&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;The feature should let users provide an email, send them a link to a page, where they can change their password.&lt;br/&gt;&lt;br/&gt;We have several steps to follow:&lt;br/&gt;&lt;ol&gt;&lt;li&gt;Show a page where people can type their email, verify the email&lt;/li&gt;&lt;li&gt;Generate a reset_password code.&lt;/li&gt;&lt;li&gt;Send an email with this link.&lt;/li&gt;&lt;li&gt;Clicking on this link should check if it's a valid reset_password code&lt;/li&gt;&lt;li&gt;If it is valid, then show a page where a user can type the new password twice.&lt;/li&gt;&lt;li&gt;Display a message appropriate for the result (error when the passwords don't match etc.)&lt;/li&gt;&lt;/ol&gt;&lt;br/&gt;Given these steps, I split the code into the following controllers and actions:&lt;br/&gt;&lt;br/&gt;&lt;b&gt;Email Verification&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;We want to let people &lt;b&gt;create&lt;/b&gt; a new &lt;b&gt;Email Verification&lt;/b&gt;, so:&lt;br/&gt;&lt;br/&gt;I created an &lt;b&gt;EmailVerificationsController&lt;/b&gt; with &lt;b&gt;new/create&lt;/b&gt; actions.&lt;br/&gt;&lt;br/&gt;It is responsible for:&lt;br/&gt;&lt;ul&gt;&lt;li&gt;finding the user by email, &lt;/li&gt;&lt;li&gt;generating the reset_password code, &lt;/li&gt;&lt;li&gt;sending the email,&lt;/li&gt;&lt;li&gt;handling errors&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;Passwords&lt;br/&gt;&lt;br/&gt;&lt;/b&gt;After veryfing the email, we want people to change (&lt;b&gt;update&lt;/b&gt;) their &lt;b&gt;Password&lt;/b&gt;, so:&lt;br/&gt;&lt;br/&gt;&lt;b&gt;PasswordController&lt;/b&gt; with &lt;b&gt;edit/update&lt;/b&gt; actions&lt;br/&gt;&lt;br/&gt;it's responsible for:&lt;br/&gt;&lt;ul&gt;&lt;li&gt;authenticating the user by the reset_password code, &lt;/li&gt;&lt;li&gt;letting the user change the password, &lt;/li&gt;&lt;li&gt;displaying errors.&lt;/li&gt;&lt;/ul&gt;&lt;br/&gt;That's it.&lt;br/&gt;&lt;br/&gt;&lt;b&gt;Techniques&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;This example shows that &lt;b&gt;a controller doesn't have to reflect a ActiveRecord class&lt;/b&gt;. There is no such model as EmailVerification in my code. It could be worth considering it, but in my case I don't think it was needed.&lt;br/&gt;&lt;br/&gt;A useful technique for REST-izing your design is to &lt;b&gt;turn verbs into nouns&lt;/b&gt;, thus making them easier to expose as resources. In this case, we wanted to verify an email, so I turned it into "creating an EmailVerification resource". &lt;br/&gt;&lt;br/&gt;Sometimes &lt;b&gt;changing the words slightly can help&lt;/b&gt;. Instead of resetting passwords, I use "update a Password".&lt;br/&gt;&lt;br/&gt;As always, &lt;b&gt;I try to move as much logic to the model as I can&lt;/b&gt;. Ideally I just call one model method and handle exceptions if there are any. &lt;a href='http://andrzejonsoftware.blogspot.com/2008/07/mvc-how-to-write-controllers.html'&gt;See this article&lt;/a&gt; for more details about this approach.&lt;br/&gt;&lt;br/&gt;&lt;b&gt;A controller can use other models&lt;/b&gt;. In our case the PasswordsController could call User.update_attributes method to change the password.&lt;br/&gt;&lt;br/&gt;&lt;b&gt;Bonus&lt;br/&gt;&lt;br/&gt;&lt;/b&gt;A friend of mine asked recently how I'd implement a feature, that let's an admin making another user a superuser. &lt;br/&gt;&lt;br/&gt;My thinking here is that I turn the "making someone a superuser" into "creating a new superuser". This shows me that we now have a resource called Superuser. According to REST a Create action expects the params to be sent using POST. So we can just POST the id of the user to the Create action. In the Create action we just call admin.make_superuser(params[:id]).&lt;br/&gt;&lt;br/&gt;&lt;b&gt;Questions&lt;br/&gt;&lt;br/&gt;&lt;/b&gt;What is your opinion about these solutions?&lt;br/&gt;Would you solve them differently?&lt;br/&gt;&lt;br/&gt;Feel free to ask me how I'd RESTfully implement other features.&lt;br/&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-317375765686377519?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/317375765686377519/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=317375765686377519" title="7 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/317375765686377519?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/317375765686377519?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/10/rest-some-tips-and-implementing-your.html" title="REST: Some tips and implementing &amp;quot;Forgot your password?&amp;quot;" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">7</thr:total></entry><entry gd:etag="W/&quot;CU8EQXw_cCp7ImA9WxdWFEg.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-3574496856125713761</id><published>2008-07-07T17:50:00.002Z</published><updated>2008-07-07T18:03:20.248Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-07T18:03:20.248Z</app:edited><title>MVC: How to write controllers</title><content type="html">&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;I've been working on many projects (web and desktop applications), and I must say that &lt;b&gt;the way MVC is implemented varies a lot&lt;/b&gt;. That's probably a good thing, since every project is different. I believe, however, that there are some common patterns that could improve most of the MVC applications.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;1. Keep it simple&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Don't create additional tiers unless they are needed. Every class should clearly belong to either Controller, Model or a View.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;2. Thin controller, fat model&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This is probably the most useful lesson I learnt from the Rails world. I'm not going to elaborate on that here, as &lt;a href='http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model'&gt;it was covered&lt;/a&gt; in many other places. Just &lt;b&gt;move all your logic to models&lt;/b&gt;. It's that simple.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. RESTful design&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If you work on a web application, and still don't know what REST is, then go and &lt;a href='http://www.b-simple.de/documents'&gt;read about it&lt;/a&gt;. It's easy and it will simplify your design a lot. Basically, it makes you think about your app as a set of resources. Every action in your controllers does one of the following with your resources: new, create, edit, update, show, destroy, index(list). At first, it can feel as a limitation, but it is the kind of limitation that actually helps you.&lt;br /&gt;&lt;br /&gt;Many people argue that not everything can be RESTified. I agree that there are places where REST is not needed, however it's always a good exercise to find a RESTful solution.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4. Communication between controllers and models&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As always, when you delegate some work further, you need to control the result somehow. The same happens when you move all the logic from controllers to models.&lt;br /&gt;&lt;br /&gt;There are three ways (that I'm aware of) of implementing the communication between a controller and a model. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;4a. Return codes&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;You use &lt;b&gt;return codes&lt;/b&gt; when your model methods return not the data, but the result of an operation. &lt;br /&gt;&lt;br /&gt;An example here is ActiveRecord::Base.save method which returns true when the save was successful, false otherwise. &lt;br /&gt;&lt;pre name='code' class='ruby'&gt;&lt;br /&gt;order = Order.new(params[:order])&lt;br /&gt;if order.save&lt;br /&gt;  flash[:message] = "The order was created!"&lt;br /&gt;  redirect_to order&lt;br /&gt;else&lt;br /&gt;  flash[:errors] = "Something was wrong"&lt;br /&gt;  redirect_to new_order_path&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In my opinion it's a good solution for simple situations. However, I don't like the "if" condition here and also it can go messy with more complex examples.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4b. Ask the object for its state&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;pre name='code' class='ruby'&gt;&lt;br /&gt;credit_card = CreditCard.new(params[:credit_card])&lt;br /&gt;if credit_card.valid?&lt;br /&gt;  if order.capture_payment(credit_card)&lt;br /&gt;    flash[:message] = "success"&lt;br /&gt;  else&lt;br /&gt;    flash[:message] = "payment failure"&lt;br /&gt;  end&lt;br /&gt;else &lt;br /&gt;  flash[:message] = "credit card validation error"&lt;br /&gt;end&lt;br /&gt;redirect_to order&lt;br /&gt;&lt;/pre&gt;This is slightly better than a), because the return code knowledge is hidden in method calls, but we still see the problem of many "if" statements.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;4c. Don't ask, tell. Use custom exceptions&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;That's my favorite, because it eliminates all the "if"s from my controller code. I know it's just a syntactical change, but I prefer to see something like this in my controller, rather than the example above:&lt;br /&gt;&lt;pre name='code' class='ruby'&gt;&lt;br /&gt;begin&lt;br /&gt;  order.pay(credit_card)&lt;br /&gt;  flash[:message] = "success"&lt;br /&gt;rescue CreditCardNotValid&lt;br /&gt;  flash[:message] = "credit card validation error"&lt;br /&gt;rescue PaymentFailed&lt;br /&gt;  flash[:message] = "payment failure"&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here we use custom exceptions to define a "protocol" for the communication between a model and a controller. Obviously we need to define the exceptions somewhere. I like to add them at the top of the associated model class:&lt;br /&gt;&lt;pre name='code' class='ruby'&gt;&lt;br /&gt;class CreditCardNotValid &amp;lt; Exception&lt;br /&gt;class PaymentFailed &amp;lt; Exception&lt;br /&gt;&lt;br /&gt;class Order &amp;lt; ActiveRecord::Base&lt;br /&gt;  def pay(credit_card)&lt;br /&gt;    ...&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So, what are &lt;b&gt;your&lt;/b&gt; favorite patterns for writing controllers?&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-3574496856125713761?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/3574496856125713761/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=3574496856125713761" title="14 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/3574496856125713761?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/3574496856125713761?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/07/mvc-how-to-write-controllers.html" title="MVC: How to write controllers" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">14</thr:total></entry><entry gd:etag="W/&quot;CUEBSH8zeSp7ImA9WxdXFEQ.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-2350440603784333460</id><published>2008-06-26T15:18:00.002Z</published><updated>2008-06-26T15:20:59.181Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-26T15:20:59.181Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="git" /><title>Git: working with branches</title><content type="html">&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;The way we work with git is that for every &lt;a href='http://andrzejonsoftware.blogspot.com/2008/02/remote-pair-programming.html'&gt;remote pair programming&lt;/a&gt; session we create a separate branch. We give it a name after the feature we're working on.&lt;br/&gt; &lt;br/&gt;&lt;pre name='code' class='ruby'&gt;&lt;br/&gt;git checkout -b feature1&lt;br/&gt;&lt;/pre&gt;&lt;br/&gt;&lt;br/&gt;It automatically switches to this branch.&lt;br/&gt;&lt;br/&gt;During our work we tend to checkin our changes quite often. We do it with:&lt;br/&gt;&lt;br/&gt;&lt;pre name='code' class='ruby'&gt;&lt;br/&gt;git checkin -a -m "created some specs for class A"&lt;br/&gt;&lt;/pre&gt; &lt;br/&gt;&lt;br/&gt;After we finish our session, we do two things.&lt;br/&gt;First, we merge our branch to master:&lt;br/&gt;&lt;br/&gt;&lt;pre name='code' class='ruby'&gt;&lt;br/&gt;git checkout master&lt;br/&gt;git merge feature1&lt;br/&gt;&lt;/pre&gt;&lt;br/&gt;&lt;br/&gt;Then, we delete the branch we were working on:&lt;br/&gt;&lt;br/&gt;&lt;pre name='code' class='ruby'&gt;&lt;br/&gt;git branch -D feature1&lt;br/&gt;&lt;/pre&gt;&lt;br/&gt;&lt;br/&gt;That's it.&lt;br/&gt;&lt;br/&gt;If you happen to delete the branch BEFORE you merge it, don't panic, there is a solution.&lt;br/&gt;In order to undelete a branch just after you deleted it, do:&lt;br/&gt;&lt;br/&gt;&lt;pre name='code' class='ruby'&gt;&lt;br/&gt;git checkout -b new_branch_name HEAD@{1}&lt;br/&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-2350440603784333460?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/2350440603784333460/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=2350440603784333460" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2350440603784333460?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2350440603784333460?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/06/git-working-with-branches.html" title="Git: working with branches" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total></entry><entry gd:etag="W/&quot;CUQMQHo8cSp7ImA9WxdQEUw.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-4722591122638579891</id><published>2008-06-10T11:58:00.004Z</published><updated>2008-06-10T15:56:21.479Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-10T15:56:21.479Z</app:edited><title>Andrzej's Rails tips #11</title><content type="html">&lt;div xmlns="http://www.w3.org/1999/xhtml"&gt;Today I'm going to show you two tips, both related to Rails controllers.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Use the current_user object whenever you access its data&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Instead of&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;@order = Order.find(params[:order_id])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;do this:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;@order = current_user.orders.find(params[:order_id])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Thanks to that, you don't have to check whether this order belongs to the user, you just need to handle ActiveRecord::RecordNotFound exception.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Move all the logic from your controller to the model&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I know you already read this statement many times, but I will repeat it anyway.&lt;br /&gt;In your actions you shouldn't manipulate your model objects, do it in the model class itself. Here's a simple example:&lt;br /&gt;&lt;br /&gt;BAD CODE:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;class OrdersController &amp;lt; ApplicationController&lt;br /&gt; def update&lt;br /&gt;   @order = current_user.orders.find(params[:order_id])&lt;br /&gt;   if params[:order][:amount] &amp;gt; 0&lt;br /&gt;     @order.prepare_invoice&lt;br /&gt;     @order.send_email&lt;br /&gt;     @order.mark_as_paid&lt;br /&gt;     @order.notify_producers&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;BETTER CODE:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;class OrdersController &amp;lt; ApplicationController&lt;br /&gt; def update&lt;br /&gt;   @order = current_user.orders.find(params[:order_id])&lt;br /&gt;   @order.pay(params[:order][:amount])&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Order &amp;lt; ActiveRecord::Base&lt;br /&gt; def pay(amount)&lt;br /&gt;   if amount &amp;gt; 0     &lt;br /&gt;     prepare_invoice&lt;br /&gt;     send_email&lt;br /&gt;     mark_as_paid&lt;br /&gt;     notify_producers&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-4722591122638579891?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/4722591122638579891/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=4722591122638579891" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/4722591122638579891?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/4722591122638579891?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/06/andrzej-rails-tips-11.html" title="Andrzej&amp;#39;s Rails tips #11" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;AkEHQnw9eSp7ImA9WxdRF0g.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-5987017089840451237</id><published>2008-06-06T13:23:00.001Z</published><updated>2008-06-06T13:23:53.261Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-06T13:23:53.261Z</app:edited><title>Andrzej's Rails tips #10</title><content type="html">&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;b&gt;form_for and namespace route&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;When you use a namespace route like the following:&lt;br/&gt;&lt;br/&gt;&lt;pre class='ruby' name='code'&gt;&lt;br/&gt;  map.namespace :admin do |a|&lt;br/&gt;    a.resources :users&lt;br/&gt;  end&lt;br/&gt;&lt;/pre&gt;&lt;br/&gt;&lt;br/&gt;then if you want to use &lt;b&gt;&lt;i&gt;form_for @user&lt;/i&gt;&lt;/b&gt;, this is the correct way:&lt;br/&gt;&lt;br/&gt;&lt;pre class='ruby' name='code'&gt;&amp;lt;% form_for([:admin, @user]) do |f| %&amp;gt;&lt;br/&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-5987017089840451237?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/5987017089840451237/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=5987017089840451237" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5987017089840451237?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5987017089840451237?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/06/andrzej-rails-tips-10.html" title="Andrzej&amp;#39;s Rails tips #10" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;D0UNRH4_eyp7ImA9WxdRF0g.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-7311846710745089126</id><published>2008-06-06T12:28:00.001Z</published><updated>2008-06-06T12:28:15.043Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-06T12:28:15.043Z</app:edited><title>2 minutes with David Chelimsky and RSpec stories (video)</title><content type="html">&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;div class='youtube-video'&gt;&lt;object width='400' height='300'&gt;    &lt;param name='allowfullscreen' value='true'&gt; &lt;/param&gt;    &lt;param name='allowscriptaccess' value='always'&gt; &lt;/param&gt;    &lt;param name='movie' value='http://www.vimeo.com/moogaloop.swf?clip_id=1104534&amp;amp;server=www.vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=&amp;amp;fullscreen=1'&gt; &lt;/param&gt;    A short (2.34 minutes) description of RSpec stories.&lt;br /&gt;&lt;br /&gt;&lt;embed width='400' height='300' src='http://www.vimeo.com/moogaloop.swf?clip_id=1104534&amp;amp;server=www.vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=&amp;amp;fullscreen=1' type='application/x-shockwave-flash' allowfullscreen='true' allowscriptaccess='always'&gt; &lt;/embed&gt;  &lt;/object&gt;&lt;/div&gt;&lt;br /&gt;&lt;a href='http://www.vimeo.com/1104534?pg=embed&amp;amp;sec=1104534'&gt;David Chelimsky at Railsconf 2008&lt;/a&gt; from &lt;a href='http://www.vimeo.com/user504460?pg=embed&amp;amp;sec=1104534'&gt;Gregg Pollack&lt;/a&gt; on &lt;a href='http://vimeo.com?pg=embed&amp;amp;sec=1104534'&gt;Vimeo&lt;/a&gt;.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-7311846710745089126?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/7311846710745089126/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=7311846710745089126" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/7311846710745089126?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/7311846710745089126?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/06/2-minutes-with-david-chelimsky-and.html" title="2 minutes with David Chelimsky and RSpec stories (video)" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DUINRXk6eCp7ImA9WxdSE0U.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-5205244837116524612</id><published>2008-05-21T16:14:00.004Z</published><updated>2008-05-21T16:33:14.710Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-21T16:33:14.710Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="webrat" /><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="tdd" /><category scheme="http://www.blogger.com/atom/ns#" term="bdd" /><title>Story Driven Development</title><content type="html">If you are interested in testing and the TDD/BDD movements, then you may find &lt;span class="caption"&gt;Bryan Helmkamp&lt;/span&gt; &lt;a href="http://goruco2008.confreaks.com/01_helmkamp.html"&gt;presentation on "Story Driven Development"&lt;/a&gt; worth watching. &lt;a href="http://www.brynary.com/"&gt;Bryan&lt;/a&gt; explains the differences between unit testing and scenarios. He also shows &lt;a href="http://github.com/brynary/webrat/tree/master"&gt;Webrat&lt;/a&gt;, a tool he works on, which makes defining stories in Rails apps very easy.&lt;br /&gt;&lt;br /&gt;I like the title that Bryan has chosen for the talk. The name "Story Driven Development" makes it clear that it's a lot about defining requirements, and not only about testing. Additionally, I think it's clear that it focuses on the acceptance level of tests without mentioning unit tests.&lt;br /&gt;&lt;br /&gt;He also provides a quote from Robert Martin:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Unit tests: “Make sure you write the code right.”&lt;br /&gt;Scenarios: “Make sure you write the right code.”&lt;/blockquote&gt;&lt;br /&gt;You can watch the slides here, or &lt;a href="http://www.brynary.com/2008/4/26/story-driven-development-slides-posted"&gt;grab the PDF version&lt;/a&gt; (which has correct code examples).&lt;br /&gt;&lt;br /&gt;&lt;div style="width: 425px; text-align: left;" id="__ss_373724"&gt;&lt;object style="margin: 0px;" height="355" width="425"&gt;&lt;param name="movie" value="http://static.slideshare.net/swf/ssplayer2.swf?doc=goruco-1209228802840166-8"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;embed src="http://static.slideshare.net/swf/ssplayer2.swf?doc=goruco-1209228802840166-8" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="355" width="425"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size: 11px; font-family: tahoma,arial; height: 26px; padding-top: 2px;"&gt;&lt;a href="http://www.slideshare.net/?src=embed"&gt;&lt;img src="http://static.slideshare.net/swf/logo_embd.png" style="border: 0px none ; margin-bottom: -5px;" alt="SlideShare" /&gt;&lt;/a&gt; | &lt;a href="http://www.slideshare.net/brynary/story-driven-development?src=embed" title="View 'Story Driven Development' on SlideShare"&gt;View&lt;/a&gt; | &lt;a href="http://www.slideshare.net/upload?src=embed"&gt;Upload your own&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-5205244837116524612?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/5205244837116524612/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=5205244837116524612" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5205244837116524612?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5205244837116524612?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/05/story-driven-development.html" title="Story Driven Development" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DEUNQXgycSp7ImA9WxZbEks.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-8597205108365374295</id><published>2008-04-15T13:29:00.000Z</published><updated>2008-04-15T13:31:30.699Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-15T13:31:30.699Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rspec" /><category scheme="http://www.blogger.com/atom/ns#" term="webrat" /><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="tdd" /><category scheme="http://www.blogger.com/atom/ns#" term="bdd" /><title>"TDD with Rails" - the slides from my talk at RuPy 2008</title><content type="html">&lt;div style="width:425px;text-align:left" id="__ss_354273"&gt;&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slideshare.net/swf/ssplayer2.swf?doc=tddtalk-1208265753264524-8"/&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed src="http://static.slideshare.net/swf/ssplayer2.swf?doc=tddtalk-1208265753264524-8" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;"&gt;&lt;a href="http://www.slideshare.net/?src=embed"&gt;&lt;img src="http://static.slideshare.net/swf/logo_embd.png" style="border:0px none;margin-bottom:-5px" alt="SlideShare"/&gt;&lt;/a&gt; | &lt;a href="http://www.slideshare.net/andrzejkrzywda/tdd-with-rails?src=embed" title="View 'TDD with Rails' on SlideShare"&gt;View&lt;/a&gt; | &lt;a href="http://www.slideshare.net/upload?src=embed"&gt;Upload your own&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-8597205108365374295?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/8597205108365374295/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=8597205108365374295" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/8597205108365374295?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/8597205108365374295?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/04/tdd-with-rails-slides-from-my-talk-at.html" title="&quot;TDD with Rails&quot; - the slides from my talk at RuPy 2008" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total></entry><entry gd:etag="W/&quot;C0MFSHo-fyp7ImA9WxZUFks.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-7242106875782099330</id><published>2008-04-08T12:54:00.006Z</published><updated>2008-04-08T13:30:19.457Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-08T13:30:19.457Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ruby" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><title>Google App Engine and dynamic languages</title><content type="html">Google has launched &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt;. It's a big news. But look at this:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;"Although Python is currently the only language supported by Google App Engine, we look forward to supporting more languages in the future."&lt;/blockquote&gt;&lt;br /&gt;You see, &lt;span style="font-weight: bold;"&gt;Python is their first choice&lt;/span&gt; as the main language for Google App Engine. It's not that surprising, Google is known to be a Python company. I'm pretty sure that Java, .Net and Ruby support will be available very soon as well. I wonder when will be the first time that some of the Google (or other companies) services are available &lt;span style="font-weight: bold;"&gt;only with dynamic languages&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;What is my point here?&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;If you are serious about your career in IT then you have to &lt;span style="font-weight: bold;"&gt;start learning how to develop software with dynamic languages&lt;/span&gt;. &lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Python or Ruby seem to be good candidates. If you ask me - if you come from .NET world go for IronPython, if you come from Java choose JRuby. That's a good start. Good luck!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-7242106875782099330?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/7242106875782099330/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=7242106875782099330" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/7242106875782099330?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/7242106875782099330?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/04/google-app-engine-and-dynamic-languages.html" title="Google App Engine and dynamic languages" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DEcDR3Y-fCp7ImA9WxZUEU8.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-1103467315178190178</id><published>2008-04-02T08:42:00.003Z</published><updated>2008-04-02T08:47:56.854Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-02T08:47:56.854Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rspec" /><category scheme="http://www.blogger.com/atom/ns#" term="webrat" /><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="bdd" /><title>BDD examples with user stories and Webrat</title><content type="html">This is the best BDD explanation I have ever seen. It explains all the philosophy behind BDD, shows how it fits with RSpec stories, and how you can use Webrat to create a high-level integration test. It even shows how to use Selenium with RSpec stories!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.chariotsolutions.com/slides/pdfs/ete2008-IntegrationTestingWithRSpec.pdf"&gt;Integration Testing in Ruby with RSpec's Story Automation Framework by David Chelimsky&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Big thanks to David Chelimsky!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-1103467315178190178?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/1103467315178190178/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=1103467315178190178" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/1103467315178190178?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/1103467315178190178?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/04/bdd-examples-with-user-stories-and.html" title="BDD examples with user stories and Webrat" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DkIASHozfSp7ImA9WxZVFkk.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-1142364549305276475</id><published>2008-03-27T18:40:00.002Z</published><updated>2008-03-27T19:02:29.485Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-27T19:02:29.485Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="euruko2008" /><title>Going to Euruko 2008?</title><content type="html">Oh, how I love the conference season!&lt;br /&gt;&lt;br /&gt;After attending the &lt;a href="http://sfi.org.pl/"&gt;SFI&lt;/a&gt; conference in Krakow (which was great, BTW!) I'm ready to go to Prague.I'm going to be at the &lt;a href="http://www.euruko2008.org/"&gt;Euruko 2008&lt;/a&gt; conference. &lt;a href="http://www.euruko2008.org/pages/2-program"&gt;Their program&lt;/a&gt; looks really interesting! All things that I like: Ruby, JRuby, testing. Hey, there's even a talk about AOP in Ruby!&lt;br /&gt;&lt;br /&gt;I'll be in Prague from Friday afternoon till Sunday evening, and I'm always happy to chat about  Rails, TDD, Agile, JRuby.&lt;br /&gt;&lt;br /&gt;I'll try to comment about the conference &lt;a href="http://twitter.com/andrzejkrzywda"&gt;on my Twitter&lt;/a&gt;. &lt;a href="http://www.rubyinside.com/going-to-euruko-2008-tomorrow-see-you-there-827.html"&gt;Peter Cooper&lt;/a&gt; is also going to &lt;a href="http://twitter.com/peterc/"&gt;update his twitter&lt;/a&gt;. Any other Twitterers in Prague?&lt;br /&gt;&lt;br /&gt;See you in Prague!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-1142364549305276475?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/1142364549305276475/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=1142364549305276475" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/1142364549305276475?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/1142364549305276475?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/03/going-to-euruko-2008.html" title="Going to Euruko 2008?" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CUYBSHsycCp7ImA9WxZVFkk.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-5813553530815448423</id><published>2008-03-27T18:19:00.003Z</published><updated>2008-03-27T18:39:19.598Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-27T18:39:19.598Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><category scheme="http://www.blogger.com/atom/ns#" term="migrations" /><title>Andrzej's Rails tips #9</title><content type="html">&lt;span style="font-weight: bold;"&gt;link_to_remote and GET request&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;If you're working with &lt;span style="font-style: italic;"&gt;link_to_remote&lt;/span&gt; and you are surprised with a message like the following:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;span style="font-style: italic;"&gt;Only get, put, and delete requests are allowed.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;then you just need to know that link_to_remote uses POST request by default. All you need is to  do is to add &lt;span style="font-style: italic; font-weight: bold;"&gt;:method =&gt; :get&lt;/span&gt; to the method call.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Use schema.rb to create a new database&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;Keep the schema.rb file in your Subversion/Git/Mercurial repository and make sure it's up-to-date. It's very useful when you want to create a database without using migrations. You just call &lt;span style="font-weight: bold;"&gt;&lt;span style="font-style: italic;"&gt;rake db:schema:load&lt;/span&gt;&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;As the documentation states:&lt;br /&gt;&lt;br /&gt;"Note that this &lt;span style="font-weight: bold;"&gt;schema.rb&lt;/span&gt; definition &lt;span style="font-weight: bold;"&gt;is the authoritative source for your database schema&lt;/span&gt;. If you need to create the application database on another system, you should be using db:schema:load, not running all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations you'll amass, the slower it'll run and the greater likelihood for issues)."&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-5813553530815448423?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/5813553530815448423/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=5813553530815448423" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5813553530815448423?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5813553530815448423?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/03/andrzejs-rails-tips-9.html" title="Andrzej's Rails tips #9" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CEYGQ3cyeip7ImA9WxZQGU4.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-6981882734817474418</id><published>2008-02-25T07:09:00.003Z</published><updated>2008-02-25T09:35:22.992Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-25T09:35:22.992Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="resource_controller" /><category scheme="http://www.blogger.com/atom/ns#" term="migrations" /><title>Andrzej's Rails tips #8</title><content type="html">&lt;span style="font-weight: bold;"&gt;Resource controller and 'new' action&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If you want to refer to the 'new' action using &lt;a href="http://jamesgolick.com/tags/resource_controller"&gt;resource controller&lt;/a&gt;, you have to use the 'new_action' method.&lt;br /&gt;BTW, if you want to do the same operation before both 'new' and 'edit' actions in a resource controller, you can do it like that:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;[new_action, edit].each do |action|&lt;br /&gt; action.before { @product_types = ProductType.find(:all) }&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Migrations and removing defaults&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If you have some default values set on some columns, then you can remove them using ':default =&gt; nil'.&lt;br /&gt;Like that:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;change_column :users, :name, :string, :default =&gt; nil&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-6981882734817474418?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/6981882734817474418/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=6981882734817474418" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/6981882734817474418?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/6981882734817474418?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/andrzejs-rails-tips-8.html" title="Andrzej's Rails tips #8" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DUEGQnc9fSp7ImA9WxZQFEk.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-8328666141240701505</id><published>2008-02-19T18:30:00.001Z</published><updated>2008-02-19T19:00:23.965Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-19T19:00:23.965Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="attachment-fu" /><category scheme="http://www.blogger.com/atom/ns#" term="rake" /><category scheme="http://www.blogger.com/atom/ns#" term="capistrano" /><title>Andrzej's Rails tips #7</title><content type="html">&lt;span style="font-weight: bold;"&gt;Dumping and loading data&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Recently, I was in a need to dump data from a sqlite database and load it to a mysql database. There are many ways of doing such a task. One of them is using a plugin released by the Heroku people - &lt;a href="http://blog.heroku.com/archives/tags/opensource"&gt;YamlDb&lt;/a&gt;.&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;rake db:data:dump&lt;br /&gt;rake db:data:load&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can install it with the following:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;script/plugin install http://opensource.heroku.com/svn/rails_plugins/yaml_db&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;attachment-fu and capistrano&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I use &lt;a href="http://agilewebdevelopment.com/plugins/attachment_fu"&gt;attachment-fu&lt;/a&gt; for one of my projects. One of the open questions here is how to deal with the files that our users uploaded to the server. I use capistrano 2, and ideally I prefer to have everything automated. The way you can use capistrano to deal with uploaded files is to have a directory which will be shared across different releases (the same as logs).&lt;br /&gt;&lt;br /&gt;First, we need to tell attachment-fu that the upload directory is going to be public/uploads. Then, we need to tell Capistrano that public/uploads is going to be a shared directory. Both those things are nicely explained in the following article:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.almosteffortless.com/2007/03/25/working-with-attachment_fu/"&gt;Working with attachment_fu&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-8328666141240701505?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/8328666141240701505/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=8328666141240701505" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/8328666141240701505?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/8328666141240701505?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/andrzejs-rails-tips-7.html" title="Andrzej's Rails tips #7" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;A04NRHgyfSp7ImA9WxZQEkU.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-9133277948978899699</id><published>2008-02-17T23:55:00.001Z</published><updated>2008-02-17T23:13:15.695Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-17T23:13:15.695Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><title>Andrzej's Rails tips #6</title><content type="html">&lt;span style="font-weight: bold;"&gt;Redo a migration&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There is a new rake task for redoing a migration:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rake db:migrate:redo&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;BTW, if you need to do it on a production server, then just append RAILS_ENV:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rake db:migrate:redo RAILS_ENV=production&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and of course, if you want to list all rake tasks:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rake -T&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Migrating to Rails 2.0 and mass-assignment warning&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I was upgrading one application from Rails 1.2.3 to Rails 2.0. The application uses restful_authentication. One of the problems we have seen, was a warning in the log, saying something like 'mass-assignment no longer supported'. After investigation we found out that the problem was updating the user object. The User model was generated with restful_authentication, but later there were some application specific attributes added by a developer. Since restful_authentication generates a line like that:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;attr_accessible :email&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;it means other attributes are not accessible. Adding the new attributes to this list solves the problem.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;atttr_accessible :email, :birthdate&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-9133277948978899699?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/9133277948978899699/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=9133277948978899699" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/9133277948978899699?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/9133277948978899699?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/andrzejs-rails-tips-6.html" title="Andrzej's Rails tips #6" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;A0YAQ34-fyp7ImA9WxZRGE0.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-40644694191314755</id><published>2008-02-11T21:01:00.000Z</published><updated>2008-02-12T09:39:02.057Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-12T09:39:02.057Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rspec" /><category scheme="http://www.blogger.com/atom/ns#" term="webrat" /><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="bdd" /><title>Andrzej's Rails tips #5</title><content type="html">Two things today, both related to RSpec stories: webrat, and using regexps in RSpec stories.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;Webrat with RSpec stories&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What is &lt;a href="http://www.brynary.com/uploads/webrat/rdoc/index.html"&gt;Webrat&lt;/a&gt;? "Webrat lets you quickly write robust and thorough acceptance tests for a Ruby web application". It uses Hpricot under the hood and is very easy to understand just by looking at the code.&lt;br /&gt;&lt;br /&gt;It took me only 30 minutes to turn most of my tests in one of my applications from a classic IntegrationTest-based RSpec story to Webrat. Here is one example:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;When "he creates an order" do&lt;br /&gt;  visits '/'&lt;br /&gt;  clicks_link "New order"&lt;br /&gt;  fills_in "Nr", :with =&gt; 'abc/2008'&lt;br /&gt;  fills_in "Company", :with =&gt; 'ABC company'&lt;br /&gt;  selects 'New'&lt;br /&gt;  clicks_button 'Create'&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Thanks to Ben, for his &lt;a href="http://www.benmabey.com/2008/02/04/rspec-plain-text-stories-webrat-chunky-bacon/"&gt;great article&lt;/a&gt; describing RSpec stories with Webrat.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;RSpec, response.should have_text&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Sometimes, all you need is just a check whether there is a certain message visible on a page. One way of doing that is with regexps. Here is an example step that checks for the message:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;  Then "he sees a $message" do |message|&lt;br /&gt;    response.should have_text(Regexp.new(message))&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-40644694191314755?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/40644694191314755/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=40644694191314755" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/40644694191314755?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/40644694191314755?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/andrzejs-rails-tips-5.html" title="Andrzej's Rails tips #5" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CkADRXs7eCp7ImA9WxZRE0k.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-2955201658873742550</id><published>2008-02-07T01:00:00.000Z</published><updated>2008-02-06T23:32:54.500Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-06T23:32:54.500Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><title>Andrzej's Rails tips #4</title><content type="html">&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;will_paginate in other languages&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The default labels for the will_paginate are 'Next' and 'previous'. It's trivial to change them like that:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;&lt;%= will_paginate :prev_label =&gt; 'Wstecz', :next_label =&gt; 'Dalej'%&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;Rails plugins, Piston and URLs&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I use piston for plugins management. It's a nice tool that allows you to call 'piston up' on any of the plugins, at any time to update. What it does worse than 'script/plugin' command is managing different repositories. With the 'plugin' command you can say:&lt;br /&gt;&lt;pre&gt;script/plugin source URL&lt;/pre&gt;and it adds the URL to the list of known repositories.&lt;br /&gt;&lt;br /&gt;What I use with Piston instead, is storing the urls as environment variables. Here is an excerpt of my ~/.bash_login file:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;export ATTACHMENT_FU=http://svn.techno-weenie.net/projects/plugins/attachment_fu/&lt;br /&gt;export RESOURCE_CONTROLLER=http://svn.jamesgolick.com/resource_controller/tags/stable/&lt;br /&gt;export ATTRIBUTE_FU=http://svn.jamesgolick.com/attribute_fu/tags/stable&lt;br /&gt;export RESTFUL_AUTHENTICATION=http://svn.techno-weenie.net/projects/plugins/restful_authentication/&lt;br /&gt;export RSPEC=http://rspec.rubyforge.org/svn/tags/CURRENT/rspec&lt;br /&gt;export RSPEC_ON_RAILS=http://rspec.rubyforge.org/svn/tags/CURRENT/rspec_on_rails&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So I just type 'piston import $ATTA', press TAB, and the shell autocompletion does it for me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-2955201658873742550?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/2955201658873742550/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=2955201658873742550" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2955201658873742550?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2955201658873742550?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/andrzejs-rails-tips-4.html" title="Andrzej's Rails tips #4" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DEQCQXw_eCp7ImA9WxZREkU.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-5464256457914225321</id><published>2008-02-06T07:57:00.000Z</published><updated>2008-02-06T08:26:00.240Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-06T08:26:00.240Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><title>Andrzej's Rails tips #3</title><content type="html">&lt;span style="font-weight: bold;"&gt;Multi-model forms with attribute_fu&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I've used attribute_fu in two of my projects now. In one of them I use it in three different places. It simplifies your code a lot, when you want to edit many models in one form. Strongly recommended!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jamesgolick.com/attribute_fu"&gt;attribute_fu&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Gradual switch to resource_controller&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jamesgolick.com/resource_controller"&gt;resource_controller&lt;/a&gt; is a plugin that allows your controller's code to ... disappear :-)&lt;br /&gt;A great thing with this plugin is that you can introduce it gradually. Just install it, and make one controller a resource controller. See how it goes, and then decide whether it was worth it. I did it like that, and now I'm switching to it wherever I can.&lt;br /&gt;&lt;br /&gt;BTW, both attribute_fu and resource_controller are made by &lt;a href="http://jamesgolick.com/"&gt;&lt;span style="font-weight: bold;"&gt;James Golick&lt;/span&gt;&lt;/a&gt;. Thanks, James!&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;restful_authentication with RSpec&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I didn't know it before, but &lt;a href="http://bartoszblimke.blogspot.com/2008/02/rspec-support-in-restful-authentication.html"&gt;Bartosz Blimke discovered&lt;/a&gt; that, when you have a spec directory in your Rails app and you generate some resources using restful_authentication, then it will generate Rspec specs instead of unit tests. Cool! I'm not a big fan of code generation/scaffolding, but when they also provide good specs then it easies the pain.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-5464256457914225321?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/5464256457914225321/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=5464256457914225321" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5464256457914225321?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5464256457914225321?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/andrzejs-rails-tips-3.html" title="Andrzej's Rails tips #3" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DUQDQX05fip7ImA9WxZREkk.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-5183815794142719221</id><published>2008-02-05T22:15:00.000Z</published><updated>2008-02-05T21:36:10.326Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-05T21:36:10.326Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="tdd" /><title>Remote pair programming</title><content type="html">This article describes my experience with remote pair programming, why I chose remote pair programming for one of the projects, how it works and what tools we use.&lt;br /&gt;&lt;br /&gt;One of the projects I'm working on is a Rails application. We work on that together with my colleague, &lt;a href="http://marcinfo.blogspot.com/"&gt;Marcin&lt;/a&gt;. There's nothing really innovative in this application. I've got enough experience with Rails and IT in general to know that it can be finished in a given time. The interesting bit is, that &lt;span style="font-weight: bold;"&gt;both me an Marcin live in different cities&lt;/span&gt;. Even better, both of us travel a lot and very often we are in different countries.&lt;br /&gt;&lt;br /&gt;A distributed team is nothing new in today's IT world. One of the most common approaches to this problem is to divide the system and responsibilities across different people/teams/locations. It can work, I was involved in many such projects. The problem is that it requires a lot of trust, patience and really good skills to know how to split the project. In my opinion, it's a very risky approach, and it's easy to do it wrong. Chad Fowler describes it perfectly in his book: "My job went to India".&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Traditional pair programming is a great practice&lt;/span&gt;. I know it's still controversial, and I agree that it doesn't have to work for all software developers and all projects, but I've seen it successful often enough to know that there are many benefits of pair programming. The benefits that I find important are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Focus on the task&lt;/li&gt;&lt;li&gt;Sharing knowledge&lt;/li&gt;&lt;li&gt;Increasing trust&lt;/li&gt;&lt;li&gt;Fun&lt;/li&gt;&lt;li&gt;Speed of development&lt;/li&gt;&lt;/ul&gt;In our case, I think all of the factors are important. As always, time is a constraint, so it's important to focus on the most important tasks first. We have different skills - while I'm a little bit more experienced with Rails and TDD, Marcin is my guru when it comes to html/css. He worked on many different IT projects, so his general experience and intuition is very important for me. It means, we both gain a lot from this kind of cooperation. &lt;span style="font-weight: bold;"&gt;Fun of development is also an important factor here :-)&lt;/span&gt; From the remote pair programming sessions we've had so far I can say that I'm also very glad of our efficiency.&lt;br /&gt;&lt;br /&gt;The tools we use are nothing new. &lt;span style="font-weight: bold;"&gt;We use vim + screen + skype&lt;/span&gt;. We both login to a development server as the same user. There is a screen session already open, so we just 'screen -x' to it, and now we see the same things at the same time. It's fun to watch, when Marcin is typing something, explaining it using skype, and I see it immediately in my vim. At any point, I can grab the keyboard and just start typing. Cool! I can't imagine working like that without a skype session, though. Using IM is just too slow.&lt;br /&gt;&lt;br /&gt;I'm strongly recommending this kind of cooperation. I'm aware of the limitations here. The application must be possible to build on a Linux/Unix machine, but even if it can't, I can imagine some work-arounds to this problem. Different timezones might be an issue here. I don't think the whole project must be built like that. However, planning some remote pair programming sessions may increase the quality of your software.&lt;br /&gt;&lt;br /&gt;I like this way of working so much, that I even consider doing it more often. &lt;span style="font-weight: bold;"&gt;Would anyone be interested in a Rails coaching session like that?&lt;/span&gt; All you need is skype and an SSH client.&lt;br /&gt;&lt;br /&gt;The articles I found useful when preparing to remote pair programming:&lt;br /&gt;&lt;a href="http://blog.lathi.net/articles/2007/10/09/remote-pair-programming"&gt;&lt;br /&gt;http://blog.lathi.net/articles/2007/10/09/remote-pair-programming&lt;/a&gt;&lt;br /&gt;&lt;a href="http://mikeburnscoder.wordpress.com/2007/06/21/my-rails-development-environment-version-1-vim-and-screen/"&gt;http://mikeburnscoder.wordpress.com/2007/06/21/my-rails-development-environment-version-1-vim-and-screen/&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-5183815794142719221?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/5183815794142719221/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=5183815794142719221" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5183815794142719221?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/5183815794142719221?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/remote-pair-programming.html" title="Remote pair programming" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total></entry><entry gd:etag="W/&quot;DkMMRXg-eip7ImA9WxZREkk.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-2775901552641705502</id><published>2008-02-05T09:12:00.000Z</published><updated>2008-02-05T20:48:04.652Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-05T20:48:04.652Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><title>Andrzej's Rails tips #2</title><content type="html">&lt;span style="font-weight: bold;font-size:130%;" &gt;Changing month names in date_select&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Sometimes you may need to change the month names that appear in the date_select (or anywhere in your application). One way of doing it is the following (setting Polish months names):&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;class Date&lt;br /&gt;  MONTHNAMES = %w{Styczeń Luty Marzec Kwiecień &lt;br /&gt;                  Maj Czerwiec Lipiec Sierpień &lt;br /&gt;                  Wrzesień Październik Listopad Grudzień}&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;RSpec Stories, steps with parameters&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There is an easy way of reusing steps in RSpec stories.&lt;br /&gt;Let's say you had a following step:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Given a search for chess&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;which was implemented with a hardcoded value like that:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Given "a search for chess" do&lt;br /&gt;  @search_term = 'chess'&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And now you'd like to add another, very similar scenario:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Given a search for bridge&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can reuse the previously implemented step by changing the implementation to:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Given "a search for $term" do |term|&lt;br /&gt;  @search_term = term&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Thanks to that, you can now reuse this step in as many scenarios as you want.&lt;br /&gt;&lt;br /&gt;You can find a nice explanation of RSpec stories on &lt;a href="http://times.usefulinc.com/2008/01/12-rspec-stories"&gt;Edd Dumbill's blog&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-2775901552641705502?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/2775901552641705502/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=2775901552641705502" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2775901552641705502?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2775901552641705502?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/andrzejs-rails-tips-2.html" title="Andrzej's Rails tips #2" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DUENQXY4fyp7ImA9WxZREUs.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-2752487934493305015</id><published>2008-02-04T14:59:00.000Z</published><updated>2008-02-04T23:28:10.837Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-04T23:28:10.837Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><title>Andrzej's Rails tips #1</title><content type="html">&lt;span style="font-weight: bold;font-size:130%;" &gt;has_many :through, :uniq=&gt;true&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It's quite common that when you need to use has_many :through, you actually want to see unique objects. Today, I had a need to display a list of producers based on an order.&lt;br /&gt;&lt;br /&gt;In my design I have:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Order.has_many      :line_items&lt;br /&gt;LineItem.belongs_to :producer&lt;br /&gt;Order.has_many      :producers,&lt;br /&gt;                   :through =&gt; :line_items&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With this in place, if an order has two products, each of them belonging to the same producer, then order.producers lists the same producer twice.&lt;br /&gt;Adding &lt;span style="font-weight: bold;"&gt;:uniq =&gt; true&lt;/span&gt;  solves the problem:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Order.has_many :producers,&lt;br /&gt;              :through =&gt; :line_items,&lt;br /&gt;              :uniq =&gt; true&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;More information about it on &lt;a href="http://blog.hasmanythrough.com/2006/05/06/through-gets-uniq"&gt;John Susser's blog&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;RESTful routes and pdf&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If you have an action that responds to many formats (html, rss, pdf), you can still use a RESTful route with a &lt;span style="font-weight: bold;"&gt;'formatted'&lt;/span&gt; prefix:&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;link_to "PDF format",&lt;br /&gt;    formatted_order_path(@order, :pdf)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Found on &lt;a href="http://railscasts.com/episodes/78"&gt;Railscast #78&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-2752487934493305015?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/2752487934493305015/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=2752487934493305015" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2752487934493305015?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2752487934493305015?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/02/andrzejs-rails-tips-1.html" title="Andrzej's Rails tips #1" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DkMERnw9cSp7ImA9WxZSEEU.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-2366399431407336523</id><published>2008-01-21T10:46:00.000Z</published><updated>2008-01-23T10:33:27.269Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-23T10:33:27.269Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tdd" /><category scheme="http://www.blogger.com/atom/ns#" term="bdd" /><title>What do I gain from TDD or BDD?</title><content type="html">&lt;span style="font-size:100%;"&gt;The question what do I gain from doing TDD/BDD is asked quite often. I understand the reasons people ask for that. I have practised TDD since 2001. This post is an explanation of why I TDD/BDD and what I gain from it.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-size:180%;"&gt;Why do I BDD?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-size:100%;"&gt;BDD helps me with 3 aspects of software development: Design, Defense, Documentation.&lt;/span&gt;&lt;br /&gt;&lt;ul style="font-weight: bold;"&gt;&lt;li style="font-weight: bold;"&gt;&lt;span style="font-size:180%;"&gt;Design&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;When I write the specifications(tests) first, I see that &lt;span style="font-weight: bold;"&gt;my design is simpler&lt;/span&gt; (as compared to write the code first) for given requirements, &lt;span style="font-weight: bold;"&gt;thus better&lt;/span&gt;.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;I know it's not very intuitive, &lt;span style="font-weight: bold;"&gt;you just have to try it&lt;/span&gt;.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;When you start with tests, &lt;span style="font-weight: bold;"&gt;your design is more testable&lt;/span&gt;.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;It makes &lt;span style="font-weight: bold;"&gt;debugging easier&lt;/span&gt;.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;TDD doesn't replace drawing diagrams.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;It's just an additional design technique.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;"&gt;&lt;span style="font-size:100%;"&gt;I still enjoy drawing UML-like diagrams!&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-size:180%;"&gt;Defense&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;I feel much safer &lt;/span&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;having my application covered with tests, &lt;/span&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;Thanks to that, &lt;span style="font-weight: bold;"&gt;I can do some refactoring when I feel it's needed&lt;/span&gt;, run the tests, and if it's 'green' I just check it in.&lt;/span&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;Whenever some of my users find any bug, I prepare a test that covers it.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;Only then I fix the bug and release the change.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: normal;"&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;There's nothing worse than having a bug that reappears again&lt;/span&gt;&lt;span style="font-size:100%;"&gt; (after fixing it) in the application.&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-size:180%;"&gt;Documentation&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;I'm not a fan of big Word documents that describe all details of the requirements.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;It's just &lt;span style="font-weight: bold;"&gt;too difficult to synchronize this document with reality&lt;/span&gt; after every requirements change&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;Changes are inevitable.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;There was one large project I was working on, where nobody from the team (including the project manager) knew what all the requirements were!&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;Yes, it is pathological.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;&lt;span style="font-weight: bold;"&gt;I store all of the requirements as executable stories&lt;/span&gt;/tests.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;Before releasing any change I run all the stories.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: normal;font-size:100%;" &gt;When there is a requirement change I just find the story responsible for that, change it appropriately and change the code.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: normal;"&gt;&lt;span style="font-size:100%;"&gt;Changes are inevitable.&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-size:180%;"&gt;Do I have to TDD/BDD in order to be successful?&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;&lt;span style="font-size:100%;"&gt;Well... No.&lt;br /&gt;&lt;br /&gt;I know some very good developers that don't write any tests and are successful. There are other ways of ensuring good design or documentation. As for defense, you may have a very fast testing department that makes sure you didn't break anything. Or maybe you just prefer to manually go through your application. For me it's just too slow, &lt;/span&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;I prefer an immediate response that is free of any human mistakes&lt;/span&gt;&lt;span style="font-size:100%;"&gt;. Chances are, you are just so perfect that you don't introduce any breaking changes. Lucky you!&lt;br /&gt;&lt;br /&gt;It's also possible that you separate the analysis, design and implementation phases so strongly, that there is no way of changing anything during the implementation phase. I'm not a big fan of this approach but I know that many people are successful with that. I believe that even in this case you can gain a lot from writing acceptance tests (executable stories). Writing unit tests for your classes is also a good thing. I'll try to cover the topic of testing on different levels (acceptance, units) in a separate post.&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;But it's difficult and expensive, isn't it?&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;&lt;span style="font-size:100%;"&gt;Yes, it is. All I gain doesn't come for free.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-size:100%;"&gt;First of all, &lt;span style="font-weight: bold;"&gt;it requires discipline&lt;/span&gt;. It takes some time to get used to write tests first.&lt;br /&gt;&lt;/span&gt;&lt;span style="font-size:100%;"&gt;Also, &lt;span style="font-weight: bold;"&gt;it takes some time to learn how to do correct TDD/BDD&lt;/span&gt;. The good news is that you can start introducing it &lt;span style="font-weight: bold;"&gt;gradually&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-size:100%;"&gt;Another thing is that after creating a lot of tests for your applications the whole suite may take some time to run. It's a problem that is not easy to solve, but believe me you'd prefer this kind of problems than a 5-days debugging session.&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Is it fun?&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;Yes, it's a lot of fun!&lt;br /&gt;Again, you have to try it before you know, what I'm talking about. If you're still new to TDD, look around, maybe there is someone experienced nearby who can show you a quick demo.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;font-size:180%;" &gt;What next?&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div style="text-align: justify;"&gt;&lt;span style="font-size:100%;"&gt;Find some TDD tools for your technology. I believe they exist for every programming language. Find a place in your application where you'd like to start testing. Start with something simple. Maybe assuring that a request to a given page is successful? Or maybe asserting that all the values in the Order class are calculated correctly? Make sure you run those tests after every change of your application.&lt;br /&gt;&lt;br /&gt;Feel free to ask me questions about TDD or BDD. I have practised it with Java, C#, IronPython and Ruby. The technology doesn't matter too much in this case. You can use comments for that or simply email me at andrzejkrzywda at gmail.com.&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-2366399431407336523?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/2366399431407336523/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=2366399431407336523" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2366399431407336523?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/2366399431407336523?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/01/what-do-i-gain-from-tdd-or-bdd.html" title="What do I gain from TDD or BDD?" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">8</thr:total></entry><entry gd:etag="W/&quot;C0MHQno-eip7ImA9WxZTFkw.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-54773000238645071</id><published>2008-01-17T16:07:00.000Z</published><updated>2008-01-17T23:10:33.452Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-17T23:10:33.452Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rspec" /><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="tdd" /><category scheme="http://www.blogger.com/atom/ns#" term="bdd" /><title>RSpec User Story example</title><content type="html">Today, just a quick example showing what kind of wonderful things you can do with the new RSpec and its support for executable User Stories, a feature I was dreaming about for years...&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Specification as a user story:&lt;/h2&gt;&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Story: Creating an order&lt;br /&gt;&lt;br /&gt;Scenario: admin user creates an order&lt;br /&gt; Given an admin user&lt;br /&gt; And some orders&lt;br /&gt; And some customers&lt;br /&gt;&lt;br /&gt; When he creates an order&lt;br /&gt;&lt;br /&gt; Then a new order is created&lt;br /&gt; And a new customer is created&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;&lt;b&gt;Implementation of the user story&lt;/b&gt;&lt;/h2&gt;&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Given "an admin user" do&lt;br /&gt; login_as_admin&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;Given "some orders" do&lt;br /&gt; @orders_count = Order.count&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;Given "some customers" do&lt;br /&gt; @customers_count = Customer.count&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;When "he creates an order" do&lt;br /&gt; post '/orders/create',&lt;br /&gt;      "order"=&gt;{"address_attributes"=&gt; {"name"=&gt;"Customer 1"}}&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;Then "a new order is created" do&lt;br /&gt; Order.count.should ==  @orders_count + 1&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;Then "a new customer is created" do&lt;br /&gt; Customer.count.should ==  @customers_count + 1&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;&lt;b&gt;The output when the user story is run:&lt;/b&gt;&lt;/h2&gt;&lt;br /&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;Running 1 scenarios&lt;br /&gt;&lt;br /&gt;Story: Creating an order&lt;br /&gt;&lt;br /&gt;Scenario: admin user creates an order&lt;br /&gt;&lt;br /&gt; Given an admin user&lt;br /&gt; And some orders&lt;br /&gt; And some customers&lt;br /&gt;&lt;br /&gt; When he creates an order&lt;br /&gt;&lt;br /&gt; Then a new order is created&lt;br /&gt; And a new customer is created&lt;br /&gt;&lt;br /&gt;1 scenarios: 1 succeeded, 0 failed, 0 pending&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-54773000238645071?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/54773000238645071/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=54773000238645071" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/54773000238645071?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/54773000238645071?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/01/rspec-user-story-example.html" title="RSpec User Story example" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">12</thr:total></entry><entry gd:etag="W/&quot;C0cAQXs8eSp7ImA9WxZTFUo.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-7584823721669998163</id><published>2008-01-15T21:39:00.000Z</published><updated>2008-01-17T11:57:20.571Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-17T11:57:20.571Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="resolver" /><category scheme="http://www.blogger.com/atom/ns#" term="spreadsheet" /><category scheme="http://www.blogger.com/atom/ns#" term="ironpython" /><title>5 reasons to try Resolver One</title><content type="html">&lt;span style="font-size:85%;"&gt;Disclaimer: I am a Resolver employee, have helped develop it and think it is extraordinarily cool.&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-size:130%;"&gt;1. It's an &lt;/span&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;amazing combination of a spreadsheet application and an IronPython IDE.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;a href="http://www.resolversystems.com/products/resolver-one.php"&gt;Resolver One&lt;/a&gt; shows a typical spreadsheet grid and a code box.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Whatever you do with the grid is reflected in a generated code.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;In the code box you can write some code which manipulates the workbook object, all the changes are then displayed in the grid.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;a href="http://www.resolversystems.com/news/?p=36"&gt;Resolver One is now 1.0&lt;/a&gt; !&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-size:130%;"&gt;2. You can &lt;/span&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;create &lt;/span&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;maintainable&lt;/span&gt;&lt;span style="font-size:130%;"&gt; &lt;/span&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;spreadsheets.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Thanks to the two-ways synchronization a spreadsheet is actually a Python program.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Thus, you can do everything that you can do with a program.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Create modules, classes, methods.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-size:130%;"&gt;3. It's probably the first time &lt;/span&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;you can actually unit test your spreadsheet logic&lt;/span&gt;&lt;span style="font-size:130%;"&gt; and furthermore do some refactoring on it!&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Once you split the logic into classes, &lt;a href="http://www.resolverhacks.net/testing_spreadsheets.html"&gt;you can easily test them&lt;/a&gt; using standard unit test tools.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-size:130%;"&gt;4. Because now &lt;/span&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;creating a spreadsheet is limited only by your imagination&lt;/span&gt;&lt;span style="font-size:130%;"&gt;.&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.resolversystems.com/documentation/index.php/Google_Spreadsheets"&gt;Would you like to import/export a google spreadsheet?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.resolversystems.com/documentation/index.php/Loading_data_from_a_database_with_Database_Worksheets"&gt;Maybe you'd prefer to load data from a database?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.resolversystems.com/documentation/index.php/Charting"&gt;You want to generate a chart?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;It's all limited only to &lt;a href="http://www.ironpython.info/index.php/Main_Page"&gt;IronPython capabilities&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style="font-size:130%;"&gt;5. It's easy to &lt;/span&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;import your existing spreadsheet&lt;/span&gt;&lt;span style="font-size:130%;"&gt;, script it with Python and export it back.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Are you convinced now?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Then go and &lt;a href="http://www.resolversystems.com/get-it/"&gt;grab a copy of Resolver One&lt;/a&gt;&lt;br /&gt;Oh, and did I mention it's free for non-commercial use?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;More resources:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://resolversystems.com/"&gt;resolversystems.com&lt;/a&gt;&lt;br /&gt;&lt;a href="http://resolverhacks.net/"&gt;resolverhacks.net&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-7584823721669998163?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/7584823721669998163/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=7584823721669998163" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/7584823721669998163?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/7584823721669998163?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/01/5-reasons-to-try-resolver-one.html" title="5 reasons to try Resolver One" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total></entry><entry gd:etag="W/&quot;DUYEQXozcCp7ImA9WxZTE0g.&quot;"><id>tag:blogger.com,1999:blog-7821856652257554779.post-659520503919821641</id><published>2008-01-04T11:00:00.000Z</published><updated>2008-01-15T00:31:40.488Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-15T00:31:40.488Z</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rails" /><category scheme="http://www.blogger.com/atom/ns#" term="nginx" /><category scheme="http://www.blogger.com/atom/ns#" term="mongrel" /><category scheme="http://www.blogger.com/atom/ns#" term="capistrano" /><title>A Guide to Deploying Rails Applications</title><content type="html">This article describes the way I deploy my Rails applications.&lt;br /&gt;&lt;br /&gt;&lt;font style="font-weight: bold;"&gt;Thanks to Ruby on Rails I can quickly help my customers.&lt;/font&gt; The speed of development with Rails and agile practices make my work really fast. The only time when I slow down a bit is when I create and deploy a new project. If I saved the time somehow I could spend this time solving business problems instead.&lt;br /&gt;I decided to start with a documentation of my deployment/development process. Then, I automated and simplified all I could and had time to. The result is a semi-automatic process which helps me with creating applications. This process is specialised for my needs and it uses the tools that I found useful. It doesn't have to be good for your needs. &lt;font style="font-weight: bold;"&gt;&lt;br /&gt;All the setup was focused on agility.&lt;/font&gt; I love to check-in frequently and be able to see the changes immediately on the production server. That's why I use tools like Capistrano or Vlad.&lt;br /&gt;&lt;br /&gt;&lt;font style="font-weight: bold;"&gt;A quick description of the tools I use:&lt;/font&gt;&lt;br /&gt;&lt;ul&gt;&lt;li style="font-weight: bold;"&gt;Rails&lt;/li&gt;&lt;ul&gt;&lt;li&gt;I don't think I have to explain why :)&lt;/li&gt;&lt;li&gt;most of the time I use edge Rails&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Mongrel&lt;/li&gt;&lt;ul&gt;&lt;li&gt;A standard way of deploying in the Rails world.&lt;/li&gt;&lt;li&gt;fast and stable&lt;/li&gt;&lt;li&gt;Easy to configure&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;nginx&lt;/li&gt;&lt;ul&gt;&lt;li&gt;an http server that is responsible for load balancing&lt;/li&gt;&lt;li&gt;easy to configure (even easier if you can read Russian blogs)&lt;/li&gt;&lt;li&gt;easy to handle many Rails applications.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Capistrano&lt;/li&gt;&lt;ul&gt;&lt;li&gt;A standard tool for easy deployment.&lt;/li&gt;&lt;li&gt;I use Vlad for some of my applications, both are good.&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Piston&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Easy plugins management&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Subversion&lt;/li&gt;&lt;li style="font-weight: bold;"&gt;mysql&lt;/li&gt;&lt;/ul&gt;Nginx configuration may be tricky when you work on a shared hosting but the main points stay the same also with Apache and other Http servers. Some of my applications are still on a shared machine (&lt;a href="http://www.webfaction.com/?affiliate=andrzej"&gt;I recommend Webfaction&lt;/a&gt;), but most of them are on a dedicated machine.&lt;br /&gt;&lt;br /&gt;Let's assume that you want to create and deploy a new Rails applications. The whole process can be described in 5 general steps.&lt;br /&gt;&lt;ol&gt;&lt;li style="font-weight: bold;"&gt;Create an SVN repository, create the app and import it to the repository.&lt;/li&gt;&lt;li style="font-weight: bold;"&gt;Use capistrano to make the remote work easier.&lt;/li&gt;&lt;li style="font-weight: bold;"&gt;Create the production database.&lt;/li&gt;&lt;li style="font-weight: bold;"&gt;Prepare mongrel_cluster to work with the new application.&lt;/li&gt;&lt;li&gt;&lt;font style="font-weight: bold;"&gt;Configure nginx and bind the domain.&lt;/font&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Ok, let's go into details.&lt;br /&gt;&lt;ul&gt;&lt;li style="font-weight: bold;"&gt;Remote: Create a repository for the application.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;svnadmin create /var/repos/app&lt;br /&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;I prefer to have a separate repo for each of my applications.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;It's good to have one directory for all repositories&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;I use svn+ssh protocol, in my case it's easier to have just one source of users (Linux users)&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li style="font-weight: bold;"&gt;Checkout the application to the /tmp/app directory&lt;/li&gt;&lt;ul&gt;&lt;li&gt;svn co $REMOTE_SVN/app /tmp/app&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;ul&gt;&lt;li&gt;I recommend having environment variables for the svn paths, works nicely with shell autocompletion.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Use edge rails to create a rails app.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;svn up ~/tmp/rails_edge&lt;/li&gt;&lt;ul&gt;&lt;li&gt;update it first&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;my rails_edge lives there, you can always create it with 'svn co $RAILS_DEV_TRUNK'&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;ruby ~/tmp/rails_edge/railities/bin/rails -dmysql /tmp/app/trunk&lt;/li&gt;&lt;ul&gt;&lt;li&gt;'-dmysql' because Rails has recently changed its default db to sqlite&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;svn ci -m "initial import"&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Live on Rails edge&lt;/li&gt;&lt;ul&gt;&lt;li&gt;piston import $RAILS_DEV_TRUNK vendor/rails&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;svn ci -m "piston imported edge rails"&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;capify&lt;/li&gt;&lt;ul&gt;&lt;li style="font-weight: bold;"&gt;&lt;font style="font-weight: normal;"&gt;capify .&lt;/font&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;It creates two files.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;modify config/deploy.rb:&lt;/li&gt;&lt;/ul&gt;&lt;pre name="code" class="ruby"&gt;&lt;br /&gt;set :application, "app"&lt;br /&gt;my_server = "12.34.56.7"&lt;br /&gt;set :repository, "svn+ssh://#{my_server}/var/repos/app/trunk"&lt;br /&gt;set :deploy_to, "/var/www/#{application}"&lt;br /&gt;role :app, my_server&lt;br /&gt;role :web, my_server&lt;br /&gt;role :db,  my_server, :primary =&gt; true&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;ul&gt;&lt;li style="font-weight: bold;"&gt;Commit.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;svn ci -m "capistrano setup"&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;cap deploy:setup&lt;/li&gt;&lt;ul&gt;&lt;li&gt;it creates all the directories on the remote server&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;cap deploy:cold&lt;/li&gt;&lt;ul&gt;&lt;li&gt;it checkouts the code, creates a 'current' symlink&lt;br /&gt;&lt;/li&gt;&lt;li&gt;logs are in the 'shared' directory&lt;/li&gt;&lt;li&gt;it fails on db:migrate task which is fine for now.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;font style="font-weight: bold;"&gt;Create the production database&lt;/font&gt;&lt;br /&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Remote: Go to /var/www/app/current&lt;/li&gt;&lt;li&gt;Remote: rake RAILS_ENV=production db:create&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Local: cap deploy:cold&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Again.&lt;/li&gt;&lt;li&gt;It should pass the db:migrate now&lt;/li&gt;&lt;li&gt;But it fails on missing script/spin file&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Create script/spin with the following content:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;mongrel_cluster_ctl restart&lt;/li&gt;&lt;li&gt;Yes, you need mongrel_cluster on the remote server&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;Add this file to svn and commit&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;cap deploy:cold&lt;/li&gt;&lt;ul&gt;&lt;li&gt;This time it worked!&lt;/li&gt;&lt;li&gt;But... we didn't setup mongrel_cluster to restart our app.&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Mongrel_cluster&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Create config/mongrel_cluster.yml&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;pre&gt;---&lt;br /&gt;cwd: /var/www/app/current&lt;br /&gt;log_file: log/mongrel.log&lt;br /&gt;port: 5000&lt;br /&gt;environment: production&lt;br /&gt;group: www-data&lt;br /&gt;user: your-username&lt;br /&gt;address: 127.0.0.1&lt;br /&gt;pid_file: tmp/pids/mongrel.pid&lt;br /&gt;servers: 3&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li style="font-weight: bold;"&gt;Commit this file&lt;/li&gt;&lt;li style="font-weight: bold;"&gt;cap deploy:cold&lt;/li&gt;&lt;ul&gt;&lt;li&gt;still doesn't work&lt;/li&gt;&lt;li&gt;Mongrel_cluster doesn't know that it should use this file&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Remote: Go to /etc/mongrel_cluster/&lt;/li&gt;&lt;li style="font-weight: bold;"&gt;Make a symbolic link to the yml file&lt;/li&gt;&lt;ul&gt;&lt;li style="font-weight: bold;"&gt;sudo ln -s /var/www/app/current/config/mongrel_cluster.yml app.yml&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;cap deploy:cold&lt;/li&gt;&lt;ul&gt;&lt;li&gt;And it works!&lt;/li&gt;&lt;li&gt;you should now be able to connect to localhost:5000 from the remote server.&lt;/li&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Now we will bind the domain to this mongrel_cluster.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;We need to configure nginx.&lt;/li&gt;&lt;li&gt;I have setup nginx so that for every application I create a single file that is automatically loaded on nginx startup.&lt;/li&gt;&lt;li&gt;The file just needs to live in the /etc/nginx/vhosts/ directory.&lt;/li&gt;&lt;li&gt;It works because I have the following line in my /etc/nginx/nginx.conf file:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;include /etc/nginx/vhosts/*.conf;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li style="font-weight: bold;"&gt;Create /etc/nginx/vhosts/app.conf&lt;/li&gt;&lt;/ul&gt;&lt;pre class="code"&gt;&lt;br /&gt;upstream app {&lt;br /&gt;server 127.0.0.1:5000;&lt;br /&gt;server 127.0.0.1:5001;&lt;br /&gt;server 127.0.0.1:5002;&lt;br /&gt;}&lt;br /&gt;server {&lt;br /&gt;listen 80;&lt;br /&gt;client_max_body_size 50M;&lt;br /&gt;server_name app.com www.app.com;&lt;br /&gt;root /var/www/app/current/public;&lt;br /&gt;access_log  /var/www/app/current/log/nginx.log;&lt;br /&gt;&lt;br /&gt;if (-f $document_root/system/maintenance.html) {&lt;br /&gt; rewrite  ^(.*)$  /system/maintenance.html last;&lt;br /&gt; break;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;location / {&lt;br /&gt; proxy_set_header  X-Real-IP  $remote_addr;&lt;br /&gt; proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;&lt;br /&gt; proxy_set_header Host $http_host;&lt;br /&gt; proxy_redirect false;&lt;br /&gt; proxy_max_temp_file_size 0;&lt;br /&gt; if (-f $request_filename) {&lt;br /&gt;   break;&lt;br /&gt; }&lt;br /&gt; if (-f $request_filename/index.html) {&lt;br /&gt;   rewrite (.*) $1/index.html break;&lt;br /&gt; }&lt;br /&gt; if (-f $request_filename.html) {&lt;br /&gt;   rewrite (.*) $1.html break;&lt;br /&gt; }&lt;br /&gt; if (!-f $request_filename) {&lt;br /&gt;   proxy_pass http://app;&lt;br /&gt;   break;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;error_page   500 502 503 504  /500.html;&lt;br /&gt;location = /500.html {&lt;br /&gt; root /var/www/app/current/public;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li style="font-weight: bold;"&gt;Restart nginx&lt;/li&gt;&lt;ul&gt;&lt;li&gt;sudo /etc/init.d/nginx stop&lt;/li&gt;&lt;li&gt;sudo /etc/init.d/nginx start&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;font style="font-weight: bold;"&gt;That's it :)&lt;/font&gt;&lt;/li&gt;&lt;li&gt;&lt;font style="font-weight: bold;"&gt;From now on you just write the code, commit and call capistrano to deploy.&lt;/font&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://andrzejonsoftware.blogspot.com/2007/05/15-tdd-steps-to-create-rails.html"&gt;Don't forget about tests!&lt;/a&gt;&lt;font style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/font&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;br /&gt;I hope you found this guide useful. There is still a lot to improve and a lot of duplicates (like defining mongrel ports). Let me know if you think that I could do something in a better way.&lt;br /&gt;&lt;br /&gt;Here is a list of articles that I found useful when I was experimenting with Rails deployment.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://railsjitsu.com/installing-and-configuring-nginx-and-mongrel-for-rails"&gt;Installing and Configuring Nginx and Mongrel for Rails&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.urbanpuddle.com/articles/2007/11/16/install-ruby-rails-on-gutsy-gibbon-nginx-version"&gt;&lt;br /&gt;Install Ruby Rails on Gutsy Gibbon (Nginx Version)&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.gregbenedict.com/2007/07/06/hosting-rails-apps-using-nginx-and-mongrel-cluster"&gt;Hosting Rails apps using nginx and Mongrel Cluster&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://toolmantim.com/article/2006/12/7/a_clean_slate_edge_rails_recipe"&gt;A clean slate, Edge Rails recipe&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7821856652257554779-659520503919821641?l=andrzejonsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://andrzejonsoftware.blogspot.com/feeds/659520503919821641/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=7821856652257554779&amp;postID=659520503919821641" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/659520503919821641?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7821856652257554779/posts/default/659520503919821641?v=2" /><link rel="alternate" type="text/html" href="http://andrzejonsoftware.blogspot.com/2008/01/guide-to-deploying-rails-applications.html" title="A Guide to Deploying Rails Applications" /><author><name>Andrzej Krzywda</name><uri>http://www.blogger.com/profile/06399276063142826365</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11149664245996257280" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total></entry></feed>
