inf:ruby:rails_3
Table of Contents
1. Rails Environment and Configuration
1.2. Startup and Application Settings
Uruchamiając środowisko Railsów (na przykład za pomocą polecenia rails server), najpierw przetwarzane są trzy pliki:
boot.rb, konfiguruje Bundlera i ładuje ścieżki,
require 'rubygems' # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
application.rb, ładuje gemy, konfiguruje aplikację,
require File.expand_path('../boot', __FILE__) require 'rails/all' if defined?(Bundler) # If you precompile assets before deploying to production, use this line Bundler.require *Rails.groups(:assets => %w(development test)) # If you want your assets lazily compiled in production, use this line # Bundler.require(:default, :assets, Rails.env) end module TestApp class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] # Enable the asset pipeline config.assets.enabled = true # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' end end
environment.rb, uruchamia initializery.
# Load the rails application require File.expand_path('../application', __FILE__) # Initialize the rails application TestApp::Application.initialize!
1.2.1. application.rb
- Standardowo w pliku tym ładuje się Railsy w całości, poprzez
require 'rails/all'. Możliwe jest jednak wybranie tylko kilku komponentów:
require "active_model/railtie" require "active_record/railtie" require "action_controller/railtie" require "action_view/railtie" require "action_mailer/railtie" require "active_resource/railtie"
- Dla naszej aplikacji utworzony został osobny moduł, po to, by umożliwić uruchomienie kilku aplikacji Railsowych pod jednym procesem.
- Railsy ładują pluginy alfabetycznie.
- Railsowe skrypty generatorów korzystają z pewnych standardowych ustawień. Można to zmienić:
config.generators do |g| g.template_engine :haml g.test_framework :rspec, :fixture => false
1.2.2. Initializers
- W katalogu
config/initializersznaleźć można pliki konfiguracyjne dla naszej aplikacji. Są one ustawiane po załadowaniu wszystkich frameworków, pluginów i gemów (na przykład ustawień tych gemów). - Standardowo Railsy tworzą pięć initializerów:
- Backtrace Silencers - zmienia wygląd backtrace'ów, usuwając część linii.
- Inflections - W Railsach istnieje klasa o nazwie
Inflector. Jest ona odpowiedzialna za zamianę wyrazów z liczby pojedynczej do mnogiej i vice versa. Można tam dodać wyrazy, z którymiInflectorsobie nie radzi.
ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'pensum', 'pensa' end
- MIME types - można tu dodać nowe typy MIME, na które ma odpowiadać
respond_to. session_store.rb- w pliku ustawiony jest tajny klucz, służący do weryfikacji ciasteczek.session_store.rb- istnieje możliwość przechowywania sesji w bazie danych zamiast w ciasteczkach. W tym pliku można to skonfigurować.
1.2.3. Dodatkowa konfiguracja
5. Working with Active Record
5.3. Defining Attributes
5.3.1. Default Attribute Values
- Najczęściej, standardowe wartości atrybutów (kolumn) ustawia się w modelu. (można również w migracji - opcja
:defaultmetodycolumn) - Instancje modeli mają kilka metod (
write_attributeiread_attribute) aby nadpisywać standardowe akcesory.
Zwracanie kategorii, a w gdy jest ona nil, zwrócenie n/a:
class TimesheetEntry < ActiveRecord::Base def category read_attribute(:category) || 'n/a' end end
5.3.2. Serialized Attributes
- Jest możliwość oznaczenia kolumny
textbazy danych jako serializowanej. Jakikolwiek obiekt zostanie przypisany atrybutowi tej kolumny, będzie zapisany w bazie jako YAML. - Kolumna o typie
textma maksymalny rozmiar 64K. - Często przechowuje się za pomocą takiego mechanizmu hash obiektów związanych z ustawieniami użytkownika (po co tworzyć na to osobną tabelę?)
class User < ActiveRecord::Base serialize :preferences, Hash end
- Dostęp do listy serializowanych atrybutów kontrolowany jest poprzez
serialized_attributes - Aby ustawić wartość domyślną atrybutów serializowanych, musimy uciec się do przeładowania akcesora atrybutu, tak by ustawiał on wartość domyślną, gdy atrybut jest
nil:
def preferences read_attribute(:preferences) || write_attribute(:preferences, {}) end
5.4. CRUD: Creating, Reading, Updating, Deleting
5.4.1. Creating New Active Record Instances
- Obiekty mogą być instancjonowane jako puste lub z ustawionymi już atrybutami, ale jeszcze nie zapisane do bazy:
>> c = Client.new => #<Client id: nil, name: nil, code: nil> >> c.new_record? => true >> c.persisted? => false
- Można od razu zapisywać obiekty do bazy danych za pomocą metody
create:
c = Client.create(:name => "sQbase", :code => "SQB")
5.4.2. Reading Active Record Objects
# Pobranie obiektu o danym id first_girl = Girl.find(1) # Pobranie ostatniego obiektu last_girl_standing = Girl.last # Pobranie wszystkich (w00t!) dziewcząt sqbells_life = Girl.all # Przedziały również i tu działają v3rts_life = Girl.find([1, 2])
5.4.3. Reading and Writing Attributes
- Po znalezieniu obiektu, do jego atrybutów dostać się można na kilka sposobów:
>> first_girl.name => "Antonina Louis"
- Możemy łatwo modyfikować zwracane wartości:
def name read_attribute(:name).reverse end # irb >> first_girl.name => "siuoL aninotnA" # trzeba uważać na nieskończoną rekurencję! def name self.name.reverse end # irb >> first_girl.name SystemStackError: stack level too deep [...]
- Do atrybutów dostać można się również za pomocą notacji hashowej:
first_girl['name'] first_girl[:name]
- Wiele metod Railsowych akceptuje parametry w postaci symbolu i łańcucha znaków. Ogólna reguła mówi, że z symboli korzystamy, gdy chodzi o nazwę czegoś a z łańcucha znaków w przypadku jakichś wartości.
- Istnieje również metoda
attributes, zwracająca Hash wszystkich atrybutów. Przydaje się to, gdy chcemy na przykład przesłać je wszystkie za jednym razem innej funkcji. Należy pamiętać, że modyfikacja Hasha nie wpłynie na atrybuty obiektu rodzimego.
>> first_girl.attributes => {"name"=>"Antonina Louis", "phone"=>"n/a", "id"=>1} >> atts = first.girl.attributes >> atts["name"] = "Kasia Vitton" => "Kasia Vitton" >> first_girl.attributes => {"name"=>"Antonina Louis", "phone"=>"n/a", "id"=>1}
5.4.4. Accessing and Manipulating Attributes Before They Are Typecast
- Czasem chcemy uzyskać dostęp do surowych danych, pobranych bezpośrednio z bazy danych, bez rzutowania. Możemy tego dokonać za pomocą akcesorów
attribute_before_type_cast
5.4.7. Dynamic Attribute-Based Finders
- W celu ułatwienia poszukiwań, istnieją dynamicznie generowane metody
find_by_ifind_all_by_, po których następuje nazwa kolumny, wedle której chcemy szukać rekordu. - Można szukać również wedle wielu kolumn.
Girl.find_by_height(176) Girl.find_by_height_and_weight(176, 60) # to samo, z pomocą klauzuli where Girl.where("height = ? AND weight = ?", height, weight)
- Metody można również łączyć z zakresami (scopes) i relacjami. Metody muszą wtedy być na końcu zapytania.
Girl.order("cuteness").find_all_by_height(176)
- Metoda
find_or_create_by_, która zwróci obiekt, jeśli on istnieje, a jeżeli nie, utworzy go.
Girl.find_or_create_by_height_and_weight_and_name("176", "60", "Zuzanna")
- Jeśli chcemy znaleźć lub utworzyć obiekt, ale nie zapisywać go, używamy metody
find_or_initialize_by_ - Wszystkie metody
find_są niekompatybilne z Arelem i dlatego coraz rzadziej używane.
5.4.8. Dynamic Scopes
- Dynamiczne zakresy są podobne do dynamicznych metod z rozdziału 5.4.7. Są jednak kompatybilne z Arelem i dlatego preferowane w aplikacjach wykorzystujących Rails 3.
- Zakresy są generowane w razie potrzeby.
Girl.scoped_by_height("176").order(:cuteness) # irb >> Girl.methods.include?("scoped_by_height") => true >> Girl.methods.include?("scoped_by_weight") => false
5.4.9. Custom SQL Queries
- Metoda
find_by_sqlumożliwia otrzymanie obiektów za pomocą bezpośredniego zapytania SQL. Wykorzystywanie SQL'a w aplikacjach Railsowych jest jednak bardzo odradzane. - Podobnie jest z metodą
count_by_sql, która zwraca liczbę rekordów w bazie danych odpowiadającej zapytaniu.
5.4.10. Query Cache
- Railsy próbują optymalizować swoją wydajność poprzez keszowanie zapytań. Jest to hash przechowywany w obecnym wątku, jeden dla każdego połączenia z bazą danych. Gdy korzystamy z metod find (lub jakichkolwiek innych, powodujących zapytania SELECT) wyniki zapisywane są w tym hashu razem z zapytaniem SQL. Jeśli takie samo zapytanie zostanie wysłane ponownie, (w krótkim, jak mniemam, odstępie czasu), to zamiast ponownie pytać serwer bazy danych, dane zostaną pobrane z hasha.
- Można ręcznie uaktywnić keszowanie zapytań za pomocą bloku
cache:
User.cache do puts User.first puts User.first puts User.first end # development.log User Load (1.0ms) SELECT * FROM users LIMIT 1 CACHE (0.0ms) SELECT * FROM users LIMIT 1 CACHE (0.0ms) SELECT * FROM users LIMIT 1
- Operacje zapisu i usuwania rekordów powodują wyczyszczenie keszu.
- Za pomocą metody
clear_query_cachemożna zrobić to ręcznie. - Domyślnie, kesz zapytań Active Recordu jest włączony dla przetwarzania akcji kontrolera.
- Różne zapytania zwracające te same wyniki są jednak keszowane jako osobne.
SELECT foo FROM bar WHERE id = 1 SELECT foo FROM bar WHERE id = 1 LIMIT 1
- Powyższe zapytania uznane zostaną jako dwa osobne.
5.4.13. Updating a Particular Instance
- Najlatwiej uaktualnic obiekt AR poprzez zmianę jego atrybutów a następnie wywołanie metody
save. - Metoda
savewstawi rekord, jeśli on nie istnieje, lub uaktualni jego atrybuty, jeśli istnieje. - Metoda
savezwrócitrue, gdy operacja się powiedzie ifalsew wypadku przeciwnym. - Istnieje również metoda
save!, która korzysta z wyjątków.
5.4.14. Updating Specific Attributes
- Metody
update_attributeiupdate_attributesuaktualniają wartości, jednocześnie zapisując zmiany do bazy danych. - Obiekt uaktualniany za pomocą metody
update_attributenie podlega walidacji. Można doprowadzić do sytuacji, gdy nieprawidłowy obiekt zostanie zapisany w bazie. Callbacki również są pomijane.
5.4.15. Convenience Updaters
- Railsy oferują kilka wygodnych metod, takich jak:
decrement,increment,toggle. Każda posiada wersję z wykrzyknikiem, która dodatkowo uruchamiaupdate_attribute.
5.4.16. Touching Records
- Czasem chcemy tylko “dotknąć” rekordu by uaktualnić daty w polach, żeby wskazać, że rekord był wyświetlany. Korzystamy wtedy z metody
touch. Gdy wywołamy ją bez żadnych parametrów, uaktualnione zostanie poleupdated_at, bez uruchamiania jakiejkolwiek walidacji czy callbacków. Jeśli podamy nazwę kolumny jako parametr, uaktualniona zostanie również i ona. - Opcję
:touch ⇒ truemożna dodać do asocjacjibelongs_to. Wtedy, gdy obiekt dziecko zostanie “dotknięty”, daty uaktualniane będą również u rodzica.
class User < ActiveRecord::Base belongs_to :client, :touch => true end # irb >> user.touch #=> user.client.touch jest również uruchamiane
5.4.17. Controlling Access to Attributes
- Konstruktory i metody
update, które przyjmują hashe do wykonania masowego przypisania atrybutów są podatne na ataki hakerów, gdy korzysta się z nich w połączeniu z hashemparamsw metodach kontrolera. - Chcąc chronić atrybuty danej klasy, mamy do dyspozycji dwie metody, które kontrolują dostęp do tych atrybutów:
attr_accessibleprzyjmuje jako argument listę atrybutów, które będą dostępne do masowego przypisania.attr_protecteddziała odwrotnie. Atrubuty podane metodzie jako argumenty będą chronione przed masowym przypisaniem.
class Girl < ActiveRecord::Base attr_protected = :phone_number end girl = Girl.new(:name => "Zuzanna", :phone_number => "605 945 344") girl.phone_number # => nil girl.attributes = { "phone_number" => "605 945 344" } girl.phone_number # => nil # atrybut można ustawić tylko tak girl.phone_number = "605 945 344" girl.phone_number # => "605 945 344"
5.4.18. Readonly Attributes
- Atrybuty ustawione jako tylko do odczytu, mogą być ustawiane tylko wtedy, gdy obiekt nie jest jeszcze zapisany
- W przypadku próby nadpisania takiego atrybutu po zapisaniu, wartość pozostaje niezmieniona. Nie jest również wyświetlany komunikat o błędzie.
- Listę takich atrybutów można otrzymać za pomocą metody
readonly_attributes.
class Girl < ActiveRecord::Base attr_readonly :annoying_level end
5.4.19. Deleting and Destroying
- Chcąc usunąć rekord z bazy danych mamy dwie możliwości:
delete- używa SQL'a bezpośrednio, nie ładuje obiektu przez co jest szybsza.destroy- ładuje obiekt, następnie go usuwa i uniemożliwia jego edycję. Gdy chcemy wykorzystać callbackbefore_destroylub gdy mamy jakieś zależności - obiekty dzieci, które powinny być usunięte wraz z usunięciem rodzica.
- Próba zapisania obiektu po jego usunięciu metodą
destroyskutkuje cichym błędem. Aby sprawdzić, czy dany obiekt jest usunięty, korzystamy z metodydestroyed?. - Metody mogą być również używane jako metody klasy, podając jako parametr id lub ich tablicę.
Girl.delete(1) Girl.delete([2, 3])
5.5. Database Locking
- Gdy istnieje możliwość, że te same dane będą modyfikowane przez więcej niż jednego użytkownika, mogą pojawić się kolizje,
- dwa podejścia do unikania kolizji są zaimplementowane w
ActiveRecord:- blokada optymistyczna (ang. optimistic locking),
- blokada pesymistyczna (ang. pesimistic locking),
- istnieją inne rozwiązania, jak zablokowanie całych tabel.
5.5.1. Optimistic Locking
- Strategia opiera się na wykrywaniu i rozwiązywaniu kolizji jeśli się one pojawią,
- zalecana w sytuacjach gdy kolizje będą pojawiały się rzadko,
- pomimo nazwy, dane nie są nigdy blokowane,
- aby umożliwić optymistyczne blokowanie, należy dodać kolumnę
integero nazwielock_versiondo danej tabeli, ze standardową wartością0, - jeśli ten sam rekord zostanie załadowany w dwóch różnych instancjach modeli i różnie zapisany, pierwsza instancja wygra uaktualnienie a druga podniesie wyjątek
ActiveRecord::StaleObjectError, - wyjątek trzeba obsłużyć, na przykład tak:
def update user = User.find(params[:id]) user.update_attributes(params[:timesheet]) rescue ActiveRecord::StaleObjectError flash[:error] = "User data was modified while you were editing it." redirect_to [:edit, user] end
- pesymistyczna blokada jest prosta do zaimplementowania, nie wymaga specjalnych funkcjonalności od bazy danych, jest jednak wolniejsza, ponieważ
lock_versionmusi zostać sprawdzone. Kolejnym minusem jest kiepskie doświadczenie użytkownika, ponieważ dowie się on o problemie po utracie danych.
5.5.2. Pessimistic Locking
- Pesymistyczne blokowanie wymaga specjalnych funkcji od bazy danych,
- blokuje konkretne wiersze w bazie danych podczas operacji uaktualniania danych, uniemożliwiając innemu użytkownikowi ich odczyt,
- współpracuje z transakcjami:
User.transaction do user = User.lock.first user.admin = true user.save! end
- można zamknąć istniejącą już instancję modelu za pomocą metody
lock!, która tak naprawdę wykonujereload(lock: true), a więc nie powinno się tego robić na instancji do której wprowadzono zmiany, bo zostaną one odrzucone. Aby usunąć blokadę:lock!(false), - hipotetycznie istnieją sytuacje, w których (na przykład z powodu zawieszenia procesu Railsów) blokada nie zostanie usunięta aż do czasu zakończenia lub time-outu połączenia z bazą danych.
5.6. Where Clauses
5.6.1. where (*conditions)
- warunki mogą być przekazane jako string lub hash,
- parametry są automatycznie sprawdzane pod kątem wstrzykiwania SQL:
# Hash style User.where(name: "Marek") User.where(name: ["Marek", "Jarek", "Darek"]) # String style User.where('name LIKE ? AND surname = ?', "%#{terms}%", surname) User.where('name IN (?)', ["Marek"], "Jarek", "Darek") # Bind variables User.where("name = :name AND surname = :surname", name: "Jarek", surname: "Gruk") # Można wykorzystać zmienną wielokrotnie User.where("name LIKE :name OR surname LIKE :name", name: '%are%') # Jeśli potrzebujemy tylko sprawdzania równości z wykorzystaniem SQL-owego AND User.where(name: name, surname: surname).first
- wartości boolowskie są zapisywane różnie przez różne bazy danych (niektóre mają natywnie wartości boolowskie, inne korzystają z pojedynczego znaku
T,F,0,1). Railsy transparentnie poradzą sobie z tym problemem, jeśli podamy w warunku Rubowetruelubfalse, - trzeba być ostrożnym, przekazując
niljako zmienną:> User.where('email = ?', nil) => SELECT * FROM users WHERE (email = NULL) > User.where(email: nil) => SELECT * FROM users WHERE (users.email IS NULL)
5.6.2. order(*clauses)
- Metoda przyjmuje jeden lub więcej symboli (reprezentujących kolumny), bądź fragmenty SQL'a:
User.order(:height) User.order('height asc')
- przyjmuje opcję
ascendingjeśli się jej nie zdefiniuje lub skorzysta z symbolu, - w przypadku gdy nie umieścimy omawianej klauzuli, dostaniemy dane w “jakimś” porządku (niekoniecznie uporządkowanych po id).
5.6.3. limit(number) i offset(number)
User.limit(10).offset(10)
5.6.8. includes(*associations)
- Eliminuje problem “N+1” zapytań, “chętnie” ładując zależności (eager loading):
User.includes(phones: [:country_codes, {region_codes: :regions}]
5.6.10. readonly
- Ustawia zwracane obiekty w trybie “tylko do odczytu”. Można zmieniać ich atrybuty, ale zmiany nie zostaną zapisane w bazie danych.
5.6.11. exists?
- Sprawdza, czy dany rekord znajduje się w bazie danych, zwracając
true/false:User.exists?(height: 176) # lub User.where(height: 176).exists?
5.6.12. arel_table
- Można dostać się bezpośrednio do tabeli danej klasy za pomocą
arel_table, w celu wygenerowania własnego SQL'a poprzez Arel:users = User.arel_Table users.where(users[:height].eq(176))
7. Active Record Associations
7.1. The Association Hierarchy
- Asocjacje pojawiają się zazwyczaj jako metody modelu. Ukrywają się jednak często jako zwykłe obiekty Rubiego.
user.addresseszwróci nam obiekt klasyArray, ale tak naprawdę to instancjeAssociationProxylub którejś z jej podklas (przykładowo dlahas_manybędzie toHasManyAssociation),
7.2. One-to-Many Relationships
«(*records)icreate(attributes = {})dodadzą jeden lub kilka obiektów do właściciela kolekcji, zachowują się jednak inaczej.«jest transakcjonalny acreatenie.«wywołuje callbacki (before_addiafter_add) acreatenie. Zwracają też różne rzeczy -«zwraca tablicę lubfalsegdy nie uda mu się dodać jakiegoś elementu,createzwraca nowo utworzoną instancję,
9. Advanced Active Record
9.1. Scopes
- Zakresy umożliwiają definiowanie i łączenie kryteriów zapytań w sposób umożliwiający zapisanie i ponowne ich użycie.
- Wcześniej (Rails 2, sprawdzić!)
named_scope, terazscope.
class Timesheet < ActiveRecord::Base scope :submitted, where(:submitted => true) scope :underutilized, where('total_hours < 40')
- Jeśli już przed uruchomieniem znane są wszystkie parametry, można korzystać z Arelowskich
where,order,limit. Jeśli nie znamy wszystkich parametrów dopóki nie uruchomimy aplikacji, należy jako drugi parametr podać lambdę. Będzie wtedy przeliczana z każdym jej wywołaniem.
class User < ActiveRecord::Base scope :delinquent, lambda{where('timesheets_updated_at < ?', 1.week.ago)}
- Korzystać z takiego zakresu możemy jak ze zwykłą metodą:
User.delinquent.
9.1.1. Scope Parameters
13. Session Management
- HTTP jest protokołem bezstanowym.
- Bez sesji niemożliwym byłoby powiązanie dwóch żądań HTTP. Konieczne byłoby uwierzytelnianie na każdej stronie.
- W Railsach, każdemu nowemu użytkownikowi nowa sesja przypisywana jest automatycznie. Przy wizycie, ciasteczko z ID sesji wysyłane jest przeglądarce.
sqbell@sqbell-netbook:~$ curl 10.0.0.10:3000 -I HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 X-Ua-Compatible: IE=Edge Etag: "492b3c7c9fd2d950e1f2ada5a46651b3" Cache-Control: max-age=0, private, must-revalidate X-Runtime: 0.100905 Content-Length: 0 Server: WEBrick/1.3.1 (Ruby/1.9.2/2011-07-09) Date: Fri, 30 Sep 2011 15:45:08 GMT Connection: Keep-Alive Set-Cookie: _depot_session=BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJWE1N2I1NWQ0NTdkY2UzN2E2MTkxNTM4ZDRlZTE5Zj Y0BjsAVEkiDGNvdW50ZXIGOwBGaQZJIhBfY3NyZl90b2tlbgY7AEZJIjFxeE1DOHhBMFVqN2RjMGZ0NCtOdWp4blRrelRSQUJHSW1 zY3NuRGdqT1g4PQY7AEY%3D--9cddc91db772a3ab182baf74f504ed390a165569; path=/; HttpOnly
- Od tego momentu, każde żądanie wysłane przez przeglądarkę będzie zawierać taki ID.
- Dobrą praktyką Rails jest przechowywanie jak najmniej danych za pomocą sesji.
13.1. What to Store in the Session
- Liczby, krótkie ciągi znaków można przechowywać w sesji, obiektów natomiast nie powinno się w sesji umieszczać.
- Większość aplikacji Railsowych przechowuje w sesji
current_user_id, ID obecnie zalogowanego użytkownika. - Nie powinno się przechowywać krytycznych danych w sesji. Użytkownik w każdej chwili może zamknąć przeglądarkę, lub wyczyścić ciasteczka, kasując tym samym sesję.
- Od wersji 3, sesje są leniwie ładowane, co oznacza, że jeśli nie odnosimy się do nich w kontrolerze, nie są ładowane i nie spowalniają działania aplikacji.
13.3. Storage Mechanisms
- Istnieją różne mechanizmy przechowywania sesji. Domyślnym dla Railsów jest zapisanie sesji w ciasteczku, co jednocześnie ogranicza ilość danych do 4KB.
- Wiąże się to jednocześnie z pewnymi względami bezpieczeństwa (session-reply attacks - http://en.wikipedia.org/wiki/Replay_attack).
13.3.1. Active Record Session Store
- Sesje można przechowywać w bazie danych.
- Odpowiednią tabelę można utworzyć za pomocą wygenerowanej poleceniem:
rake db:sessions:createmigracji. - Trzeba również dodać linię do pliku
config/initializers/session_store.rb:
MyApplication::Application.config.session_store :active_record_store
13.3.2. Memcache Session Storage
17. Caching and Performance
17.1. View Caching
Mamy trzy rodzaje cache'owania widoków:
- page caching, gdzie wszystko co zwróci dana akcja kontrolera zostanie zapisane a późniejsze żądania nie będą wymagały jakiejkolwiek reakcji ze strony Railsów,
- action caching, podobnie jak w przypadku page caching, ale Railsy są już zaangażowane w obsługę żądań, filtry w kontrolerach są uruchamiane,
- fragment caching, różne części strony są zapisywane.
17.1.2. Page Caching
- Uruchamiany za pomocą metody makra
caches_page, - dla Railsów wygląda to jak statyczna strona HTML w katalogu
public.
17.1.3. Action Caching
- Uruchamiany za pomocą metody
caches_action, - prawie taki sam jak page caching, ale uruchamia filtry kontrolerów,
- zaimplementowane z wykorzystaniem technik Fragment Caching oraz filtra
around_filter, - zawartość jest zapisywana w pamięci podręcznej na podstawie klucza opartego na hoście i ścieżce,
- różne formaty danych (XML, JSON, HTML) są traktowane jako osobne żądania i są przechowywane w pamięci podręcznej osobno.
17.1.4. Fragment Caching
- Umożliwia zapisanie części renderowanej strony i serwowanie ich dla przyszłych żądań bez konieczności ich ponownego renderowania,
- ustawiany na poziomie widoku:
<% cache do %> <%= render 'entry', collection: @entries %> <% end %>
- konieczne jest podawanie nazwy danego fragmentu, jeśli mamy więcej fragmentów w danym widoku, ponieważ fragmenty są identyfikowane na podstawie hosta i ścieżki:
<% cache(fragment: 'entries') do %> ...
- istnieje możliwość utworzenia globalnych fragmentów, tj. nie przypisanych do jednego adresu URL. Jest to przydatne, gdy dany fragment pojawia się w kilku widokach różnych akcji różnych kontrolerów:
<% cache(@user.name + "_stats") do %> ...
- gdy dany fragment jest już dodany do pamięci podręcznej, nie ma potrzeby wysyłania żądań do bazy danych dla tego fragmentu. Korzystamy z metody
fragment_exists?:def index unless fragment_exists?(fragment: 'entries') @entries = Entry.all.limit(10) end end
17.1.5. Wygasanie zawartości pamięci podręcznej
- Przechowując zawartość w pamięci podręcznej, trzeba rozważyć wszystkie możliwości, które sprawią, że ta zawartość nie będzie aktualna,
- metody
expire_pageiexpire_actionusuwają zawartość pamięci podręcznej, która będzie zregenerowana przy następnym żądaniu, należy pamiętać o poprawnej identyfikacji zawartości:# Zazwyczaj ''update'' i ''destroy'' również dezaktualizują zawartość pamięci podręcznej def create ... if @entry.save expire_page action: 'public' redirect_to entries_path(@entry) end ... end
- różne reprezentacje tego samego zasobu (HTTP, XML, etc) wymagają również osobnego czyszczenia:
expire_page(action: 'public', format: :xml), - aby usunąć fragment, należy skorzystać z metody
expire_fragment:expire_fragment(fragment: 'entries')
- jeżeli została zastosowana paginacja, lub zasoby są bardziej skomplikowane, konieczne być może zastosowanie wyrażeń regularnych, należy jednak pamiętać, że nie działają one z
Memcached:expire_Fragment(%r{entries/.*})
17.1.6. Automatic Cache Expiry with Sweepers
- Klasa
Sweeperto swego rodzajuObserver, wyspecjalizowany w czyszczeniu zawartości pamięci podręcznej, - pisząc zamiatacza, określamy listę modeli, jakie ma obserwować:
class EntrySweeper < ActionController::Caching::Sweeper observe Entry def expire_cached_content(entry) expire_page controller: 'entries', action: 'public' expire_fragment(%r{entries/.*}) expire_Fragment(fragment: (entry.user.name + "_stats")) end alias_method :after_save, :expire_cached_content alias_method :after_destroy, :expire_cached_content end
- tak napisanego zamiatacza musimy podać w kontrolerze:
class EntriesController < ApplicationController ... cache_sweeper :entry_sweeper, only: [:create, :update, :destroy] ... end
17.1.9. Cache Storage
Mamy do wykorzystanie trzy różne opcje przechowywania pamięci podręcznej:
ActiveSupport::Cache::FileStore- przechowuje zawartość na dysku, udostępnia fragmenty wszystkim procesom,ActiveSupport::Cache::MemoryStore- przechowuje fragmenty w pamięci, w efekcie czego może konsumować bardzo duże ilości pamięci, w przypadku kiepskich strategii wygaśnięcia,ActiveSupport::Cache::MemCacheStore- przechowuje fragmenty za pomocąMemcached, opcja uznawana za najlepszą.
17.2. General Caching
- Zawsze można korzystać z ustawionego mechanizmu pamięci podręcznej:
> Rails.cache.write(:actress, "Anne Hathaway") => true > Rails.cache.read :actress => "Anne Hathaway"
17.2.1. Eliminating Extra Database Lookups
- Często korzysta się z pamięci podręcznej do przechowywania częstych zapytań do bazy danych. Poniżej, metoda
fetchnajpierw sprawdza w pamięci podręcznej, a dopiero później pyta bazę danych:class User < ActiveRecord:Base def self.fetch(id) Rails.cache.fetch("user_#{id}") { User.find(id) } end def after_save Rails.cache.write("user_#{id}", self) end def after_destroy Rails.cache.delete("city_#{id}") end end
17.3. Control Web Caching
- Dostępne są metody do kontroli nagłówków HTTP 1.1
Cache-Control, - standardowo, ustawiana jest instrukcja
private, która mówi, że żądanie jest przeznaczone dla konkretnego użytkownika i tym samym nie powinno być zapisane w pamięci podręcznej pośredników (web proxies), - ustawienie
publicumożliwia pośrednikom przechowywanie zawartości w pamięci podręcznej, nie powinno być stosowane w przypadku stron, na których istnieją elementy dla konkretnego użytkownika, expires_in- metoda nadpisuje nagłówekCache-Control:expires_in 3.hours, 'max-stale' => 5.hours, public: true
expires_nowustawia nagłówekCache-Controlnano-cache, czyli bez zachowywania w pamięci podręcznej.
17.4. ETags
- ETagi, entity tags, to technika, dzięki której nie trzeba w ogóle wysyłać zawartości do klienta, jeśli od ostatniej wizyty nic na serwerze się nie zmieniło,
- poprawnie zaimplementowane, są jedną z najlepszych technik zwiększających wydajność na stronach o dużym ruchu,
- standardowo, renderowanie automatycznie dodaje nagłówek
ETagdo każdej200 OKodpowiedzi. Wartość tej zmiennej to hash MD5 ciała odpowiedzi. Jeśli następne żądanie będzie miało taki samEtag, odpowiedzią będzie304 Not Modified, a ciało odpowiedzi będzie puste, RFC 2616zaleca wysyłanie zarówno silnegoETagjak i wartościLast-Modified. Ta ostatnia nie jest dodawana przez Railsy automatycznie,- metoda
fresh_whenustawiaETagi/lubLast-Modifiedi renderuje odpowiedź304 Not Modifiedjeśli żądanie jest “świeże”. Świeżość jest obliczna za pomocą metodycache_keyobiektu lub kolekcji obiektów przekazywanej metodzie jako opcja:etag:def show @article = Article.find(params[:id]) fresh_when(etag: @article, last_modified: @article.created_at.utc, public: true) end
- metoda
stale?ustawiaETagi/lubLast-Modifiedodpowiedzi i porównuje je z żądaniem klienta (wykorzystując metodęfresh_When). Jeśli nie pasują do siebie, żądanie jest określane jako nieświeże i powinno być wygenerowane od nowa. Ta metoda jest wykorzystywana zamiastfresh_when, jeśli dodatkowa logika jest potrzebna w kontrolerze do renderowania widoku.
inf/ruby/rails_3.txt · Last modified: 2021/02/16 09:56 by 127.0.0.1
