deferred?(env) w Thin, Ebb i Merb

18 kwietnia 2008, 11:04:35

Poziom: 0 | Kategoria: Komputerowo-internetowo, Merb, Ruby, Techblog. | Dodaj komentarz

Jak donosi Ezra Zygmuntowicz Merb doczekał się wsparcia dla deferred requests. Ale o co chodzi?

Porównując serwery operujące na zdarzeniach (event driven) z operującymi na wątkach (threaded) można dojść do wniosków, iż te operujące na zdarzeniach są o wiele szybsze przy serwowaniu krótkich żądań. Dzieje się tak, gdyż nie ma narzutu na odpalenie nowego wątku i przełączaniu kontekstu. Ry Dahl wspominał o tym w swojej prezentacji na RuPy. Jednak co z długimi requestami, takimi jak upload pliku, czy skomplikowane wyliczenia? Jak się okazuje jedne z najpopularniejszych i najszybszych serwerów do frameworków w Rubym, które wspierają interfejs RackThin oraz Ebb mają metodę deferred?(env) w ich Rackowym interfejsie. Oba serwery będą odpalały tę metodę na objekcie aplikacji przed jego wywołaniem. Taki trik pozwala adapterowi Rack zadecydować czy request powinien być obsłużony przez osobny wątek, czy w pętli zdarzeń. Aby przyspieszyć działanie serwera, wszystkie wolne akcje powinny być oznaczone flagą deferred i wywoływane w osobnych wątkach.

O ile nic nie wiadomo mi o wsparciu deferred?(env) w Railsach, o tyle, jak wspomniałem na początku, Merb doczekał się właśnie takiej funkcjonalności. Jeżeli chcemy poinformować nasz serwer, że jakieś akcje są wolne, do pliku init.rb powinniśmy dodać następującą linijkę:

Merb::Config[:deferred_actions] = ["/uploads/create", "reports/longaction"]

Powyższa linijka sprawi, że wszystkie akcje oprócz wymienionych będą obsługiwane przez event loop. Każdy request do jednej z powyższych akcji spowoduje odpalenie nowego wątku dla niej i nie będzie spowalniać naszej aplikacji.

Co ważne, aby korzystać z dobrodziejstw deferred?(env), należy zaopatrzyć się w najnowsze developerskie wersje zarówno serwerów, jak i Merba. Wszystko można znaleźć oczywiście na GitHubie.

Tagi:

RuPy — dzień drugi

16 kwietnia 2008, 14:30:19

Poziom: 0 | Kategoria: Komputerowo-internetowo, Python, Ruby. | 2 komentarze

Wczoraj podsumowałem pierwszy dzień RuPy, dzisiaj pora na drugi.

Drugi dzień zaczął się od prezentacji Rails, Amazon Web Services and you wygłoszonej przez Krzysztofa Szafranka. Generalnie Amazon Web Services to świetna sprawa dla ludzi potrzebujących dobrej skalowalności i wielkich powierzchni za niewygórowaną cenę. Dowiedziałem się jednak czegoś o innej usłudze — AWS Mechanical Turk. W skrócie to miejsce, gdzie można wynająć sobie prawdziwych ludzi do zrobienia dla nas określonych rzeczy. Dokładniej możemy zadawać pytania w formacie XML, na które wynajęci przez nas Workerzy będą starali się odpowiedzieć. Pytania nie muszą wymagać odpowiedzi "Tak" lub "Nie", możemy na przykład "zapytać" o nową notkę na nasz blog. Jednym z większych zastosowań tego mechanizmu, który na razie jest w fazie BETA, było poszukiwanie na samolotu Steeve'a Fosseta. Niestety aby skorzystać z AWS Mechanical Turk musimy posiadać prawidłowy adres z USA oraz aktywną kartę kredytową tam wydaną. Krzysztof zdołał również zareklamować swój serwis — natrasie.pl.

Następną prezentacją była prezentacja "Rabiego" i "Rabistak", czyli Ruby on Rails deployment with RubyStack Daniela Liszki. Idea RubyStack jest ciekawa i warta poświęcenia chwili uwagi. RubyStack to instalator całego środowiska uruchomieniowego dla Ruby/Railsów, spakowane do jednego pliku wraz z instalatorem graficznym. Można go uruchamiać pod wieloma systemami — aktualnie Windows, Linux, MacOS X, w planach jest obsługa Solarisa. Jednym kliknięciem możemy zainstalować np. Ruby, Rubygems, Apache wraz z potrzebnymi modułami, Railsy i kilka must-have gemów. W przyszłości ma pojawić się narzędzie do dostosowywania do swoich potrzeb paczek. Prezentacja zakończyła się małą polemiką słuchaczy na temat tego, iż /bin/sh nie musi wcale wskazywać na /bin/bash.

Po RubyStack przyszła kolej na krótką prezentację sponsora — Megiteam. Z rozmów kuluarowych dowiedziałem się nowej, ciekawej rzeczy, która prawdopodobnie skusi mnie do skorzystania z usług Megiteam, chodzi o nowy sposób rozliczania usług. Nie ma już rozliczania per proces, zamiast tego dostajemy gwarantowany RAM na swoje procesy + dodatkowe 20MB do krótkoterminowego użycia. To sprawia, że ich oferta staje się bardziej atrakcyjna, co więcej nie trzeba martwić się o konsumpcję pamięci przez serwer mySQL.

Następną prezentacją, na której byłem, była pierwsza część próby napisania IMDB-like aplikacji w Railsach w dosyć krótkim czasie, czyli Is Rails as agile as advertise by Rida Al Barazi. Prezentacja całkiem udana, chociaż po drodze było trochę błędów, które zgłaszała sala. Nie zostałem jednak na drugiej części, która chyba była ciekawsza, ponieważ skupiała się na bardziej zaawansowanych funkcjonalnościach oraz dopracowywaniu graficznej strony aplikacji.

W niedzielę nie zostałem do końca prezentacji i ostatnią, na której byłem był A need of REST Łukasza Piestrzeniewicza. Z powodu "pythonowej" sali i większej części "pythonistów" została pominięta część dotycząca Railsów, Łukasz skupił się bardziej na założeniach REST i jego wykorzystaniu. Ciekawa prezentacja, szczególnie dla ludzi, którzy nie znali lub nie rozumieli do końca idei REST

Podsumowując konferencję, muszę przyznać, że warto było na nią przyjechać, nie tylko ze względu na ciastka i obiadki. Aczkolwiek muszę przyznać, że w tym roku RuPy powinno nazywać się RailsPy — w zasadzie jedna lub dwie prezentacje w części Ruby nie dotyczyły Railsów. Oczywiście nie mam nikomu tego za złe, w końcu sam pracuję w Railsach. Mam tylko nadzieję, że w przyszłym roku pojawi się więcej prezentacji dotyczących samego Rubiego albo innych frameworków (np. Merba). Tegoroczne prezentacje mogą utwierdzać niektórych w mylnym przekonaniu, że Ruby = Rails.

Tagi:

RuPy — krótkie podsumowanie dnia pierwszego

15 kwietnia 2008, 11:03:24

Poziom: 0 | Kategoria: Komputerowo-internetowo, Python, Ruby. | Dodaj komentarz

Miałem przyjemność uczestniczyć w tegorocznym RuPy. Generalnie impreza bardzo przydatna i ciekawa. Chciałem dorzucić swoje trzy groszę i przekazać moje wrażenia.

Dzień 1

Dzień pierwszy zaczął się, jak to na konferencjach bywa, od rejestracji uczestników. Ponieważ na pytanie "Ruby czy Python?" odpowiadam Ruby, można mnie było zobaczyć w zasadzie tylko w sali A, gdzie odbywały się prezentacje związane z Rubym.

Na pierwszy ogień poszła prezentacja Jarosława Rzeszótko o metaprogramowaniu w Ruby. Prezentacja fajna, części trików nie znałem, więc na pewno czegoś się nauczyłem. Jednak sama prezentacja była dosyć szybko przeprowadzona, na slajdach było momentami dużo kodu, co mogło trochę skołować uczestników. Przydałoby się więcej jakiegoś live-codingu zamiast kilkudziesięciu linijek kodu na slajdach.

Następnym prelegentem był Ry Dahl. Opowiedział o swoim serwerze do uruchamiania aplikacji napisanych w Railsach, Merbie i innych frameworkach opartych o Rack, a w przyszłości także Pythonowych aplikacji WSGI — Ebb. Przedstawił po krótce architekturę serwera, wyjaśnił, dlaczego jest on szybszy od innych oraz, co może niektórych zasmucić, powiedział, że nie planuje wersji Ebb pracującej pod kontrolą JRuby.

Następną prezentacją było największe show na RuPy 2008 — Correlations and Conclusions by Zed Shaw. Sama prezentacja przedstawiała bardziej statystyczne podejście do testowania swoich hipotez i wyciągania wniosków z wyników badań. Przy okazji Zed pochwalił się swoimi projektami w Pythonie, które pomogły mu przygotować prezentację, włączając w to ciekawy konwerter Yaml do prezentacji LaTeX-owej w Beamerze.

Po dawce dobrego show nadszedł czas na posiłek. Nie będę tutaj opisywał lunchu, bo i po co. Po obiadku prezentował się Hosting 365, ja w tym czasie ładowałem baterię na korytarzu...

Po sesji ładowania baterii nadszedł czas na TDD w Railsach. Andrzej w ciekawy sposób przedstawił swoje spostrzeżenia na temat TDD jak i BDD. Opisał swoje doświadczenia z wyżej wymienionymi procesami, jak również zafundował uczestnikom małą sesję live-coding. Prezentacja oczywiście na plus, parę rzeczy mi się rozjaśniło, będę na pewno starał się być pragmatycznym w testowaniu i chyba zrezygnuję z bardzo rozbudowanych testów unitowych.

Następnym prelegentem był Jay Fields, który opowiedział o "DSL dla biznesu", czyli "Business Natural Languages". Bardzo ciekawie zaprezentowany bardzo ciekawy temat. Może tylko przykładowa aplikacja zaprezentowana podczas prezentacji była nadto prosta, co mogło nasuwać niektórym na myśl sensowność wytaczania takiej armaty, jaką jest DSL do tak prostych zastosowań, jednak z pewnością Business Natural Languages są w stanie polepszyć użyteczność aplikacji i zachęcić mniej technicznych ludzi do pracy z komputerem. W wielkim skrócie Business Natural Languages w założeniu mają uprościć proces wytwarzania aplikacji z modelu Business --> Developers --> QA do Business --> Business przez zastosowanie Descriptive and maintainable phrases (DAMP) w taki sposób, by managerowie mogli sami definiować reguły aplikacji posługując się językiem naturalnym (prawie).

Kolejnym prelegentem był Pedro Sousa. Niestety jego acts_as_problem_solved była najsłabszą częścią dnia pierwszego, przynajmniej jeśli chodzi o część dotyczącą Ruby (o Pythonie się nie wypowiadam, bo nie uczestniczyłem w żadnej prezentacji go dotyczącej). Tak naprawdę taką prezentację mógłby wygłosić każdy, kto napisał 5 projektów w Railsach i korzystał z kilku pluginów. Niezadowolenie było widać na sali, część uczestników wyszła po kilku minutach. Ja zająłem się przeglądaniem Blipcasta.

Na zakończenie dnia pierwszego o cache-owaniu aplikacji Railsowych opowiadał Wiktor Schmidt z Netguru. Wszystko byłoby fajnie, gdyby nie trochę niedopracowany live-coding, prawdopodobnie przez świeżo nabytego MacBooka i małe doświadczenie w pracy z TextMatem.

Podsumowując, pierwszy dzień konferencji minął na ciekawych prezentacjach, szkoda, że nie dotarłem w końcu na RuPy Geek Party, choć, od znajomych wiem, że klub był trochę za mały na pomieszczenie konferencyjnej ekipy.

Tagi:

Google App Engine — oddaj głos na wsparcie dla Ruby

10 kwietnia 2008, 13:48:00

Poziom: 0 | Kategoria: Komputerowo-internetowo, Ruby, Techblog. | 20 komentarzy

Jak już zapewne większość z Was zauważyło, Google zrobiło konkurencję dla Amazon Web Services. Google App Engine jest póki co w fazie beta i na razie obsługuje jedynie aplikacje napisane w Pythonie, jednak niebawem ma się pojawić wsparcie dla innych języków. Dlatego też warto już teraz dodać gwiazdkę i skomentować chęć dołączenia języka Ruby do języków wspieranych przez Google App Engine. Można to zrobić komentując Issue 29. Tak więć "star" that ticket!

Tagi:

Modelujemy znajomych w Ruby on Rails

05 kwietnia 2008, 17:51:05

Poziom: 0 | Kategoria: Komputerowo-internetowo, Ruby, Ruby on Rails, Techblog. | 12 komentarzy

Ponieważ jestem w trakcie pisania małego serwisu społecznościowego w ramach pracy dyplomowej, nadszedł czas na wymodelowanie sieci znajomych. Przeglądałem trochę książek, szukałem trochę po przeróżnych blogach. W końcu stworzyłem hybrydę z rzeczy, które znalazłem.

Piszę w Railsach 2.0.2, chciałem więc w pełni wykorzystać REST. Pierwsze pytania pojawiły się już na poziomie projektowania bazy danych: czy lepiej podwajać krotki i trzymać w nich jakieś dodatkowe atrybuty? Czy może lepiej oszczędzić miejsce redukując rozmiar tabeli do jednej krotki na znajomość kosztem bardziej złożonego zapytania? Stanęło na tym, że każda "znajomość" ma dwie krotki w tabeli pośredniej, a znajomych przeglądamy przeglądając atrubuty "friend_id" krotek wybranych po atrybucie "user_id". Zatem dostaliśmy relację wiele-wiele pomiędzy tabelą Users a nią samą. Nasz model usera zaczął wyglądać więc mniej więcej tak:

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :friendships, :dependent => :destroy
end

Model samej relacji "być znajomym" nabrał takiego kształtu:

# app/models/friendship.rb
class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, :class_name => 'User', :foreign_key => 'friend_id'
end

Przyjęliśmy, że każde "zaproszenie" może mieć jeden z trzech stanów:

Stan zaproszenia przechowywany więc jest w tabeli pośredniej relacji wiele-wiele. Chcemy również w łatwy sposób docierać do naszych znajomych z poziomu usera. Z pomocą przychodzi nam metoda has_many :through, którą wykorzystujemy w modelu User (dla uproszczenia stany zaproszeń wyrażane są za pomocą stringów, innym sposobem, w moim mniemaniu lepszym, jest użycie, zamiast napisów, np. jakiś stałych liczbowych):

# app/models/user.rb
class User < ActiveRecord::Base
# (...)  
  has_many :friendships, :dependent => :destroy
  
  has_many :friends,
    :through => :friendships,
    :conditions => ["status = ?", 'zaakceptowane']
  
  has_many :requested_friends,
    :through => :friendships,
    :source => :friend,
    :conditions => ["status = ?", 'wyslane']
  
  has_many :pending_friends,
    :through => :friendships,
    :source => :friend,
    :conditions => ["status = ?", 'oczekujace']
  
  has_many :any_friends,
    :through => :friendships,
    :source => :friend
# (...)
end

Teraz dobrze jest zapewnić jakieś metody do zarządzania znajomościami. W tym celu utworzymy metody klasy Friendship, które będą wysyłały, akceptowały i odrzucały zaproszenia do znajomości:

# app/models/friendship.rb
class Friendship < ActiveRecord::Base

  belongs_to :user
  belongs_to :friend, :class_name => 'User', :foreign_key => 'friend_id'
  
  validates_presence_of :user_id, :friend_id
  
  def self.exists?(user, friend)
    not find_by_user_id_and_friend_id(user, friend).nil?
  end
  
  def self.request(user, friend)
    unless user == friend or Friendship.exists?(user, friend)
      transaction do
        create(:user => user, :friend => friend, :status => 'wyslane')
        create(:user => friend, :friend => user, :status => 'oczekujace')
      end
    end
  end
  
  def self.accept(user, friend)
    transaction do
      accepted_at = Time.now
      accept_one_side(user, friend, accepted_at)
      accept_one_side(friend, user, accepted_at)
    end
  end
  
  def self.breakup(user, friend)
    transaction do
      destroy(find_by_user_id_and_friend_id(user, friend))
      destroy(find_by_user_id_and_friend_id(friend, user))
    end
  end
  
  private
  def self.accept_one_side(user, friend, accepted_at)
    request = find_by_user_id_and_friend_id(user, friend)
    request.status = 'zaakceptowane'
    request.accepted_at = accepted_at
    request.save!
  end
end

Jak widać kluczowe akcje modelu wywoływane są w bloku metody transaction. To zapewnia nam transakcyjne wykonanie kodu, co zabda o spójność danych i nie dopuści do powstania sytuacji, w której podczas wysyłania zaproszenia od użytkownika A do użytkownika B w bazie pojawi się tylko krotka (A,B,'wyslane').

Można powiedzieć, że model jest już gotowy, pora zatem na kontroler, który będzie oferował podzbiór standardowych akcji: create, index, show oraz destroy. Akcja index zajmie się wyświetlaniem listy znajomych danego użytkownika, show przekieruje nas na profil użytkownika, create będzie odpowiedzialna za wysłanie lub zaakceptowanie zaproszenia, a destroy pozwoli na usunięcie kogoś z listy znajomych, jak również anulowanie wysłanego zaproszenia czy odrzucenie zaproszenia od niechcianej osoby. Całość umieściłem za filtrem :login_required (korzystam z pluginu restful_authentication) aby zarządzanie znajomymi było dostępne tylko po zalogowaniu.

Poszczególne akcje kontrolera nie są jakoś bardzo wyszukane, dla przykładu wkleję akcję destroy:

# app/controllers/friendships_controller.rb
class FriendshipsController < ApplicationController
# (...)
  def destroy
    # current_user zwraca aktualnie zalogowanego uzytkownika
    @user = current_user # dbamy o to, aby nikt nie usunal za nas naszych znajomych
    @friend = User.find(params[:id]) rescue nil
    if @friend
      if @user.any_friends.include?(@friend)
        Friendship.breakup(@user,@friend)
        flash[:notice] = 'Zaproszenie zostało usunięte'
      else
        flash[:notice] = 'Nie ma takiego zaproszenia'
      end
    else
      flash[:notice] = 'Nie ma takiego użytkownika'
    end
    redirect_to user_friendships_url(@user)
  end
# (...)

Zasób Friendships zagnieżdzony został przeze mnie w zasobie Users, co zapewnia taki fragment routes.rb:

# config/routes.rb
# (...)
 map.resources :users do |u|
    u.resources :friendships
  end
# (...)

Wydaje mi się, że takie rozwiązanie jest jednym z najprostszych, a mimo to zapewnia nam podstawowe rzeczy, które mogą okazać się przydatne, np. to, kto był stroną wysyłającą zaproszenie, czy kiedy zostało ono zaakceptowane.

Tagi:

Wcześniejsze wpisy Nowsze wpisy