diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2007-10-26 05:56:46 +0000 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2007-10-26 05:56:46 +0000 |
commit | 49eafd8c3620bf8e46d21d447fc634a12c8280ab (patch) | |
tree | 5e76c1587d3e3db619944091923659846ecede51 | |
parent | 742694e0eb1abd049aa2a4eadbc7a93591c65db4 (diff) | |
download | rails-49eafd8c3620bf8e46d21d447fc634a12c8280ab.tar.gz rails-49eafd8c3620bf8e46d21d447fc634a12c8280ab.tar.bz2 rails-49eafd8c3620bf8e46d21d447fc634a12c8280ab.zip |
Foxy fixtures. Adapter#disable_referential_integrity. Closes #9981.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8036 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
-rw-r--r-- | activerecord/CHANGELOG | 9 | ||||
-rwxr-xr-x | activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 7 | ||||
-rwxr-xr-x | activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 12 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb | 8 | ||||
-rwxr-xr-x | activerecord/lib/active_record/fixtures.rb | 310 | ||||
-rw-r--r-- | activerecord/test/associations/eager_test.rb | 4 | ||||
-rwxr-xr-x | activerecord/test/associations_test.rb | 2 | ||||
-rw-r--r-- | activerecord/test/fixtures/db_definitions/schema.rb | 29 | ||||
-rw-r--r-- | activerecord/test/fixtures/parrot.rb | 4 | ||||
-rw-r--r-- | activerecord/test/fixtures/parrots.yml | 16 | ||||
-rw-r--r-- | activerecord/test/fixtures/parrots_pirates.yml | 7 | ||||
-rw-r--r-- | activerecord/test/fixtures/pirate.rb | 4 | ||||
-rw-r--r-- | activerecord/test/fixtures/pirates.yml | 9 | ||||
-rw-r--r-- | activerecord/test/fixtures/treasure.rb | 3 | ||||
-rw-r--r-- | activerecord/test/fixtures/treasures.yml | 8 | ||||
-rwxr-xr-x | activerecord/test/fixtures_test.rb | 83 |
16 files changed, 497 insertions, 18 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index e2cf060525..af436232f2 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,14 @@ *SVN* +* Foxy fixtures, from rathole (http://svn.geeksomnia.com/rathole/trunk/README) + - stable, autogenerated IDs + - specify associations (belongs_to, has_one, has_many) by label, not ID + - specify HABTM associations as inline lists + - autofill timestamp columns + - support YAML defaults + - fixture label interpolation + Enabled for fixtures that correspond to a model class and don't specify a primary key value. #9981 [jbarnette] + * Add docs explaining how to protect all attributes using attr_accessible with no arguments. Closes #9631 [boone, rmm5t] * Update add_index documentation to use new options api. Closes #9787 [kamal] diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 0741a47cc2..da3c9ad7bd 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -70,6 +70,13 @@ module ActiveRecord name end + # REFERENTIAL INTEGRITY ==================================== + + # Override to turn off referential integrity while executing +&block+ + def disable_referential_integrity(&block) + yield + end + # CONNECTION MANAGEMENT ==================================== # Is this connection active and ready to perform queries? diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 2b79823d04..04869bd925 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -224,6 +224,18 @@ module ActiveRecord "0" end + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity(&block) #:nodoc: + old = select_value("SELECT @@FOREIGN_KEY_CHECKS") + + begin + update("SET FOREIGN_KEY_CHECKS = 0") + yield + ensure + update("SET FOREIGN_KEY_CHECKS = #{old}") + end + end # CONNECTION MANAGEMENT ==================================== diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a5d550c84b..bf8fed1111 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -364,6 +364,14 @@ module ActiveRecord end end + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity(&block) #:nodoc: + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) + yield + ensure + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + end # DATABASE STATEMENTS ====================================== diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index b314340f0c..c9fc87756d 100755 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -215,6 +215,199 @@ end # the results of your transaction until Active Record supports nested transactions or savepoints (in progress.) # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. # Use InnoDB, MaxDB, or NDB instead. +# +# = Advanced YAML Fixtures +# +# YAML fixtures that don't specify an ID get some extra features: +# +# * Stable, autogenerated ID's +# * Label references for associations (belongs_to, has_one, has_many) +# * HABTM associations as inline lists +# * Autofilled timestamp columns +# * Fixture label interpolation +# * Support for YAML defaults +# +# == Stable, autogenerated ID's +# +# Here, have a monkey fixture: +# +# george: +# id: 1 +# name: George the Monkey +# +# reginald: +# id: 2 +# name: Reginald the Pirate +# +# Each of these fixtures has two unique identifiers: one for the database +# and one for the humans. Why don't we generate the primary key instead? +# Hashing each fixture's label yields a consistent ID: +# +# george: # generated id: 503576764 +# name: George the Monkey +# +# reginald: # generated id: 324201669 +# name: Reginald the Pirate +# +# ActiveRecord looks at the fixture's model class, discovers the correct +# primary key, and generates it right before inserting the fixture +# into the database. +# +# The generated ID for a given label is constant, so we can discover +# any fixture's ID without loading anything, as long as we know the label. +# +# == Label references for associations (belongs_to, has_one, has_many) +# +# Specifying foreign keys in fixtures can be very fragile, not to +# mention difficult to read. Since ActiveRecord can figure out the ID of +# and fixture from its label, you can specify FK's by label instead of ID. +# +# === belongs_to +# +# Let's break out some more monkeys and pirates. +# +# ### in pirates.yml +# +# reginald: +# id: 1 +# name: Reginald the Pirate +# monkey_id: 1 +# +# ### in monkeys.yml +# +# george: +# id: 1 +# name: George the Monkey +# pirate_id: 1 +# +# Add a few more monkeys and pirates and break this into multiple files, +# and it gets pretty hard to keep track of what's going on. Let's +# use labels instead of ID's: +# +# ### in pirates.yml +# +# reginald: +# name: Reginald the Pirate +# monkey: george +# +# ### in monkeys.yml +# +# george: +# name: George the Monkey +# pirate: reginald +# +# Pow! All is made clear. ActiveRecord reflects on the fixture's model class, +# finds all the +belongs_to+ associations, and allows you to specify +# a target *label* for the *association* (monkey: george) rather than +# a target *id* for the *FK* (monkey_id: 1). +# +# === has_and_belongs_to_many +# +# Time to give our monkey some fruit. +# +# ### in monkeys.yml +# +# george: +# id: 1 +# name: George the Monkey +# pirate_id: 1 +# +# ### in fruits.yml +# +# apple: +# id: 1 +# name: apple +# +# orange: +# id: 2 +# name: orange +# +# grape: +# id: 3 +# name: grape +# +# ### in fruits_monkeys.yml +# +# apple_george: +# fruit_id: 1 +# monkey_id: 1 +# +# orange_george: +# fruit_id: 2 +# monkey_id: 1 +# +# grape_george: +# fruit_id: 3 +# monkey_id: 1 +# +# Let's make the HABTM fixture go away. +# +# ### in monkeys.yml +# +# george: +# name: George the Monkey +# pirate: reginald +# fruits: apple, orange, grape +# +# ### in fruits.yml +# +# apple: +# name: apple +# +# orange: +# name: orange +# +# grape: +# name: grape +# +# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits +# on George's fixture, but we could've just as easily specified a list +# of monkeys on each fruit. As with +belongs_to+, ActiveRecord reflects on +# the fixture's model class and discovers the +has_and_belongs_to_many+ +# associations. +# +# == Autofilled timestamp columns +# +# If your table/model specifies any of ActiveRecord's +# standard timestamp columns (created_at, created_on, updated_at, updated_on), +# they will automatically be set to Time.now. +# +# If you've set specific values, they'll be left alone. +# +# == Fixture label interpolation +# +# The label of the current fixture is always available as a column value: +# +# geeksomnia: +# name: Geeksomnia's Account +# subdomain: $LABEL +# +# Also, sometimes (like when porting older join table fixtures) you'll need +# to be able to get ahold of the identifier for a given label. ERB +# to the rescue: +# +# george_reginald: +# monkey_id: <%= Fixtures.identify(:reginald) %> +# pirate_id: <%= Fixtures.identify(:george) %> +# +# == Support for YAML defaults +# +# You probably already know how to use YAML to set and reuse defaults in +# your +database.yml+ file,. You can use the same technique in your fixtures: +# +# DEFAULTS: &DEFAULTS +# created_on: <%= 3.weeks.ago.to_s(:db) %> +# +# first: +# name: Smurf +# <<: *DEFAULTS +# +# second: +# name: Fraggle +# <<: *DEFAULTS +# +# Any fixture labeled "DEFAULTS" is safely ignored. + class Fixtures < YAML::Omap DEFAULT_FILTER_RE = /\.ya?ml$/ @@ -279,32 +472,41 @@ class Fixtures < YAML::Omap unless table_names_to_fetch.empty? ActiveRecord::Base.silence do - fixtures_map = {} + connection.disable_referential_integrity do + fixtures_map = {} - fixtures = table_names_to_fetch.map do |table_name| - fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s)) - end + fixtures = table_names_to_fetch.map do |table_name| + fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s)) + end - all_loaded_fixtures.update(fixtures_map) + all_loaded_fixtures.update(fixtures_map) - connection.transaction(Thread.current['open_transactions'].to_i == 0) do - fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } - fixtures.each { |fixture| fixture.insert_fixtures } + connection.transaction(Thread.current['open_transactions'].to_i == 0) do + fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } + fixtures.each { |fixture| fixture.insert_fixtures } - # Cap primary key sequences to max(pk). - if connection.respond_to?(:reset_pk_sequence!) - table_names.each do |table_name| - connection.reset_pk_sequence!(table_name) + # Cap primary key sequences to max(pk). + if connection.respond_to?(:reset_pk_sequence!) + table_names.each do |table_name| + connection.reset_pk_sequence!(table_name) + end end end - end - cache_fixtures(connection, fixtures) + cache_fixtures(connection, fixtures) + end end end cached_fixtures(connection, table_names) end + # Returns a consistent identifier for +label+. This will always + # be a positive integer, and will always be the same for a given + # label, assuming the same OS, platform, and version of Ruby. + def self.identify(label) + label.to_s.hash.abs + end + attr_reader :table_name def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE) @@ -322,12 +524,90 @@ class Fixtures < YAML::Omap end def insert_fixtures - values.each do |fixture| + now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now + now = now.to_s(:db) + + # allow a standard key to be used for doing defaults in YAML + delete(assoc("DEFAULTS")) + + # track any join tables we need to insert later + habtm_fixtures = Hash.new do |h, habtm| + h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil) + end + + each do |label, fixture| + row = fixture.to_hash + + if model_class && model_class < ActiveRecord::Base && !row[primary_key_name] + # fill in timestamp columns if they aren't specified + timestamp_column_names.each do |name| + row[name] = now unless row.key?(name) + end + + # interpolate the fixture label + row.each do |key, value| + row[key] = label if value == "$LABEL" + end + + # generate a primary key + row[primary_key_name] = Fixtures.identify(label) + + model_class.reflect_on_all_associations.each do |association| + case association.macro + when :belongs_to + if value = row.delete(association.name.to_s) + fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s + row[fk_name] = Fixtures.identify(value) + end + when :has_and_belongs_to_many + if (targets = row.delete(association.name.to_s)) + targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) + join_fixtures = habtm_fixtures[association] + + targets.each do |target| + join_fixtures["#{label}_#{target}"] = Fixture.new( + { association.primary_key_name => Fixtures.identify(label), + association.association_foreign_key => Fixtures.identify(target) }, nil) + end + end + end + end + end + @connection.insert_fixture(fixture, @table_name) end + + # insert any HABTM join tables we discovered + habtm_fixtures.values.each do |fixture| + fixture.delete_existing_fixtures + fixture.insert_fixtures + end end private + class HabtmFixtures < ::Fixtures #:nodoc: + def read_fixture_files; end + end + + def model_class + @model_class ||= @class_name.is_a?(Class) ? + @class_name : @class_name.constantize rescue nil + end + + def primary_key_name + @primary_key_name ||= model_class && model_class.primary_key + end + + def timestamp_column_names + @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name| + column_names.include?(name) + end + end + + def column_names + @column_names ||= @connection.columns(@table_name).collect(&:name) + end + def read_fixture_files if File.file?(yaml_file_path) read_yaml_fixture_files diff --git a/activerecord/test/associations/eager_test.rb b/activerecord/test/associations/eager_test.rb index 2b02971238..75948ab59f 100644 --- a/activerecord/test/associations/eager_test.rb +++ b/activerecord/test/associations/eager_test.rb @@ -252,9 +252,9 @@ class EagerAssociationTest < Test::Unit::TestCase end def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope - posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id', :include => :comments, :order => 'posts.id DESC', :limit => 2) + posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :order => 'posts.id DESC', :limit => 2) posts_with_scoped_order = Post.with_scope(:find => {:order => 'posts.id DESC'}) do - Post.find(:all, :conditions => 'comments.id', :include => :comments, :limit => 2) + Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :limit => 2) end assert_equal posts_with_explicit_order, posts_with_scoped_order end diff --git a/activerecord/test/associations_test.rb b/activerecord/test/associations_test.rb index 9f155dd4ab..121a8c5ac4 100755 --- a/activerecord/test/associations_test.rb +++ b/activerecord/test/associations_test.rb @@ -548,7 +548,7 @@ class HasManyAssociationsTest < Test::Unit::TestCase client_ary = firm.clients_using_finder_sql.find("2", "3") assert_kind_of Array, client_ary assert_equal 2, client_ary.size - assert_equal client, client_ary.first + assert client_ary.include?(client) end def test_find_all diff --git a/activerecord/test/fixtures/db_definitions/schema.rb b/activerecord/test/fixtures/db_definitions/schema.rb index dcb6222cd3..9cadc5bdb8 100644 --- a/activerecord/test/fixtures/db_definitions/schema.rb +++ b/activerecord/test/fixtures/db_definitions/schema.rb @@ -295,4 +295,33 @@ ActiveRecord::Schema.define do t.column :city, :string, :null => false t.column :type, :string end + + create_table :parrots, :force => true do |t| + t.column :name, :string + t.column :created_at, :datetime + t.column :created_on, :datetime + t.column :updated_at, :datetime + t.column :updated_on, :datetime + end + + create_table :pirates, :force => true do |t| + t.column :catchphrase, :string + t.column :parrot_id, :integer + t.column :created_on, :datetime + t.column :updated_on, :datetime + end + + create_table :parrots_pirates, :id => false, :force => true do |t| + t.column :parrot_id, :integer + t.column :pirate_id, :integer + end + + create_table :treasures, :force => true do |t| + t.column :name, :string + end + + create_table :parrots_treasures, :id => false, :force => true do |t| + t.column :parrot_id, :integer + t.column :treasure_id, :integer + end end diff --git a/activerecord/test/fixtures/parrot.rb b/activerecord/test/fixtures/parrot.rb new file mode 100644 index 0000000000..340c73b907 --- /dev/null +++ b/activerecord/test/fixtures/parrot.rb @@ -0,0 +1,4 @@ +class Parrot < ActiveRecord::Base + has_and_belongs_to_many :pirates + has_and_belongs_to_many :treasures +end diff --git a/activerecord/test/fixtures/parrots.yml b/activerecord/test/fixtures/parrots.yml new file mode 100644 index 0000000000..74bc6b4e78 --- /dev/null +++ b/activerecord/test/fixtures/parrots.yml @@ -0,0 +1,16 @@ +george: + name: "Curious George" + treasures: diamond, sapphire + +louis: + name: "King Louis" + treasures: [diamond, sapphire] + +frederick: + name: $LABEL + +DEFAULTS: &DEFAULTS + treasures: sapphire, ruby + +davey: + <<: *DEFAULTS diff --git a/activerecord/test/fixtures/parrots_pirates.yml b/activerecord/test/fixtures/parrots_pirates.yml new file mode 100644 index 0000000000..6b17a37d68 --- /dev/null +++ b/activerecord/test/fixtures/parrots_pirates.yml @@ -0,0 +1,7 @@ +george_blackbeard: + parrot_id: <%= Fixtures.identify(:george) %> + pirate_id: <%= Fixtures.identify(:blackbeard) %> + +louis_blackbeard: + parrot_id: <%= Fixtures.identify(:louis) %> + pirate_id: <%= Fixtures.identify(:blackbeard) %> diff --git a/activerecord/test/fixtures/pirate.rb b/activerecord/test/fixtures/pirate.rb new file mode 100644 index 0000000000..d22d66bac9 --- /dev/null +++ b/activerecord/test/fixtures/pirate.rb @@ -0,0 +1,4 @@ +class Pirate < ActiveRecord::Base + belongs_to :parrot + has_and_belongs_to_many :parrots +end diff --git a/activerecord/test/fixtures/pirates.yml b/activerecord/test/fixtures/pirates.yml new file mode 100644 index 0000000000..abb91101da --- /dev/null +++ b/activerecord/test/fixtures/pirates.yml @@ -0,0 +1,9 @@ +blackbeard: + catchphrase: "Yar." + parrot: george + +redbeard: + catchphrase: "Avast!" + parrot: louis + created_on: <%= 2.weeks.ago.to_s(:db) %> + updated_on: <%= 2.weeks.ago.to_s(:db) %> diff --git a/activerecord/test/fixtures/treasure.rb b/activerecord/test/fixtures/treasure.rb new file mode 100644 index 0000000000..cbb3ec3c08 --- /dev/null +++ b/activerecord/test/fixtures/treasure.rb @@ -0,0 +1,3 @@ +class Treasure < ActiveRecord::Base + has_and_belongs_to_many :parrots +end diff --git a/activerecord/test/fixtures/treasures.yml b/activerecord/test/fixtures/treasures.yml new file mode 100644 index 0000000000..c6fe30d0e3 --- /dev/null +++ b/activerecord/test/fixtures/treasures.yml @@ -0,0 +1,8 @@ +diamond: + name: $LABEL + +sapphire: + name: $LABEL + +ruby: + name: $LABEL diff --git a/activerecord/test/fixtures_test.rb b/activerecord/test/fixtures_test.rb index f35e1ef2c7..2bc72d0b4e 100755 --- a/activerecord/test/fixtures_test.rb +++ b/activerecord/test/fixtures_test.rb @@ -7,6 +7,9 @@ require 'fixtures/reply' require 'fixtures/joke' require 'fixtures/course' require 'fixtures/category' +require 'fixtures/parrot' +require 'fixtures/pirate' +require 'fixtures/treasure' class FixturesTest < Test::Unit::TestCase self.use_instantiated_fixtures = true @@ -446,3 +449,83 @@ class FasterFixturesTest < Test::Unit::TestCase assert_equal 'Welcome to the weblog', posts(:welcome).title end end + +class FoxyFixturesTest < Test::Unit::TestCase + fixtures :parrots, :parrots_pirates, :pirates, :treasures + + def test_identifies_strings + assert_equal(Fixtures.identify("foo"), Fixtures.identify("foo")) + assert_not_equal(Fixtures.identify("foo"), Fixtures.identify("FOO")) + end + + def test_identifies_symbols + assert_equal(Fixtures.identify(:foo), Fixtures.identify(:foo)) + end + + TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) + + def test_populates_timestamp_columns + TIMESTAMP_COLUMNS.each do |property| + assert_not_nil(parrots(:george).send(property), "should set #{property}") + end + end + + def test_populates_all_columns_with_the_same_time + last = nil + + TIMESTAMP_COLUMNS.each do |property| + current = parrots(:george).send(property) + last ||= current + + assert_equal(last, current) + last = current + end + end + + def test_only_populates_columns_that_exist + assert_not_nil(pirates(:blackbeard).created_on) + assert_not_nil(pirates(:blackbeard).updated_on) + end + + def test_preserves_existing_fixture_data + assert_equal(2.weeks.ago.to_date, pirates(:redbeard).created_on.to_date) + assert_equal(2.weeks.ago.to_date, pirates(:redbeard).updated_on.to_date) + end + + def test_generates_unique_ids + assert_not_nil(parrots(:george).id) + assert_not_equal(parrots(:george).id, parrots(:louis).id) + end + + def test_resolves_belongs_to_symbols + assert_equal(parrots(:george), pirates(:blackbeard).parrot) + end + + def test_supports_join_tables + assert(pirates(:blackbeard).parrots.include?(parrots(:george))) + assert(pirates(:blackbeard).parrots.include?(parrots(:louis))) + assert(parrots(:george).pirates.include?(pirates(:blackbeard))) + end + + def test_supports_inline_habtm + assert(parrots(:george).treasures.include?(treasures(:diamond))) + assert(parrots(:george).treasures.include?(treasures(:sapphire))) + assert(!parrots(:george).treasures.include?(treasures(:ruby))) + end + + def test_supports_yaml_arrays + assert(parrots(:louis).treasures.include?(treasures(:diamond))) + assert(parrots(:louis).treasures.include?(treasures(:sapphire))) + end + + def test_strips_DEFAULTS_key + assert_raise(StandardError) { parrots(:DEFAULTS) } + + # this lets us do YAML defaults and not have an extra fixture entry + %w(sapphire ruby).each { |t| assert(parrots(:davey).treasures.include?(treasures(t))) } + end + + def test_supports_label_interpolation + assert_equal("frederick", parrots(:frederick).name) + end +end |