Cucumber - obsługa kilku sesji

Wiekszość standardowych zadań związanych z testowaniem aplikacji jest w prosty sposób do zrealizowania z użyciem domyślnych kroków Cucumbera. Z założenia Cucumber służy do testów funkcjonalnych, lecz można go również namówić do realizacji "testów" integracyjnych. Chodzi mi o taką sytuację kiedy chcemy prztestować w jednym scenariuszu interakcje pomiędzy działaniami kilku użytkowników, szczególnie gdy z jakiś powodów nie możemy używać ponownego wylogowania i zalogowania gdyż wpływa ono w jakiś sposób na stan aplikacji. Na ten problem zwrócił mi uwagę mój kolega Michał Papis

W przypadku standardowych wbudowanych mechanizmów testowania możemy skorzystać z bloku open_session:

def login(user)
  open_session do |sess|
    sess.extend(CustomDsl)
    u = users(user)
    sess.https!
    sess.post "/login", :username => u.username, :password => u.password
    assert_equal '/welcome', path
    sess.https!(false)
  end
end

Lecz w przypadku cucumbera który opiera się o poszczególne definicje kroków konieczne jest znalezienie rozwiązania pasującego do formy w jakiej tworzone są scenariusze.

W tym celu przygotowałem taki oto plik kroków (mizzeria_steps.rb):

module ActionController
  module Integration
    class Session
      def switch_session_by_name(name)
        if @sessions_by_name.nil? 
          @sessions_by_name = { :default => @response.session.clone }
        end
        @sessions_by_name[name.to_sym] ||= @sessions_by_name[:default].clone
        @response.session = @sessions_by_name[name.to_sym]
      end
    end
  end
end

Given /^session name is "([^\"]*)"$/ do |name|
  switch_session_by_name(name)
end 

Użycie tego mechanizmu (multiple session) jest trywialnie proste, wykonujemy następujący krok:

Given session name is "new user"

W tym przypadku tworzona jest nazwana sesja która jest nie zależna od innych (również od domyślnej). Dostęp do domyślnej nazwanej sesji odbywa się poprzez użycie nazwy: default

Given session name is "default"

Jak to mówią małe proste i funkcjonalne rozwiązanie, a cieszy :)

Lektura obowiązkowa:
http://guides.rubyonrails.org/testing.html#integration-testing-examples

Rails 3 od zera ... (część 1.)

Framework Ruby on Rails doczekał się w końcu wersji 3, czas więc na pierwszą "szpachle" ;)

Pierwszą niezbędną wg mnie rzeczą jaką będziemy potrzebować jest RVM (Ruby Version Manager), który znacznie ułatwia zarówno eksperymenty, testowanie jak codzienną pracę. RVM pozwala na instalacje kilku wersji interpretera Ruby oraz sprawne i szybkie przełaczanie między nimi. Największą jego zaletą jest również fakt instalacji interpreterów w katalogu użytkownika, eliminując konieczność posiadania uprawnień do instalacji oprogramowania i używania polecenia sudo

Proponuje zainstalować go bezpośrednio ze źródeł, odcinając tym samym pępowine od systemowej instalacji języka Ruby:

Last login: Fri Feb 26 18:29:46 on ttys004
mkdir -p ~/.rvm/src/ && cd ~/.rvm/src && rm -rf ./rvm/ && git clone git://github.com/wayneeseguin/rvm.git && cd rvm && ./install

Następnie dodajemy niezbędno linijkę do pliku ~/.profile:

if [[ -s /Users/andrzejsliwa/.rvm/scripts/rvm ]] ; then source /Users/andrzejsliwa/.rvm/scripts/rvm ; fi     

W tym momencie musimy uruchomić nową sesję terminala tak by dodane przez nas zmiany odniosły skutek. Poprawną instalację powinnień potwierdzić taki oto rezultat polecenia rvm list:

mac:~ andrzejsliwa$ rvm list

rvm Rubies

System Ruby

   system [ x86_64 i386 ppc ]

mac:~ andrzejsliwa$ rvm install 1.9.1

Installing Ruby from source to: /Users/andrzejsliwa/.rvm/rubies/ruby-1.9.1-p378
Downloading ruby-1.9.1-p378, this may take a while depending on your connection...

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 8862k  100 8862k    0     0   100k      0  0:01:28  0:01:28 --:--:--  121k

Extracting ruby-1.9.1-p378 ...
Configuring ruby-1.9.1-p378, this may take a while depending on your cpu(s)...
Compiling ruby-1.9.1-p378, this may take a while, depending on your cpu(s)...
Installing ruby-1.9.1-p378
Installation of ruby-1.9.1-p378 is complete.
Updating rubygems for ruby-1.9.1-p378
Installing gems for ruby-1.9.1-p378.
Installing rake
Installation of gems for ruby-1.9.1-p378 is complete.

Następnie instalujemy interpreter Rubego 1.9.1

mac:~ andrzejsliwa$ rvm 1.9.1 --default

Poprawność instalacji możemy zweryfikować w następujący sposób:

mac:~ andrzejsliwa$ which ruby
/Users/andrzejsliwa/.rvm/rubies/ruby-1.9.1-p378/bin/ruby

mac:~ andrzejsliwa$ which gem
/Users/andrzejsliwa/.rvm/rubies/ruby-1.9.1-p378/bin/gem

mac:~ andrzejsliwa$ ruby -v 
ruby 1.9.1p378 (2010-01-10 revision 26273) [i386-darwin10.2.0]

mac:~ andrzejsliwa$ gem -v
1.3.6

Chciałbym wyraźnie w tym miejscu przypomnieć że począwszy od wersji 1.3.6 RubyGems, domyślnym źródłem gemów jest strona http://rubygems.org/ (dawny gemcutter.org)

Jedną z ciekawych funkcji RVM jest możliwość definiowania różnych nazwanych zbiorów gemów które można przełączać w ramach jednego interpretera. (domyślnym zbiorem gemów jest %global)

Tworzymy zbiór gemów odpowiedzialny za testowanie Rails w wersji 3.0 pre:

mac:~ andrzejsliwa$ rvm gemset create rails3pre
Gemset 'rails3pre' created.

Następnie wybieramy go jako bierzący:

mac:~ andrzejsliwa$ rvm 1.9.1%rails3pre

mac:~ andrzejsliwa$ rvm list

rvm Rubies

=> ruby-1.9.1-p378 [ x86_64 ]

Default Ruby (for new shells)

   ruby-1.9.1-p378 [ x86_64 ]

System Ruby

   system [ x86_64 i386 ppc ]

Następnie instalujemy Ruby on Rails

mac:~ andrzejsliwa$ gem install rails --pre

Due to a rubygems bug, you must uninstall older versions of the bundler gem for 0.9 to work.
If you still need bundler 0.8, install the 'bundler08' gem.
Successfully installed i18n-0.3.5
Successfully installed tzinfo-0.3.16
Successfully installed builder-2.1.2
Successfully installed memcache-client-1.7.8
Successfully installed activesupport-3.0.0.beta
Successfully installed activemodel-3.0.0.beta
Successfully installed rack-1.1.0
Successfully installed rack-test-0.5.3
Successfully installed rack-mount-0.4.7
Successfully installed abstract-1.0.0
Successfully installed erubis-2.6.5
Successfully installed actionpack-3.0.0.beta
Successfully installed arel-0.2.1
Successfully installed activerecord-3.0.0.beta
Successfully installed activeresource-3.0.0.beta
Successfully installed mime-types-1.16
Successfully installed mail-2.1.3
Successfully installed text-hyphen-1.0.0
Successfully installed text-format-1.0.0
Successfully installed actionmailer-3.0.0.beta
Successfully installed thor-0.13.4
Successfully installed railties-3.0.0.beta
Successfully installed bundler-0.9.9
Successfully installed rails-3.0.0.beta
24 gems installed
Installing ri documentation for i18n-0.3.5...
Installing ri documentation for tzinfo-0.3.16...
Installing ri documentation for builder-2.1.2...
Installing ri documentation for memcache-client-1.7.8...
Installing ri documentation for activesupport-3.0.0.beta...
Installing ri documentation for activemodel-3.0.0.beta...
Installing ri documentation for rack-1.1.0...
Installing ri documentation for rack-test-0.5.3...
Installing ri documentation for rack-mount-0.4.7...
Installing ri documentation for abstract-1.0.0...
Installing ri documentation for erubis-2.6.5...
Installing ri documentation for actionpack-3.0.0.beta...
Installing ri documentation for arel-0.2.1...
Installing ri documentation for activerecord-3.0.0.beta...
Installing ri documentation for activeresource-3.0.0.beta...
Installing ri documentation for mime-types-1.16...
Installing ri documentation for mail-2.1.3...
Installing ri documentation for text-hyphen-1.0.0...
Installing ri documentation for text-format-1.0.0...
Installing ri documentation for actionmailer-3.0.0.beta...
Installing ri documentation for thor-0.13.4...
Installing ri documentation for railties-3.0.0.beta...
Installing ri documentation for bundler-0.9.9...
Installing ri documentation for rails-3.0.0.beta...
Installing RDoc documentation for i18n-0.3.5...
Installing RDoc documentation for tzinfo-0.3.16...
Installing RDoc documentation for builder-2.1.2...
Installing RDoc documentation for memcache-client-1.7.8...
Installing RDoc documentation for activesupport-3.0.0.beta...
Installing RDoc documentation for activemodel-3.0.0.beta...
Installing RDoc documentation for rack-1.1.0...
Installing RDoc documentation for rack-test-0.5.3...
Installing RDoc documentation for rack-mount-0.4.7...
Installing RDoc documentation for abstract-1.0.0...
Installing RDoc documentation for erubis-2.6.5...
Installing RDoc documentation for actionpack-3.0.0.beta...
Installing RDoc documentation for arel-0.2.1...
Installing RDoc documentation for activerecord-3.0.0.beta...
Installing RDoc documentation for activeresource-3.0.0.beta...
Installing RDoc documentation for mime-types-1.16...
Installing RDoc documentation for mail-2.1.3...
Installing RDoc documentation for text-hyphen-1.0.0...
Installing RDoc documentation for text-format-1.0.0...
Installing RDoc documentation for actionmailer-3.0.0.beta...
Installing RDoc documentation for thor-0.13.4...
Installing RDoc documentation for railties-3.0.0.beta...
Installing RDoc documentation for bundler-0.9.9...
Installing RDoc documentation for rails-3.0.0.beta...

Aby upewnić się że pracujemy na naszym zbiorze gemów przełączamy się na domyślny i weryfikujemy że nie ma zainstalowanych gemów związanych z rails 3

mac:~ andrzejsliwa$ rvm 1.9.1
mac:~ andrzejsliwa$ gem list

*** LOCAL GEMS ***

rake (0.8.7)
rubygems-update (1.3.6)

Następnie wracamy ponownie do naszego zbioru rails3pre:

mac:~ andrzejsliwa$ rvm 1.9.1%rails3pre
mac:~ andrzejsliwa$ gem list

*** LOCAL GEMS ***

abstract (1.0.0)
actionmailer (3.0.0.beta)
actionpack (3.0.0.beta)
activemodel (3.0.0.beta)
activerecord (3.0.0.beta)
activeresource (3.0.0.beta)
activesupport (3.0.0.beta)
arel (0.2.1)
builder (2.1.2)
bundler (0.9.9)
erubis (2.6.5)
i18n (0.3.5)
mail (2.1.3)
memcache-client (1.7.8)
mime-types (1.16)
rack (1.1.0)
rack-mount (0.4.7)
rack-test (0.5.3)
rails (3.0.0.beta)
railties (3.0.0.beta)
rake (0.8.7)
rubygems-update (1.3.6)
text-format (1.0.0)
text-hyphen (1.0.0)
thor (0.13.4)
tzinfo (0.3.16)

Kolejnym krokiem jest stworzenie naszej przykładowej aplikacji

mac:work andrzejsliwa$ rails testapp -d postgresql
      create  
      create  README
      create  .gitignore
      create  Rakefile
      create  config.ru
      create  Gemfile
      create  app
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/models
      create  app/views/layouts
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/cookie_verification_secret.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/session_store.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  doc
      create  doc/README_FOR_APP
      create  lib
      create  lib/tasks
      create  lib/tasks/.gitkeep
      create  log
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/favicon.ico
      create  public/index.html
      create  public/robots.txt
      create  public/images
      create  public/images/rails.png
      create  public/stylesheets
      create  public/stylesheets/.gitkeep
      create  public/javascripts
      create  public/javascripts/application.js
      create  public/javascripts/controls.js
      create  public/javascripts/dragdrop.js
      create  public/javascripts/effects.js
      create  public/javascripts/prototype.js
      create  public/javascripts/rails.js
      create  script
      create  script/rails
      create  test
      create  test/performance/browsing_test.rb
      create  test/test_helper.rb
      create  test/fixtures
      create  test/functional
      create  test/integration
      create  test/unit
      create  tmp
      create  tmp/sessions
      create  tmp/sockets
      create  tmp/cache
      create  tmp/pids
      create  vendor/plugins
      create  vendor/plugins/.gitkeep
  
mac:work andrzejsliwa$ cd testapp/

Instalujemy zależności za pomocą narzędzia bundler:

mac:testapp andrzejsliwa$ bundle -h
Tasks:
  bundle check        # Checks if the dependencies listed in Gemfile are satisfied by currently installed gems
  bundle exec         # Run the command in context of the bundle
  bundle help [TASK]  # Describe available tasks or one specific task
  bundle init         # Generates a Gemfile into the current working directory
  bundle install      # Install the current environment to the system
  bundle lock         # Locks the bundle to the current set of dependencies, including all child dependencies.
  bundle package      # Locks and then caches all of the gems into vendor/cache
  bundle show         # Shows all gems that are part of the bundle.
  bundle unlock       # Unlock the bundle. This allows gem versions to be changed
  bundle version      # Prints the bundler's version information

mac:testapp andrzejsliwa$ bundle install
Fetching source index from http://gemcutter.org/
Resolving dependencies
Installing abstract (1.0.0) from system gems 
Installing actionmailer (3.0.0.beta) from system gems 
Installing actionpack (3.0.0.beta) from system gems 
Installing activemodel (3.0.0.beta) from system gems 
Installing activerecord (3.0.0.beta) from system gems 
Installing activeresource (3.0.0.beta) from system gems 
Installing activesupport (3.0.0.beta) from system gems 
Installing arel (0.2.1) from system gems 
Installing builder (2.1.2) from system gems 
Installing bundler (0.9.9) from system gems 
Installing erubis (2.6.5) from system gems 
Installing i18n (0.3.5) from system gems 
Installing mail (2.1.3) from system gems 
Installing memcache-client (1.7.8) from system gems 
Installing mime-types (1.16) from system gems 
Installing pg (0.8.0) from rubygems repository at http://gemcutter.org/ with native extensions 
Installing rack (1.1.0) from system gems 
Installing rack-mount (0.4.7) from system gems 
Installing rack-test (0.5.3) from system gems 
Installing rails (3.0.0.beta) from system gems 
Installing railties (3.0.0.beta) from system gems 
Installing rake (0.8.7) from system gems 
Installing text-format (1.0.0) from system gems 
Installing text-hyphen (1.0.0) from system gems 
Installing thor (0.13.4) from system gems 
Installing tzinfo (0.3.16) from system gems 
Your bundle is complete!

W tym momencie możemy zmodyfikować plik .gitignore taka aby wyglądał następująco:

.bundle                                                                                                                           
.DS_Store                                                                                                                         
db/*.sqlite3                                                                                                                      
log/*.log                                                                                                                         
tmp/**/*                                                                                                                          
config/database.yml

Następnie modyfikujemy plik config/database.yml

# PostgreSQL. Versions 7.4 and 8.x are supported.
#
# Install the ruby-postgres driver:
#   gem install ruby-postgres
# On Mac OS X:
#   gem install ruby-postgres -- --include=/usr/local/pgsql
# On Windows:
#   gem install ruby-postgres
#       Choose the win32 build.
#       Install PostgreSQL and put its /bin directory on your path.
base: &base
  adapter: postgresql
  encoding: unicode
  pool: 5
  username: postgres
  password:

development:
  database: testapp_development
  <<: *base

  # Connect on a TCP socket. Omitted by default since the client uses a
  # domain socket that doesn't need configuration. Windows does not have
  # domain sockets, so uncomment these lines.
  #host: localhost
  #port: 5432

  # Schema search path. The server defaults to $user,public
  #schema_search_path: myapp,sharedapp,public

  # Minimum log levels, in increasing order:
  #   debug5, debug4, debug3, debug2, debug1,
  #   log, notice, warning, error, fatal, and panic
  # The server defaults to notice.
  #min_messages: warning

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  database: testapp_test
  <<: *base

production:
  database: testapp_production
  <<: *base

Kopiujemy plik jako przykładowy:

mac:testapp andrzejsliwa$ cp config/database.yml config/database.yml.example

Możemy w tym momencie również się rozejrzeć po aplikacji i zadaniach rake

mac:testapp andrzejsliwa$ rake -T
(in /Users/andrzejsliwa/work/testapp)
rake about                                # Explain the current environment
rake db:abort_if_pending_migrations       # Raises an error if there are pending migrations
rake db:charset                           # Retrieves the charset for the current environment's database
rake db:collation                         # Retrieves the collation for the current environment's database
rake db:create                            # Create the database defined in config/database.yml for the current Rails.env - al...
rake db:create:all                        # Create all the local databases defined in config/database.yml
rake db:drop                              # Drops the database for the current Rails.env
rake db:drop:all                          # Drops all the local databases defined in config/database.yml
rake db:fixtures:identify                 # Search for a fixture given a LABEL or ID.
rake db:fixtures:load                     # Load fixtures into the current environment's database.
rake db:forward                           # Pushes the schema to the next version.
rake db:migrate                           # Migrate the database through scripts in db/migrate and update db/schema.rb by inv...
rake db:migrate:down                      # Runs the "down" for a given migration VERSION.
rake db:migrate:redo                      # Rollbacks the database one migration and re migrate up.
rake db:migrate:reset                     # Resets your database using your migrations for the current environment
rake db:migrate:up                        # Runs the "up" for a given migration VERSION.
rake db:reset                             # Drops and recreates the database from db/schema.rb for the current environment an...
rake db:rollback                          # Rolls the schema back to the previous version.
rake db:schema:dump                       # Create a db/schema.rb file that can be portably used against any DB supported by AR
rake db:schema:load                       # Load a schema.rb file into the database
rake db:seed                              # Load the seed data from db/seeds.rb
rake db:sessions:clear                    # Clear the sessions table
rake db:sessions:create                   # Creates a sessions migration for use with ActiveRecord::SessionStore
rake db:setup                             # Create the database, load the schema, and initialize with the seed data
rake db:structure:dump                    # Dump the database structure to a SQL file
rake db:test:clone                        # Recreate the test database from the current environment's database schema
rake db:test:clone_structure              # Recreate the test databases from the development structure
rake db:test:load                         # Recreate the test database from the current schema.rb
rake db:test:prepare                      # Check for pending migrations and load the test schema
rake db:test:purge                        # Empty the test database
rake db:version                           # Retrieves the current schema version number
rake doc:app                              # Build the RDOC HTML Files
rake doc:clobber_app                      # Remove rdoc products
rake doc:clobber_plugins                  # Remove plugin documentation
rake doc:clobber_rails                    # Remove rdoc products
rake doc:guides                           # Generate Rails guides
rake doc:plugins                          # Generate documentation for all installed plugins
rake doc:rails                            # Build the RDOC HTML Files
rake doc:reapp                            # Force a rebuild of the RDOC files
rake doc:rerails                          # Force a rebuild of the RDOC files
rake log:clear                            # Truncates all *.log files in log/ to zero bytes
rake middleware                           # Prints out your Rack middleware stack
rake notes                                # Enumerate all annotations
rake notes:custom                         # Enumerate a custom annotation, specify with ANNOTATION=CUSTOM
rake notes:fixme                          # Enumerate all FIXME annotations
rake notes:optimize                       # Enumerate all OPTIMIZE annotations
rake notes:todo                           # Enumerate all TODO annotations
rake rails:freeze:edge                    # The freeze:edge command has been deprecated, specify the path setting in your app...
rake rails:freeze:gems                    # The rails:freeze:gems is deprecated, please use bundle install instead
rake rails:template                       # Applies the template supplied by LOCATION=/path/to/template
rake rails:unfreeze                       # The unfreeze command has been deprecated, please use bundler commands instead
rake rails:update                         # Update both configs, scripts and public/javascripts from Rails
rake rails:update:application_controller  # Rename application.rb to application_controller.rb
rake rails:update:configs                 # Update config/boot.rb from your current rails install
rake rails:update:javascripts             # Update Prototype javascripts from your current rails install
rake rails:update:scripts                 # Adds new scripts to the application script/ directory
rake routes                               # Print out all defined routes in match order, with names.
rake secret                               # Generate a crytographically secure secret key.
rake stats                                # Report code statistics (KLOCs, etc) from the application
rake test                                 # Run all unit, functional and integration tests
rake test:benchmark                       # Run tests for {:benchmark=>"db:test:prepare"} / Benchmark the performance tests
rake test:functionals                     # Run tests for {:functionals=>"db:test:prepare"} / Run the functional tests in tes...
rake test:integration                     # Run tests for {:integration=>"db:test:prepare"} / Run the integration tests in te...
rake test:plugins                         # Run tests for {:plugins=>:environment} / Run the plugin tests in vendor/plugins/*...
rake test:profile                         # Run tests for {:profile=>"db:test:prepare"} / Profile the performance tests
rake test:recent                          # Run tests for {:recent=>"db:test:prepare"} / Test recent changes
rake test:uncommitted                     # Run tests for {:uncommitted=>"db:test:prepare"} / Test changes since last checkin...
rake test:units                           # Run tests for {:units=>"db:test:prepare"} / Run the unit tests in test/unit
rake time:zones:all                       # Displays names of all time zones recognized by the Rails TimeZone class, grouped ...
rake time:zones:local                     # Displays names of time zones recognized by the Rails TimeZone class with the same...
rake time:zones:us                        # Displays names of US time zones recognized by the Rails TimeZone class, grouped b...
rake tmp:cache:clear                      # Clears all files and directories in tmp/cache
rake tmp:clear                            # Clear session, cache, and socket files from tmp/
rake tmp:create                           # Creates tmp directories for sessions, cache, sockets, and pids
rake tmp:pids:clear                       # Clears all files in tmp/pids
rake tmp:sessions:clear                   # Clears all files in tmp/sessions
rake tmp:sockets:clear                    # Clears all files in tmp/sockets

W wersji 3.0 ulegl zmianie sposób wywoływania podstawowych poleceń, zastąpiono pojedyńcze skrypty poleceniem rails i odpowiednim parametrem (lub jego skrutem):

mac:testapp andrzejsliwa$ rails -h
Usage: rails COMMAND [ARGS]

The most common rails commands are:
 generate    Generate new code (short-cut alias: "g")
 console     Start the Rails console (short-cut alias: "c")
 server      Start the Rails server (short-cut alias: "s")
 dbconsole   Start a console for the database specified in config/database.yml
             (short-cut alias: "db")

In addition to those, there are:
 application  Generate the Rails application code
 destroy      Undo code generated with "generate"
 benchmarker  See how fast a piece of code runs
 profiler     Get profile information from a piece of code
 plugin       Install a plugin
 runner       Run a piece of code in the application environment

All commands can be run with -h for more information.

Kolejnym krokiem przed uruchomieniem naszej przykladowej aplikacji jest stworzenie niezbędnych baz oraz uruchomienie serwera:

mac:testapp andrzejsliwa$ rake db:create:all
(in /Users/andrzejsliwa/work/testapp)

mac:testapp andrzejsliwa$ rails s
=> Booting WEBrick
=> Rails 3.0.0.beta application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2010-02-26 19:37:50] INFO  WEBrick 1.3.1
[2010-02-26 19:37:50] INFO  ruby 1.9.1 (2010-01-10) [i386-darwin10.2.0]
[2010-02-26 19:37:50] INFO  WEBrick::HTTPServer#start: pid=44009 port=3000

Teraz możemy użyć generatorów które zostały w wersji 3.0 w całości przebudowane (tym samym stając się nie kompatybilne z poprzednią wersją). Między innymi wprowadzono w nich uchwyty pozwalające na wymienne stosowanie np. frameworków testowania, widoku itp:

mac:testapp andrzejsliwa$ r g 
Usage: rails generate GENERATOR [args] [options]

General options:
  -h, [--help]     # Print generators options and usage
  -p, [--pretend]  # Run but do not make any changes
  -f, [--force]    # Overwrite files that already exist
  -s, [--skip]     # Skip files that already exist
  -q, [--quiet]    # Supress status output

Please choose a generator below.

Rails:
  controller
  generator
  helper
  integration_test
  mailer
  metal
  migration
  model
  model_subclass
  observer
  performance_test
  plugin
  resource
  scaffold
  scaffold_controller
  session_migration
  stylesheets

ActiveRecord:
  active_record:migration
  active_record:model
  active_record:observer
  active_record:session_migration

Erb:
  erb:controller
  erb:mailer
  erb:scaffold

TestUnit:
  test_unit:controller
  test_unit:helper
  test_unit:integration
  test_unit:mailer
  test_unit:model
  test_unit:observer
  test_unit:performance
  test_unit:plugin
  test_unit:scaffold


mac-2:testapp andrzejsliwa$ r g scaffold Post title:string content:text
      invoke  active_record
      create    db/migrate/20100226184313_create_posts.rb
      create    app/models/post.rb
      invoke    test_unit
      create      test/unit/post_test.rb
      create      test/fixtures/posts.yml
       route  resources :posts
      invoke  scaffold_controller
      create    app/controllers/posts_controller.rb
      invoke    erb
      create      app/views/posts
      create      app/views/posts/index.html.erb
      create      app/views/posts/edit.html.erb
      create      app/views/posts/show.html.erb
      create      app/views/posts/new.html.erb
      create      app/views/posts/_form.html.erb
      create      app/views/layouts/posts.html.erb
      invoke    test_unit
      create      test/functional/posts_controller_test.rb
      invoke    helper
      create      app/helpers/posts_helper.rb
      invoke      test_unit
      create        test/unit/helpers/posts_helper_test.rb
      invoke  stylesheets
      create    public/stylesheets/scaffold.css

Na tym etapie usuwamy zbędne pliki:

mac:testapp andrzejsliwa$ rm public/index.html 
mac:testapp andrzejsliwa$ rm public/favicon.ico

Migrujemy bazę danych:

mac:testapp andrzejsliwa$ rake db:migrate
(in /Users/andrzejsliwa/work/testapp)
==  CreatePosts: migrating ====================================================
-- create_table(:posts)
NOTICE:  CREATE TABLE will create implicit sequence "posts_id_seq" for serial column "posts.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "posts_pkey" for table "posts"
   -> 0.0073s
==  CreatePosts: migrated (0.0074s) ===========================================

Oraz definiujemy routing:

Testapp::Application.routes.draw do |map|
  resources :posts
  # ...
  
  root :to => "posts#index"
end

Tak przygotowaną aplikacje, po przetestowaniu możemy dodać do repozytorium gita:

mac:testapp andrzejsliwa$ git init
Initialized empty Git repository in /Users/andrzejsliwa/work/testapp/.git/
mac:testapp andrzejsliwa$ git add .
mac:testapp andrzejsliwa$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#   new file:   .gitignore
#   new file:   Gemfile
#   new file:   README
#   new file:   Rakefile
#   new file:   app/controllers/application_controller.rb
#   new file:   app/controllers/posts_controller.rb
#   new file:   app/helpers/application_helper.rb
#   new file:   app/helpers/posts_helper.rb
#   new file:   app/models/post.rb
#   new file:   app/views/layouts/posts.html.erb
#   new file:   app/views/posts/_form.html.erb
#   new file:   app/views/posts/edit.html.erb
#   new file:   app/views/posts/index.html.erb
#   new file:   app/views/posts/new.html.erb
#   new file:   app/views/posts/show.html.erb
#   new file:   config.ru
#   new file:   config/application.rb
#   new file:   config/boot.rb
#   new file:   config/database.yml.example
#   new file:   config/environment.rb
#   new file:   config/environments/development.rb
#   new file:   config/environments/production.rb
#   new file:   config/environments/test.rb
#   new file:   config/initializers/backtrace_silencers.rb
#   new file:   config/initializers/cookie_verification_secret.rb
#   new file:   config/initializers/inflections.rb
#   new file:   config/initializers/mime_types.rb
#   new file:   config/initializers/session_store.rb
#   new file:   config/locales/en.yml
#   new file:   config/routes.rb
#   new file:   db/migrate/20100226184313_create_posts.rb
#   new file:   db/schema.rb
#   new file:   db/seeds.rb
#   new file:   doc/README_FOR_APP
#   new file:   lib/tasks/.gitkeep
#   new file:   public/404.html
#   new file:   public/422.html
#   new file:   public/500.html
#   new file:   public/images/rails.png
#   new file:   public/javascripts/application.js
#   new file:   public/javascripts/controls.js
#   new file:   public/javascripts/dragdrop.js
#   new file:   public/javascripts/effects.js
#   new file:   public/javascripts/prototype.js
#   new file:   public/javascripts/rails.js
#   new file:   public/robots.txt
#   new file:   public/stylesheets/.gitkeep
#   new file:   public/stylesheets/scaffold.css
#   new file:   script/rails
#   new file:   test/fixtures/posts.yml
#   new file:   test/functional/posts_controller_test.rb
#   new file:   test/performance/browsing_test.rb
#   new file:   test/test_helper.rb
#   new file:   test/unit/helpers/posts_helper_test.rb
#   new file:   test/unit/post_test.rb
#   new file:   vendor/plugins/.gitkeep
#

mac:testapp andrzejsliwa$ git commit -m "initial import"
[master (root-commit) 54399d1] initial import
 53 files changed, 8540 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 Gemfile
 create mode 100644 README
 create mode 100644 Rakefile
 create mode 100644 app/controllers/application_controller.rb
 create mode 100644 app/controllers/posts_controller.rb
 create mode 100644 app/helpers/application_helper.rb
 create mode 100644 app/helpers/posts_helper.rb
 create mode 100644 app/models/post.rb
 create mode 100644 app/views/layouts/posts.html.erb
 create mode 100644 app/views/posts/_form.html.erb
 create mode 100644 app/views/posts/edit.html.erb
 create mode 100644 app/views/posts/index.html.erb
 create mode 100644 app/views/posts/new.html.erb
 create mode 100644 app/views/posts/show.html.erb
 create mode 100644 config.ru
 create mode 100644 config/application.rb
 create mode 100644 config/boot.rb
 create mode 100644 config/database.yml.example
 create mode 100644 config/environment.rb
 create mode 100644 config/environments/development.rb
 create mode 100644 config/environments/production.rb
 create mode 100644 config/environments/test.rb
 create mode 100644 config/initializers/backtrace_silencers.rb
 create mode 100644 config/initializers/cookie_verification_secret.rb
 create mode 100644 config/initializers/inflections.rb
 create mode 100644 config/initializers/mime_types.rb
 create mode 100644 config/initializers/session_store.rb
 create mode 100644 config/locales/en.yml
 create mode 100644 config/routes.rb
 create mode 100644 db/migrate/20100226184313_create_posts.rb
 create mode 100644 db/schema.rb
 create mode 100644 db/seeds.rb
 create mode 100644 doc/README_FOR_APP
 create mode 100644 lib/tasks/.gitkeep
 create mode 100644 public/404.html
 create mode 100644 public/422.html
 create mode 100644 public/500.html
 create mode 100644 public/images/rails.png
 create mode 100644 public/javascripts/application.js
 create mode 100644 public/javascripts/controls.js
 create mode 100644 public/javascripts/dragdrop.js
 create mode 100644 public/javascripts/effects.js
 create mode 100644 public/javascripts/prototype.js
 create mode 100644 public/javascripts/rails.js
 create mode 100644 public/robots.txt
 create mode 100644 public/stylesheets/.gitkeep
 create mode 100644 public/stylesheets/scaffold.css
 create mode 100755 script/rails
 create mode 100644 test/fixtures/posts.yml
 create mode 100644 test/functional/posts_controller_test.rb
 create mode 100644 test/performance/browsing_test.rb
 create mode 100644 test/test_helper.rb
 create mode 100644 test/unit/helpers/posts_helper_test.rb
 create mode 100644 test/unit/post_test.rb
 create mode 100644 vendor/plugins/.gitkeep
 
mac-2:testapp andrzejsliwa$ git remote add origin git@github.com:andrzejsliwa/testapp.git
mac-2:testapp andrzejsliwa$ git push origin master
Counting objects: 84, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (68/68), done.
Writing objects: 100% (84/84), 81.08 KiB, done.
Total 84 (delta 2), reused 0 (delta 0)
To git@github.com:andrzejsliwa/testapp.git
* [new branch]      master -> master

Drukowanie bezposrednio do PDF

W aplikacjach webowych format PDF ugruntował już swoją pozycję. W wiekszości przypadków jest formatem w którym "drukowane" są zarówno faktury jak i wszelkiej maści dokumenty informacyjne. W przypadku frameworka Ruby on Rails do tej pory korzystałem z biblioteki Prawn.

Niestety możliwości tej biblioteki są dość ubogie jeśli chodzi o tworzenie dokumentów mocno customizowanych. Wymusząjąc wręcz rysowanie co bardziej skomplikowanych elementów wizualnych. W powiązaniu z wymaganiami klienta powodowało to ciągłą, syzyfową pracę, by zapewnić poprawne wyświetlanie dokumentów gdzie treść oraz jej rozkład mogł się zmieniać.

Zrażony tymi problemami postanowiłem znaleść rozwiązanie bazujące na htmlu jako formacie źródłowym dla PDF. Skierowałem swoje pierwsze kroki w kierunku GitHuba i tam też znalazłem gotowe rozwiązanie moich problemów w postaci plugina.

Plugin nazywa się Wicket PDF i jest tak naprawde prostym wrapperem dla programu uruchamianego z lini poleceń wkhtmltopdf (bazujący na webkit).

Instalacja rozwiązania polega na zainstalowaniu wkhtmltopdf (ze źródeł, bądź z prekompilowanych binarek)

w przypadku mojego systemu operacyjnego(Mac OSX) wygląda to następująco:

$ wget http://wkhtmltopdf.googlecode.com/files/wkhtmltopdf-0.9.1-OS-X.i368
$ sudo mv wkhtmltopdf-0.9.1-OS-X.i368 /opt/local/bin/wkhtmltopdf
$ sudo chmod +x wkhtmltopdf

możemy oczywiście przetestować funkcjonowanie tego programu:

$ wkhtmltopdf www.google.pl google.pdf

lub

$ wkhtmltopdf file:///Users/andrzejsliwa/Desktop/test.html test.pdf

następnie instalujemy sam plugin:

$ script/plugin install git://github.com/mileszs/wicked_pdf.git
$ script/generate wicked_pdf

tak zainstalowany plugin można bez problemu wykorzystać w następujący sposób:

# GET /pages/1
# GET /pages/1.xml
def show
  @page = Page.find(params[:id])

  respond_to do |format|
    format.html # show.html.erb
    format.xml  { render :xml => @page }
    format.pdf do
      render :Pdf => "#{@page.id}",
        :template => 'pages/show.html.erb',
        :wkhtmltopdf => '/opt/local/bin/wkhtmlopdf'
    end
  end
end

generowanie linków dla dokumentów pdf może wyglądać następująco:

<%= link_to "PDF", page_path(@page, :format => 'pdf'), :target => "_blank"%>

Lektura obowiązkowa:
http://github.com/mileszs/wicked_pdf
http://code.google.com/p/wkhtmltopdf/

Oszukiwanie jest fajne

W codziennej pracy często zdarza się sytuacja kiedy potrzebujemy informacji odnosnie jakiegos narzędzia, bibiloteki itp. Wtedy ruszamy często do Googla. Lecz można również... oszukiwać (cheat - oszustwo) instalując taki oto przydatny wynalazek ułatwiający nam życie:

$ gem install cheat

i odrazu możemy z niego korzystać, powiedzmy że potrzebuję informacji jak używać git'a:

$ cheat git

I dostaniemy taki oto cheatsheet:

git:
  Setup
  -----
  
  git clone <repo>
    clone the repository specified by <repo>; this is similar to "checkout" in
    some other version control systems such as Subversion and CVS
  
  Add colors to your ~/.gitconfig file:
  
    [color]
      ui = auto
    [color "branch"]
      current = yellow reverse
      local = yellow
      remote = green
    [color "diff"]
      meta = yellow bold
      frag = magenta bold
      old = red bold
      new = green bold
    [color "status"]
      added = yellow
      changed = green
      untracked = cyan
  
  Highlight whitespace in diffs
  
    [color]
      ui = true
    [color "diff"]
      whitespace = red reverse
    [core]
      whitespace=fix,-indent-with-non-tab,trailing-space,cr-at-eol
  
  Add aliases to your ~/.gitconfig file:
  
    [alias]
      st = status
      ci = commit
      br = branch
      co = checkout
      df = diff
      lg = log -p
  
  
  Configuration
  -------------
  
  git config -e [--global]
    edit the .git/config [or ~/.gitconfig] file in your $EDITOR
  
  git config --global user.name 'John Doe'
  git config --global user.email johndoe@example.com
    sets your name and email for commit messages
  
  ...
  

Albo informacji odnośnie np. rspec'a:

$ cheat rspec

I dostaniemy taki oto cheatsheet:

rspec:
  INSTALL
  =======
    INSTALL rspec
    =============
  $ sudo gem install rspec
    OR
  $ ./script/plugin install git://github.com/dchelimsky/rspec.git
  
    INSTALL rspec_on_rails plugin
    =============================
  $ ./script/plugin install git://github.com/dchelimsky/rspec-rails.git
  
  
  BOOTSTRAP THE APP
  =================
  $ ./script/generate rspec
        create  spec
        create  spec/spec_helper.rb
        create  spec/spec.opts
        create  previous_failures.txt
        create  script/spec_server
        create  script/spec
  
  
  
  HOW TO USE
  ==========
  
    COMMAND LINE
    =============
  spec --color --format specdoc user.rspec
  
    RAILS
    =============
  ./script/generate rspec_model User
  rake doc:plugins # generates local docs for your app's plugins
  rake -T spec # lists all rspec rake tasks
  rake spec # run all specs
  rake spec SPEC=spec/models/mymodel_spec.rb SPEC_OPTS="-e \"should do
  something\"" #run a single spec
  
  
  
  
  module UserSpecHelper
    def valid_user_attributes
      { :email => "joe@bloggs.com",
        :username => "joebloggs",
        :password => "abcdefg"}
    end
  end
  
  
  describe "A User (in general)" do
    include UserSpecHelper
  
    before(:each) do
      @user = User.new
    end
  
    it "should be invalid without a username" do
      pending "some other thing we depend on"
      @user.attributes = valid_user_attributes.except(:username)
      @user.should_not be_valid
      @user.should have(1).error_on(:username)
      @user.errors.on(:username).should == "is required"
      @user.username = "someusername"
      @user.should be_valid
    end
  end
  
  SHOULDA COULDA WOULDA
  =====================
  target.should satisfy {|arg| ...}
  target.should_not satisfy {|arg| ...}
  
  target.should equal <value>
  target.should not_equal <value>
  
  target.should be_close <value>, <tolerance>
  target.should_not be_close <value>, <tolerance>
  
  target.should be <value>
  target.should_not be <value>
  
  target.should predicate [optional args]
  target.should be_predicate [optional args]
  target.should_not predicate [optional args]
  target.should_not be_predicate [optional args]
  
  target.should be < 6
  target.should == 5
  target.should_not == 'Samantha'
  
  target.should match <regex>
  target.should_not match <regex>
  
  target.should be_an_instance_of <class>
  target.should_not be_an_instance_of <class>
  
  target.should be_a_kind_of <class>
  target.should_not be_a_kind_of <class>
  
  target.should respond_to <symbol>
  target.should_not respond_to <symbol>
  
  *OLD:*
  proc.should raise <exception>
  proc.should_not raise <exception>
  proc.should_raise <exception> # not available anymore
  *NEW:*
  lambda {a_call}.should raise_error
  lambda {a_call}.should raise_error(<exception> [, message])
  lambda {a_call}.should_not raise_error
  lambda {a_call}.should_not raise_error(<exception> [, message])
  
  proc.should throw <symbol>
  proc.should_not throw <symbol>
  
  target.should include <object>
  target.should_not include <object>
  
  target.should have(<number>).things
  target.should have_at_least(<number>).things
  target.should have_at_most(<number>).things
  
  target.should have(<number>).errors_on(:field)
  
  proc { thing.approve! }.should change(thing, :status).
      from(Status::AWAITING_APPROVAL).
      to(Status::APPROVED)
  
  proc { thing.destroy }.should change(Thing, :count).by(-1)
  
  Mocks and Stubs
  ===============
  
  user_mock = mock "User"
  user_mock.should_receive(:authenticate).with("password").and_return(true)
  user_mock.should_receive(:coffee).exactly(3).times.and_return(:americano)
 
  user_mock.should_receive(:coffee).exactly(5).times.and_raise(NotEnoughCoffeeExc
  ption)
  
  people_stub = mock "people"
  people_stub.stub!(:each).and_yield(mock_user)
  people_stub.stub!(:bad_method).and_raise(RuntimeError)
  
  user_stub = mock_model("User", :id => 23, :username => "pat", :email =>
  "pat@example.com")
  

Prawda że fajnie jest oszukiwać ! ;)

Lektura obowiązkowa:
http://cheat.errtheblog.com/