Merb fixtures
30 lipca 2008Ostatnio szukałem jakiegoś dobrego narzędzia do fixtures (nie mam pojęcia jak to można po polsku nazwa) w Merbie i znalazłem projekt na githubie merb-fixtures. Niestety okazało się, że sprawia pewne problemy w szczególności z dm-is-nested_set z paczki dm-more DataMappera. Jednak postanowiłem się nie poddawać i korzystając z kodu merb-fixtures napisać coś własnego.
Cała zawartość pliku fixtures.rb prezentuje się tak:
module Fixtures class Manager class << self def init @@fixtures = {} end def load Merb.logger.debug("Loading fixtures ...") unless Merb.env?("production") directory = Merb.root / "app" / "fixtures" if File.exist?(directory) Dir[directory / "*.rb"].each do |file| Kernel.load(file) end else raise 'FixturesDirectoryNotFound' end end end def create(klass, name, &block) unless object = klass.fixture(name) object = klass.new @@fixtures[klass] << {:name => name, :object => object} end object.instance_eval(&block) if block_given? object.save! if object.dirty? object end def fixture(klass, name) if fix = (@@fixtures[klass] ||= []).find {|e| e[:name] == name} fix[:object] end end def fixtures(klass) @@fixtures[klass] end def delete_fixtures(klass) @@fixtures[klass] = [] end def save_all @@fixtures.each_pair do |_, fixtures| fixtures.each {|f| f[:object].save} end end end end module Extensions def self.included(base) base.extend(ClassMethods) end module ClassMethods def fixture(name) Manager.fixture(self, name) end def fixtures Manager.fixtures(self) end def delete_fixtures Manager.delete_fixtures(self) end end end end models = [] ObjectSpace.each_object(Class) do |klass| if klass.included_modules.include?(DataMapper::Resource) klass.send(:include, Fixtures::Extensions) end end module Kernel def fixture_for(klass, name, &block) Fixtures::Manager.create(klass, name, &block) end end
Teraz krótko o tym jak się tego używa.
Po pierwsze wrzucamy plik fixtures.rb do katalogu lib i dodajemy w pliku config/init.rb
Merb::BootLoader.after_app_loads do require 'lib/fixtures.rb' end
Jest to bardzo istotne aby umieścić require w tym bloku ponieważ biblioteka wymaga dostępu do klas modeli.
Następnie tworzymy katalog app/fixtures i w nim dla przykładu plik category.rb o zawartości:
fixture_for(Category, :root) do self.name = 'root' end fixture_for(Category, :space_ships) do self.name = 'Space ships' self.parent = Category.fixture(:root) end fixture_for(Category, :fast_space_ships) do self.name = 'Fast ships' self.parent = Category.fixture(:space_ships) end fixture_for(Category, :big_space_ships) do self.name = 'Big ships' self.parent = Category.fixture(:space_ships) end fixture_for(Category, :robots) do self.name = 'Robots' self.parent = Category.fixture(:root) end fixture_for(Category, :human_like_robots) do self.name = 'Human like' self.parent = Category.fixture(:robots) end fixture_for(Category, :other_robots) do self.name = 'Other' self.parent = Category.fixture(:robots) end
Teraz możemy załadować (poprzez merb -i) dane do bazy za pomocą Fixtures::Manager.load. Jednak najbardziej przydanym wykorzystaniem fixtures są testy...
W pliku spec/spec_helper.rb dodajemy:
DataMapper.auto_migrate! Fixtures::Manager.init def fixtures(*classes) Fixtures::Manager.load classes.each {|klass| klass.fixtures.each { |e| instance_variable_set("@#{e[:name]}", e[:object].clone) } } end
Teraz w pliku category_spec.rb:
before(:each) do fixtures(Category) # można załadować kilka na raz np. # fixtures(Category, Product, User) end
Da nam to nie tylko dostęp do obiektów za pomocą Klasa.fixture(:nazwa), ale też @nazwa (w tym przypadku np.@space_ships)
Nie pozostaje nic innego tylko zabrać się do wygodnego pisanie testów ;)
P.S. Znowu wyszła tona kodu + dwa zdania...
1 komentarz
Przyda się, wielkie dzięki. Oryginalne merb-fixtures nie dość, że sprawia kłopoty to przeraża ilością plików. Teraz wypadało by ukulturalnić i opisać RSpecowanie kontrolerów.
PS. Nie istniej chyba tłumaczenie słowa "fixture" na język polski sensowne w tym kontekście.