//DEVGURU

Category archives ‘rails’

Turning off auto timestamping for testing in Rails

Saturday, May 9th, 2009

Suppose that you implemented a functionality that depends on values of created_at or updated_at fields of your models. How do you test it?

If you use fixtures that reside in test/fixture/*.yml files then there is no problem, because the values you set there for created_at and updated_at fields are saved to the database ‘as is’. So you can easily have an article created one week ago:
[sourcecode lang="yml"]article:
title: What a great day
created_at: < %= 1.week.ago.to_s(:db) %>
updated_at: < %= 1.week.ago.to_s(:db) %>
[/sourcecode]

However, I don’t use fixtures files myself. I feel a bit dirty using them ;) I find fixture replacement tools far more maintainable. Namely, I love thoughtbot’s Factory Girl. But here comes the problem. This won’t work as expected with Factory Girl:


Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)

That’s because ActiveRecord’s automatic timestamping feature sets Time.now for created_at and updated_at fields overriding our values. At least that’s ActiveRecord’s default behavior. Fortunately it can be disabled with:


Article.record_timestamps = false

Chances are that after creating a model with a custom timestamp we’ll want to turn automatic timestamping back on. But turning it off and on in many places in your unit tests would be pretty cumbersome. Wouldn’t it be cool if you could achieve all of this with a snippet below?

without_timestamping_of Article do
  Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)
end

It turns timestamping off, executes the block and turns timestamping back on. I find it clean and dry. Here’s the code to place in your test_helper.rb:


# test_helper.rb
class Test::Unit::TestCase # or class ActiveSupport::TestCase in Rails 2.3.x
  def without_timestamping_of(*klasses)
    if block_given?
      klasses.delete_if { |klass| !klass.record_timestamps }
      klasses.each { |klass| klass.record_timestamps = false }
      begin
        yield
      ensure
        klasses.each { |klass| klass.record_timestamps = true }
      end
    end
  end
end

Hope you like it. If so, share :)

Dynamic cookie domains with Rack’s middleware

Friday, April 17th, 2009

Handling sessions in multi-domain environment is not the simplest things to do, because of the fact that cookies are scoped to a domain they were set by.

Recently we were developing an application with such an idea in mind:

  • Application will work as a base for other mini-applications (which we call sites)
  • Each site can be accessed via different url types: site.example.org and example.org/site
  • We want the users to remain logged in when switching from one url type to another

I won’t be covering application structure, routing, etc. here, I will only write about maintaing the sessions is such an environment.

So this is pretty simple here – all that we needed to do was to set cookie domain to .example.org (note the “dot” at the beginning). This could be done via:

ActionController::Base.session = {
  :domain => ".example.org"
}

However there was an additional requirement that we need to deal with:

  • Each site can be accessed via custom domain – site.com
  • Of course there’s no way here to keep the user logged in when he’s switching from site.com to example.org/site or site.example.org, at least it cannot be done with setting cookie domain to whatever value

Technically, to access the site via site.com, that domain must point to our IP address. Then we need to detect that the site is being accessed via custom domain and set cookie domain respectively.

This could be done via some funky before_filters in an Application Controller, however we found much better and cleaner way.

Rack’s middleware to the rescue

Rack itself is a minimal interface between web server and your ruby framework. It’s used by Ruby on Rails (since 2.3) and Merb. The request comes from web server, goes through middleware layers and enters the application.

So we wrote a middleware layer that detects the host with which our application is accessed and sets cookie domain for the request. Here it is:

app/middlewares/set_cookie_domain.rb

class SetCookieDomain
  def initialize(app, default_domain)
    @app = app
    @default_domain = default_domain
  end

  def call(env)
    host = env["HTTP_HOST"].split(':').first
    env["rack.session.options"][:domain] = custom_domain?(host) ? ".#{host}" : "#{@default_domain}"
    @app.call(env)
  end

  def custom_domain?(host)
    domain = @default_domain.sub(/^\./, '')
    host !~ Regexp.new("#{domain}$", Regexp::IGNORECASE)
  end
end

Now we need to turn it on:

environment.rb

config.load_paths += %W( #{RAILS_ROOT}/app/middlewares )

production.rb

config.middleware.use "SetCookieDomain", ".example.org"

.example.org is the default domain that will be used unless the application is accessed via custom domain (like site.com), we give it different values depending on environment (production/staging/development etc).

And since we’re fans of test driven development, here’s the test that ensures us that everything works as expected:

tests/integration/set_cookie_domain_test.rb

require 'test_helper'

class SetCookieDomainTest < ActionController::IntegrationTest

  context "when accessing site at example.org" do
    setup do
      host! 'example.org'
      visit '/'
    end

    should "set cookie_domain to .example.org" do
      assert_equal '.example.org', @integration_session.controller.request.session_options[:domain]
    end
  end

  context "when accessing site at site.com" do
    setup do
      host! 'site.com'
      visit '/'
    end

    should "set cookie_domain to .site.com" do
      assert_equal '.site.com', @integration_session.controller.request.session_options[:domain]
    end
  end

  context "when accessing site at site.example.org" do
    setup do
      host! 'site.example.org'
      visit '/'
    end

    should "set cookie_domain to .example.org" do
      assert_equal '.example.org', @integration_session.controller.request.session_options[:domain]
    end
  end

end

Test is sponsored by great Shoulda and Webrat gems.

Feel free to comment and share.

Websecurity through conventions and best practices

Friday, March 27th, 2009

Let me present you materials from a talk I gave at IT Underground Conference in Prague on 24.03.2009. The title of a presentation is “Websecurity through conventions and best practices” and it’s focused on securing web applications from the most common attacks with examples from Ruby on Rails web development framework.

The slides include all speaker’s notes.

Useful gems: Quick Scopes

Thursday, March 26th, 2009

Some time ago I’ve found an useful gem – internuity’s quick scopes.

As readme for the plugin says,

A Rails plugin to automatically add some quick named_scopes to your models.

These are especially useful for quick modifications to a query on an association.

It creates default named_scopes for any of your models:

  • order
  • limit
  • offset
  • with – alias for :include
  • where – alias for :conditions

Personally I don’t like mixing named_scopes with :conditions and :order options – with this gem we can chain our scopes with limit, order etc:

  user.posts.order('created_at')

  user.posts.where(:published => true)

Another small thing that makes our code cleaner.

Rails 2.2.2, Ajax and respond_to

Saturday, January 31st, 2009

As I wrote some time ago in the article about Rails, Ajax and jQuery, sometimes there are problems with Rails not interpreting correctly content type headers of ajax requests. It’s because not all web browsers send that header in the same way.

What I proposed was to sort the request.accepts array (array containing content type headers sent by browser) so that xml content type would be the first element. That would then trigger format.xml in our respond_to block.

However that approach does not work in Rails 2.2.2, because now the request.accepts array is frozen and it cannot be modified. I spent some time googling for the solution, but with no effect. So I dived into the API and Rails’ source code and came up with pretty nice and simple solution to the problem.

class ApplicationController < ActionController::Base
  before_filter :xhr_to_xml

  def xhr_to_xml
    request.format = :xml if request.xhr?
  end
end

This piece of code is an equivalent of the snippet I proposed in the article I referred to at the beginning. Now all ajax request will trigger format.xml in respond_to blocks.

Authentication made easy: Authlogic

Wednesday, January 28th, 2009

Before Authlogic, here at Netguru we we’re using Restful Authentication to handle user authentication.

Because we think that Rails applications should be restful. And so should be the authentication systems. But restful auth had some downsides – it was generating so much code that our user model was too messy (Digitalism should change their lyrics to “so take a look at my User model, it’s such a mess!”). Then I started looking for some better way. Wiktor suggested that I should try authgasm, but! Authgasm has changed it’s name to Authlogic.

Idea
What’s so good about it? It’s just as DRY and clean as authentication plugin can be. All that we need to do is define “acts_as_authentic” in our user model, generate the UserSession model and create a restful UserSessions controller, where we need to create and save an UserSession. Just like any other ActiveRecord model.

Features
Doesn’t sound nice enough? We can customize almost every aspect of it. We want to use email as user’s login? No problem, it’ll check uniqueness and even validate_format_of. We want to scope the uniqueness validation of login to certain account? No problem, just pass the :scope => :account_id to acts_as_authentic and then change UserSessions controller to use @account.user_sessions. We want to log such things as login count, last login ip or current login date? Just create “magic columns” named login_count, last_login_ip, current_login_at. Persistence tokens, single access tokens? It’s all there. It’s also framework agnostic and can easily support OpenID…

What next? I recommend you to check the readme and example rails application with authlogic. It’s worth giving a try.

What stuff I found in Rails 2.2

Monday, January 26th, 2009
When I started our first serious project in Rails the latest stable version was 2.1. Altrough I were still learning the framework, it quickly became obvious that some of it’s internals could have been implemented better. And in fact the next stable version, 2.2, which was released few months ago, was improved in many of the aspects I were thinking of.  Here are the new features I welcomed the most:

  • Added :only and :except to map.resources to let people cut down on the number of redundant routes in an application. Typically only useful for huge routesets.

You can find :only and :except in many places in the rails application and now, finally, in routes too. Usage:

  • Fixed that polymorphic_url should compact given array.

That will definitely dry up some code if you’re using polymorphic routes extensively.

  • Routes may be restricted to lists of HTTP methods instead of a single method or :any.

I could never really understand why I need to specify :any if I only want to allow POST and GET in my actions and not PUT or DELETE. Not more such problems after that fix. Usage:

  • Add :as option to render a collection of partials with a custom local variable name.

That is cool. No more setting :locals or variable renaming inside the parial. Usage:

  • Added block-call style to link_to

Nice alternative to one-line link_to’s. Helps to format HTML in links nicely. Usage:

  • Merge scoped :joins together instead of overwriting them.

This should be there much earlier. Now be careful, this may expose scoping bugs in your code when you’re migrating from earlier versions of Rails.

  • before_save, before_validation and before_destroy callbacks that return false will now ROLLBACK the transaction. Previously this would have been committed before the processing was aborted.

I never realized that integrity of my Rails 2.1 app database could be abused because of before_* callbacks breaking the action chain. Now I do and I am happy that I’m protected.

  • Allow conditions on multiple tables to be specified using hash.

That is something I was intuitively using when learning Rails which was resulting with errors as this feature had not been implemented at that time. Now it is, great.

  • Wrapped Rails.env in StringInquirer so you can do Rails.env.development?

Nice and pretty looking. No more messing with ENV array.

When I was preparing to making the switch to 2.2 I found out that Rails API documentation is missing lot of stuff. Fortunately that stuff can still be found in Rails changelogs and I recommend that you read them!

Rails and IE false image upload headers.

Wednesday, January 14th, 2009

Some time ago, when I was young and unexperienced (ok, it was an hour ago…), I’ve found out that my image upload form is not working on IE. No errors, but no image either.

Here is what i came to  after an hour of debug:

the model has following validation rule:

validates_attachment_content_type :content, :content_type => ['image/jpeg', 'image/png', 'image/gif']

but IE sends false image header (god knows why?) so you need to add:

‘image/pjpeg’, ‘image/x-png’

to get it going again.

Works like a charm.

Fragment caching with Memcache and Rails 2.1

Saturday, January 10th, 2009

I’ve only recently had a possibility to try the new Rails 2.1 integrated caching. What I first learned was that there hardly is any documentation about it.

So it took me a while to find out that caching HTML fragments with time-based expiration (with Memcache) is as easy as adding:

config.cache_store = :mem_cache_store, { :namespace => 'myapp_production' }

to your config/environments/production.rb to specify Memcache as the caching mechanism. You can also use memory, or filesystem, but let’s be serious here. Memcache is the way to go. What’smore, time-based expiration doesn’t work for the other ones.

Now you can use something like this in your view:

The hardest thing to google for was the :expires_in option. Remember it only works with memcache.

By the way, it’s a good idea to use Memcache also for session storage. To do so, add the following line to config/environments/production.rb.

config.action_controller.session_store = :mem_cache_store

For more information on the basics check out the blog post by TheWebFellas.

Metric_fu i Rails 2.2

Friday, January 2nd, 2009

O Metric_fu można poczytać u Sebastiana Nowaka – najprościej rzecz biorąc jest to zbiór rake tasków z narzędziami, które sprawdzają nam takie rzeczy jak złożoność kodu czy pokrycie kodu testami (przy użyciu rcov).

Z tym drugim problem mają Railsy 2.2 – po krótkiej korespondencji mailowej z Jake Scruggsem (autorem metric_fu) wynika, że:

  • task “coverage” potrzebuje trochę pracy ;),
  • nie wiadomo, czy coverage zostanie w metric_fu – Jake powołuje się na to, że ludzie często przy testach stosują swoje autorskie rozwiązania i ciężko jest napisać coś, co zadowoli wszystkich
Dodatkowo mamy kawałek kodu, który możemy wrzucić do jakiegoś raketaska i powinien nam “zrcovować” nasze testy:

Rails, Ajax i jQuery

Tuesday, December 9th, 2008

Poniżej przedstawiam swoje podejście do wprowadzania ajaxowych funkcjonalności do aplikacji napisanych w Ruby on Rails. Jest to zbitka rozwiązań znalezionych na różnych blogach, forach oraz trochę moich przemyśleń. Do obsługi Javascript używam biblioteki jQuery, nie korzystam z jRails ani żadnych helperów do JS/Ajax dostępnych z Rails. Dzięki temu cały kod javascriptowy/htmlowy zaprezentowany poniżej może być bez trudu użyty w aplikacjach napisanych w innych językach/frameworkach. Zacznijmy.

Ruby on Rails a REST

Requesty ajaxowe różnią się trochę od zwykłych requestów, które dochodzą do serwera gdy użytkownik wpisze URL w przeglądarce. A przynajmniej powinny się różnić, żebyśmy po stronie serwera wiedzieli jaką odpowiedź (widok) zwrócić, nie ma bowiem sensu renderować całego layoutu np. dla requestu ajaxowego oceniającego zdjęcie w naszym serwisie. Tym aspektem odróżniającym różne typy requestów jest w tym przypadku nagłówek zawierający oczekiwany format odpowiedzi od serwera.

Dzięki implementacji REST w Railsach, sposób w jaki obsługujemy akcje jest zawsze taki sam niezależnie od typu requestu, jedyna zmiana dotyczy zwracanego przez nas widoku. Decydujemy o tym w kontrolerze:

To, który blok zostanie wywołany i w efekcie tego jaki widok zwrócony, zależy od route’ów zdefiniowanych w routes.rb oraz od oczekiwanego przez klienta formatu danych. W większości chcielibyśmy, żeby wszelkie ajaxowe requesty oczekiwały formatu XML (w odróżnieniu od HTML dla normalnych requestów), dlatego musimy zadbać o to, by przeglądarka odpowiednio ustawiała nagłówek Accept na ‘text/xml’ przy wywołaniach ajaxowych. W jQuery możemy ustawić to od razu dla wszystkich requestów z góry:

Różnice między przeglądarkami

Ręczne ustawienie nagłówka Accept niestety potrafi dawać różne efekty w różnych przeglądarkach. Wartością domyślnie ustawianą w przeglądarkach dla tego nagłówka jest text/html. Przy wywołaniu powyższego kodu Firefox zmieni wartość na ‘text/xml’, podczas gdy niektóre wersje IE i Safari ustawią ‘text/html; text/xml’ oczekując w ten sposób odpowiedzi od serwera w jednym z tych dwóch formatów.

Jest to dość istotna różnica, ponieważ Railsy decydując o tym, który format odpowiedzi wybrać, biorą pod uwagę pierwszy format napotkany w tablicy request.accept, którym w przypadku IE i Safari będzie text/html. Takie zachowanie zaobserwowałem chociażby w wersji 2.1.0 Railsów.

Poniższy kod naprawia opisane zachowanie przenosząc format xml na początek tablicy (jeśli się tam w ogóle znajduje), dzięki czemu będzie on znaleziony jako pierwszy.

Ajaxowe linki

Poniższy kod Javascript pozwala szybko zamienić istniejące już linki na działające ajaxowo. Proponuję go wrzucić do pliku appliacation.js.

 

Teraz wystarczy dodać do istniejących linków klasę (CSS’ową) get, post, put lub delete w zależności od pożądanej metody wywołania linku, by zmienić je w linki ajaxowe. Polecam do tego plugin LiveQuery, który to zachowanie automatycznie podepnie także do każdego nowego linku opatrzonego którąś z tych klas, który pojawi się na stronie (np. w efekcie działania jakiegoś wywołania ajaxowego). Dodatkowo do naszych linków możemy dodać opcjonalny atrybut ajaxtaget i ustawić jego wartość na selektor CSS’owy wskazujący na element, do którego zostanie załadowany widok zwrócony przez serwer.

Ajaxowe formularze

Do wysyłki formularzy porzez ajax będziemy potrzebować kolejnego pluginu – jQuery Form.

Ajaxowe formularze będą identyfikowane poprzez CSS’ową klasę ‘ajax’.

Modyfikacja strony po wywołaniu ajaxowym

Standardowym sposobem wyświetlania efektów wywołania ajaxowego jest odbieranie, obróbka i umieszczenie tego, co ono zwróci w jakimś miejscu na stronie. Innym sposobem jest umieszczenie całego kodu modyfikującego stronę w odpowiedzi zwracanej przez ajaxowe wywołanie i tylko wykonywanie go przez przeglądarkę. Taką funkcjonalność daje nam kolejny plugin do jQuery – Taconite. Cytując autora pluginu: “Taconite pozwala wykonywać modyfikacji wielu elementów strony naraz, korzystając z odpowiedzi zrwóconej przez pojedyncze wywołanie ajaxowe. Taconite używa do tego XML’owego dokumentu zawierającego instrukcję jak modyfikować stronę”.

Przykładowy widok XML z instrukcjami dla Taconite wygląda tak:

Dzięki podejściu oferowanym przez Taconite możemy poprawić strukturę aplikacji. Weźmy dla przykładu wspomnianą wcześniej funkcjonalność głosowania. Do tej pory kod Javascriptowy, który dbał o wyświetlenie rezultatów oddania głosu (np. zwiększenie countera, usunięcie linku ‘głosuj’, wyświetlenie komunikatu ‘głos został oddany’, itp) musiał być umieszczany na każdej stronie, na której było możliwe oddanie głosu. Teraz jest on po prostu zwracany jako odpowiedź serwera na oddanie głosu, czyli znajduje się tam gdzie być powinien.

Kolejny przykład – wyświetlanie flash messages w odpowiedzi na wywołania ajaxowe. Załóżmy, że to jest fragment layoutu naszej strony, w którym wyświetlamy komunikaty flash[:notice]:

Niech to będzie layout taconite, w którym zagnieżdzamy widoki zwracane na wywołania ajaxowe:

Teraz komunikaty flash[:notice] będą wyświetlane w standardowym miejscu na stronie z efektem fade-in. W podobny sposób możemy aktualizować inne elementy strony, np. wszelakie countery.

A Ty jakich narzędzi używasz?

Jestem ciekaw jakich rozwiązań używacie do implementacji Ajaxa w Waszych aplikacjach. Preferujecie jakieś inne pluginy/biblioteki?

Przyspieszanie nowych Railsów: memoization

Tuesday, December 9th, 2008

Problem?

Termin memoization nie jest niczym nowym w programowaniu, prawie-rzetelne źródła podają rok 1968 jako pierwsze pojawienie się go.

Na czym polega memoizacja (że tak sobie pozwolę na spolszczenie)? Proste: metoda zapamiętuje (można o tym pomyśleć jak o keszowaniu) swój wynik (w zależności od podanych argumentów) i wywołana kolejny raz – zwraca zapamiętaną i zamrożoną wartość.

Implementacja w Railsach

Memoization było często stosowaną praktyką – pisane “z ręki”, ale jako metoda ActiveSupport weszło do core railsów kilka m-cy temu, dzięki czemu wyszło wraz z wersją 2.2. Spróbujmy!

W ten sposób metoda wywołana kolejny raz nie będzie wykonywać skomplikowanych obliczeń, a jedynie zwróci swoją zmemoizowaną wartość. Co więcej, metoda może przyjmować argumenty – i zapamiętuje swoją wartość w zależności od podanego argumentu!

Tak więc mamy bardzo miły sposób na nie-zarzynanie rubiego.

Kiedy nie stosować memoizacji?

W przypadku dynamiczności zwracanych wyników (kiedy metoda korzysta np. z Time.now). Wyniki są zamrożone dla danego requestu i jeśli chcemy, żeby podczas niego obliczała coś kilka razy – odpuścmy sobie memoizację (lub wykonajmy zmemoizowaną metodę z symbolem :reload jako ostatnim argumentem).

Railsowa magia nie do końca skuteczna, czyli counter cache

Wednesday, November 19th, 2008

Magia!

Railsy są tworzone z myślą “konwencja nad konfiguracją” – czego jednym z przykładów może być counter cache. Jak to działa?

Mamy model User. User ma wiele Postów, więc Post belongs_to :user. Chcemy wyświetlić liczbę postów, jakie napisał użytkownik. Wywołując, dajmy na to, User.first.posts.count dostajemy aktualną liczbę rekordów Post w bazie. Nic zaskakującego. Jeśli zdefiniujemy w tabeli users pole “posts_count” a przy definiowaniu relacji Post belongs_to :user dodamy :counter_cache => true – liczba postów należących do użytkownika będzie zapisywana w rekordzie użytkownika (a konkretnie – inkrementowna lub dekrementowana przy tworzeniu postów), przez co (kosztem nadmiarowości (redundancja to zbyt egzotyczne, jak dla mnie, słowo) – ale to mocno dyskusyjna sprawa) oszczędzamy sobie zapytań SQL zmuszających bazę do nieustannego mielenia. Fajne.

Fajne – w standardowych przypadkach.

“Konwencja nad konfiguracją” ma to do siebie, że działa w standardowych zastosowaniach, a problemy pojawiają się, gdy chcemy rozszerzyć jakąś funkcjonalność.

Przykład: komentarze muszą zostać zatwierdzone przez moderatora, a więc nie chcemy podliczać użytkownikowi zawieszonych przez moderatora komentarzy (a jedynie te aktywne – do takich rzeczy polecam acts_as_state_machine). Counter_cache w tym przypadku odpada – będzie inkrementował licznik komnetarzy przy stworzeniu, dekrementował przy wywołaniu destroy, ale zmiana stanu na nic nie wpłynie.

Napiszmy to sami.

Odpuścimy sobie ustawianie counter_cache na true przy belongs_to i dopiszemy kilka callbacków w modelu komentarza:

(Powyższy kod zakłada, że mamy zdefiniowane named_scope :active).
Zastosowań tej metody jest więcej – możemy np. nie usuwać rekordów z bazy przy usuwaniu komentarza, a jedynie zmieniać jego stan (acts_as_state_machine!) na “deleted” i wyświetlać ładną informację pt. “ten komentarz został usunięty”, przez co nie zaburzamy toku dyskusji – after_save wywoływany przy zmianie stanu zmieni liczbę komentarzy na aktualną; działa też b. fajnie w momencie gdy obiekt nie zostaje stworzony, a jedynie przypisany parentowi (po drobnej modyfikacji, rzecz jasna).

Paginacja po polsku, czyli numerowanie stron

Wednesday, October 29th, 2008

Prawie każdy projekt Railsowy, w którym wyświetla się jakiekolwiek dane z podziałem na strony korzysta z will_paginate, czy to w formie plugina, czy gema. Nie ma się co oszukiwać, jest to swego rodzaju standard, tak jak kiedyś attachment_fu (”kiedyś”, czyli do czasu pojawienia się Paperclipa). Zresztą, nie jest to dziwne, gdyż korzystanie z niego jest bardzo wygodne. Drobny problem pojawia się jedynie gdy chcemy dostosować will_paginate do polskich realiów.

Generowany przez will_paginate kod HTML domyślnie wygląda następująco:

My jednak znajdujemy się w kraju Piastów i rażą nas angielskie terminy “next” i “previous”. Jest na to proste rozwiązanie, wystarczy do metody will_paginate dodać odpowiednie parametry:

I mamy prawdziwie słowiańskie podpisy.

Jak ktoś chce pójść krok dalej i samemu zdefiniować jak powinna wyglądać numeracja stron, to polecam dobre wprowadzenie na blogu thewebfellas.

Ulepszanie Railsowego Paperclipa

Sunday, October 26th, 2008

Paperclip to obecnie jeden z najlepszych, o ile nie najlepszy plugin do Ruby on Rails automatyzujący proces uploadu plików na serwer. Potrafi m.in. od razu sam wygenerować miniaturki zapisywanych obrazków, obsługuje także zapis na serwerach S3 Amazona. Każdy, kto go używał na pewno docenia jego zalety i prostotę obsługi, ale myślę też, że wielu natknęło się na sytuacje gdy okazał się on jednak niewystarczający.

Przykładowo, miniaturki obrazków generowane są zawsze w ten sam standardowy sposób co daje zamierzony efekt w większości przypadków, ale gdy chcemy poddać nasz obrazek jakimś dodatkowym transformacjom podczas skalowania, okazuje się to niemożliwe.

Innym przykładem jest obsługa S3 Amazona. Paperclip potrafi tam zapisywać pliki bez żadnych problemów, wystarczy, że podamy nasz API key oraz nazwę bucketa, do którego trafiać mają pliki. Co jednak w sytuacji gdy później na jednej stronie chcemy wyświetlić na raz wiele zuploadowanych zdjęć siedzących w jednym buckecie? Zdjęcia będą się ładować wolno. A to dlatego, że większość przeglądarek ma domyślnie włączone ograniczenie pozwalające w jednej chwili pobierać tylko 2 pliki z jednego hosta, niezależnie od dostępności łącza. Zatem dobrze było by rozmieścić nasze zdjęcia na wielu hostach (bucketach), żeby zwiększyć prędkość pobierania.

Na szczęście dzięki przemyślanej budowie samego pluginu i możliwości łatwego rozszerzania klas i modułów z dowolnego miejsca w języku Ruby nic nie stoi na przeszkodzie, żeby powyższe funkcjonalności dodać samemu.

Postanowiliśmy więc nie czekać na developerów Paperclipa i stworzyliśmy pakiet własnych rozszerzeń – PaperclipExtended. Znajdziecie tam rozwiązania powyżej wymienionych problemów oraz paru mniejszych. Jeśli okaże się, że jeszcze czegoś nam brakuje, na pewno to dodamy.

P.S. Paperclip 3.0 ma zawierać możliwość dowolnego definiowania komend przekształcających obrazki (coś a’la commands w PaperclipExtended) oraz móc skalować pliki video. Ale to dopiero w planach developerów, zanim się tego doczekamy, musimy działać sami :)