diff --git a/.gitignore b/.gitignore
index f533b0a..35f6fd7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,5 @@ public/stylesheets/parlour_tag.css
public/stylesheets/common.css
public/mps.html
public/mps
+db/schema.rb
tags
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 84c4081..8ebefc8 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,13 +1,13 @@
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
- def cloud_tag(word)
- style = "font-size: #{(word.size_factor * 100).round / 100.0}em;"
- style << " color: ##{word.colour};"
- style << " left: #{word.x}px;"
- style << " top: #{word.y}px"
+ def cloud_tag(term)
+ style = "font-size: #{(term.size_factor * 100).round / 100.0}em;"
+ style << " color: ##{term.colour};"
+ style << " left: #{term.x}px;"
+ style << " top: #{term.y}px"
- %[
#{word.text}\n]
+ %[#{term.text}\n]
end
end
diff --git a/app/models/cloud.rb b/app/models/cloud.rb
index f4f9e40..c08608e 100644
--- a/app/models/cloud.rb
+++ b/app/models/cloud.rb
@@ -1,17 +1,26 @@
-class Cloud
- attr_accessor :title, :words
+# == Schema Information
+# Schema version: 20090322184246
+#
+# Table name: clouds
+#
+# id :integer(4) not null, primary key
+# mp_id :integer(4)
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Cloud < ActiveRecord::Base
+ belongs_to :mp
+ has_many :terms
def initialize(params)
- @mp = params[:mp]
- @title = "#{@mp.full_name} Written Questions"
- tc = TextChunk.new text
- @words = tc.terms_with_counts
+ super params
+ tc = TextChunk.new mp.text_for_cloud
+ self.terms = tc.terms_with_counts
end
- private
-
- def text
- @text ||= @mp.written_answer_text
+ def title
+ "#{mp.full_name} Written Questions"
end
end
diff --git a/app/models/mp.rb b/app/models/mp.rb
index a701cf3..66098d8 100644
--- a/app/models/mp.rb
+++ b/app/models/mp.rb
@@ -1,14 +1,33 @@
+# == Schema Information
+# Schema version: 20090322184246
+#
+# Table name: mps
+#
+# id :integer(4) not null, primary key
+# full_name :string(255)
+# person_id :integer(4)
+# constituency :string(255)
+# party :string(255)
+# written_questions_text :text
+# created_at :datetime
+# updated_at :datetime
+#
+
require 'twfy'
-class Mp
+class Mp < ActiveRecord::Base
include Keys
-
- attr_accessor :full_name, :person_id, :constituency, :party
+ has_many :clouds
+
+ validates_presence_of :full_name, :person_id
+ validates_numericality_of :person_id, :only_integer => true
+ validates_uniqueness_of :person_id
def self.fetch_list
+ RAILS_DEFAULT_LOGGER.info "Fetching MP list from TWFY"
objs = self.twfy_client.mps
mps = objs.collect do |obj|
- self.instantiate obj
+ self.find_or_create_from_twfy obj
end
mps.sort {|a,b| a.surname <=> b.surname}
end
@@ -17,20 +36,24 @@ def self.twfy_client
Twfy::Client.new TWFY_API_KEY
end
- def self.from_postcode(postcode)
- client = self.twfy_client
- mp = client.mp :postcode => postcode
- self.instantiate obj
- end
+# def self.from_postcode(postcode)
+# client = self.twfy_client
+# mp = client.mp :postcode => postcode
+# self.find_or_create_from_twfy obj
+# end
# TWFY person_id
def self.from_person_id(person_id)
client = self.twfy_client
mp = client.mp :id => person_id
- self.instantiate mp[0]
+ self.find_or_create_from_twfy mp[0]
+ end
+
+ def self.find_or_create_from_twfy(obj)
+ self.find_by_person_id(obj.person_id) || self.create_from_twfy(obj)
end
- def self.instantiate(obj)
+ def self.create_from_twfy(obj)
mp = Mp.new
name = obj.respond_to?(:full_name) ? obj.full_name : obj.name
name = obj.name if name.blank?
@@ -38,27 +61,48 @@ def self.instantiate(obj)
mp.person_id = obj.person_id
mp.party = obj.party
mp.constituency = obj.constituency.name
+ mp.save!
mp
end
def surname
- @full_name.split(/ /)[-1]
+ full_name.to_s.split(/ /)[-1]
end
def to_param
- "#{person_id}-#{@full_name.gsub(/ /, '-')}"
+ "#{person_id}-#{full_name.gsub(/ /, '-')}"
+ end
+
+ def get_cloud
+ cloud = self.clouds.last
+ return clouds.create if cloud.nil? or written_question_text_has_changed?
+ cloud
end
- def cloud
- @cloud ||= Cloud.new(:mp => self)
+ def text_for_cloud
+ return written_questions_text unless written_questions_text.blank?
+ self.written_questions_text = get_wrans
+ save!
+ self.written_questions_text
end
- def written_answer_text
+ def get_wrans
+ RAILS_DEFAULT_LOGGER.info "Calling TWFY.wrans for MP:#{full_name}"
answers = twfy_client.wrans :person => person_id
answers.rows.inject("") {|text, row| text << row['body']}
end
- private
+ def written_question_text_has_changed?
+ return true if written_questions_text.blank?
+ text = get_wrans
+ if self.written_questions_text != text
+ self.written_questions_text = text
+ save!
+ return true
+ else
+ return false
+ end
+ end
def twfy_client
@twfy_client ||= Twfy::Client.new TWFY_API_KEY
diff --git a/app/models/term.rb b/app/models/term.rb
new file mode 100644
index 0000000..bcbdc86
--- /dev/null
+++ b/app/models/term.rb
@@ -0,0 +1,45 @@
+# == Schema Information
+# Schema version: 20090322184246
+#
+# Table name: terms
+#
+# id :integer(4) not null, primary key
+# text :string(255)
+# value :integer(4)
+# cloud_id :integer(4)
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Term < ActiveRecord::Base
+ include PhraseCoordinates
+
+ belongs_to :cloud
+
+ OFFSET_FROM_TOP = 160
+ OFFSET_FROM_LEFT = 10
+
+ # distance from left in percentages
+ def x
+ OFFSET_FROM_LEFT + (coordinates.x * 700).to_i
+ end
+
+ # distance from top in percentages
+ def y
+ OFFSET_FROM_TOP + (coordinates.y * 700).to_i
+ end
+
+ def colour
+ phrase_colour
+ end
+
+ def size_factor
+ (Math.log(value) / Math.log(3) + 1) * 1.5
+ end
+
+ private
+
+ def coordinates
+ @coordinates ||= phrase_coordinates
+ end
+end
diff --git a/app/models/text_chunk.rb b/app/models/text_chunk.rb
index 4f0ecd7..7a719af 100644
--- a/app/models/text_chunk.rb
+++ b/app/models/text_chunk.rb
@@ -37,11 +37,11 @@ def terms_with_counts
RAILS_DEFAULT_LOGGER.debug "Counting: #{term}, #{value}"
@max_count = [ @max_count, value ].max
@min_count = [ @min_count, value ].min
- word = Word.new(term, value)
- rtn << word
+ term = Term.new(:text => term, :value => value)
+ rtn << term
end
- # Return words in descending order of occurrences, so that the
- # biggest words are drawn on the page first, with smaller words
+ # Return terms in descending order of occurrences, so that the
+ # biggest terms are drawn on the page first, with smaller terms
# on top, to make it easier to read the tag cloud
rtn.sort {|a,b| a.value <=> b.value}.reverse
end
diff --git a/app/models/word.rb b/app/models/word.rb
deleted file mode 100644
index d6a9593..0000000
--- a/app/models/word.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-class Word
- include PhraseCoordinates
-
- attr_accessor :text, :value
-
- def initialize(text, value)
- @text = text
- @value = value
- end
-
- # distance from left in percentages
- def x
- 10 + (coordinates.x * 700).to_i
- end
-
- # distance from top in percentages
- def y
- 120 + (coordinates.y * 700).to_i
- end
-
- def colour
- phrase_colour
- end
-
- def size_factor
- (Math.log(value) / Math.log(3) + 1) * 1.5
- end
-
- private
-
- def coordinates
- @coordinates ||= phrase_coordinates
- end
-end
diff --git a/app/views/mps/show.html.haml b/app/views/mps/show.html.haml
index 3557aad..523b522 100644
--- a/app/views/mps/show.html.haml
+++ b/app/views/mps/show.html.haml
@@ -3,11 +3,12 @@
%p.back_link
=link_to 'back', mps_path
- %h1= @mp.cloud.title
-
- %h2= "#{@mp.party} MP for #{@mp.constituency}"
+ - cloud = @mp.get_cloud
+ %h1= cloud.title
+ %h3= "#{@mp.party} MP for #{@mp.constituency}"
+ %h2= "Updated on #{cloud.created_at.to_date.to_s(:db)}"
#cloud
%ul
- = render :partial => @mp.cloud.words
+ = render :partial => cloud.terms
diff --git a/app/views/terms/_term.html.haml b/app/views/terms/_term.html.haml
new file mode 100644
index 0000000..3e4e0e5
--- /dev/null
+++ b/app/views/terms/_term.html.haml
@@ -0,0 +1,2 @@
+= cloud_tag term
+
diff --git a/app/views/words/_word.html.haml b/app/views/words/_word.html.haml
deleted file mode 100644
index 5ef416e..0000000
--- a/app/views/words/_word.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-= cloud_tag word
-
diff --git a/db/migrate/20090322184246_create_models.rb b/db/migrate/20090322184246_create_models.rb
new file mode 100644
index 0000000..e2bca4d
--- /dev/null
+++ b/db/migrate/20090322184246_create_models.rb
@@ -0,0 +1,31 @@
+class CreateModels < ActiveRecord::Migration
+ def self.up
+
+ create_table :clouds do |t|
+ t.integer :mp_id
+ t.timestamps
+ end
+
+ create_table :terms do |t|
+ t.string :text
+ t.integer :value
+ t.integer :cloud_id
+ t.timestamps
+ end
+
+ create_table :mps do |t|
+ t.string :full_name
+ t.integer :person_id
+ t.string :constituency
+ t.string :party
+ t.text :written_questions_text
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :mps
+ drop_table :terms
+ drop_table :clouds
+ end
+end
diff --git a/lib/phrase_coordinates.rb b/lib/phrase_coordinates.rb
index 8885bb3..021b03c 100644
--- a/lib/phrase_coordinates.rb
+++ b/lib/phrase_coordinates.rb
@@ -7,14 +7,14 @@ class Coordinate < Struct.new(:x, :y)
MAX_COORDINATE_VALUE = "ffff".hex.to_f
def phrase_coordinates
- md5 = MD5::hexdigest(@text)
+ md5 = MD5::hexdigest(self.text)
x = md5[0, 4].hex / MAX_COORDINATE_VALUE
y = md5[4, 4].hex / MAX_COORDINATE_VALUE
Coordinate.new(x, y)
end
def phrase_colour
- md5 = MD5::hexdigest(@text)
+ md5 = MD5::hexdigest(self.text)
r = md5[0,10].hex % 255
g = md5[11,20].hex % 255
b = md5[21,31].hex % 255
diff --git a/lib/tasks/rspec.rake b/lib/tasks/rspec.rake
index a0ed791..1f3f1a9 100644
--- a/lib/tasks/rspec.rake
+++ b/lib/tasks/rspec.rake
@@ -3,32 +3,8 @@ gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9
# Don't load rspec if running "rake gems:*"
unless ARGV.any? {|a| a =~ /^gems/}
-begin
- require 'spec/rake/spectask'
-rescue MissingSourceFile
- module Spec
- module Rake
- class SpecTask
- def initialize(name)
- task name do
- # if rspec-rails is a configured gem, this will output helpful material and exit ...
- require File.expand_path(File.dirname(__FILE__) + "/../../config/environment")
-
- # ... otherwise, do this:
- raise <<-MSG
-
-#{"*" * 80}
-* You are trying to run an rspec rake task defined in
-* #{__FILE__},
-* but rspec can not be found in vendor/gems, vendor/plugins or system gems.
-#{"*" * 80}
-MSG
- end
- end
- end
- end
- end
-end
+require File.expand_path(File.dirname(__FILE__) + "/../../config/environment")
+require 'spec/rake/spectask'
Rake.application.instance_variable_get('@tasks').delete('default')
@@ -162,4 +138,4 @@ namespace :spec do
end
end
-end
\ No newline at end of file
+end
diff --git a/public/stylesheets/sass/parlour_tag.sass b/public/stylesheets/sass/parlour_tag.sass
index 3a0dbd2..1f94408 100644
--- a/public/stylesheets/sass/parlour_tag.sass
+++ b/public/stylesheets/sass/parlour_tag.sass
@@ -11,6 +11,6 @@
li
:list-style none
-.word
+.term
:position absolute
:float left
diff --git a/spec/blueprint.rb b/spec/blueprint.rb
new file mode 100644
index 0000000..3a2c904
--- /dev/null
+++ b/spec/blueprint.rb
@@ -0,0 +1,8 @@
+
+Mp.blueprint do
+ full_name "John Expense-Fiddler"
+ person_id 1234
+ party "Lying Bastards"
+ constituency "Somewhere in the tory belt"
+end
+
diff --git a/spec/models/cloud_spec.rb b/spec/models/cloud_spec.rb
new file mode 100644
index 0000000..71b1fb9
--- /dev/null
+++ b/spec/models/cloud_spec.rb
@@ -0,0 +1,26 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Cloud do
+
+ before(:each) do
+ Term.delete_all
+ Cloud.delete_all
+ @mp = mock_model Mp, :id => 99, :full_name => "Expense-fiddling Git"
+ @mp.stub!(:text_for_cloud).and_return(sample_text)
+ TagExtractor.stub!(:extract).and_return(sample_text_terms)
+ end
+
+ it "should save terms" do
+ cloud = Cloud.new :mp => @mp
+ cloud.save!
+ Term.count.should == sample_text_terms.size
+ end
+
+ it "should save" do
+ cloud = Cloud.new :mp => @mp
+ cloud.save!
+ Cloud.count.should == 1
+ end
+
+end
+
diff --git a/spec/models/mp_spec.rb b/spec/models/mp_spec.rb
new file mode 100644
index 0000000..1f9533b
--- /dev/null
+++ b/spec/models/mp_spec.rb
@@ -0,0 +1,107 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Mp do
+
+ before(:each) do
+ @params = {
+ :full_name => "John Expense-Fiddler",
+ :person_id => 1234,
+ :party => "Lying Bastards",
+ :constituency => "Somewhere in the tory belt"
+ }
+ end
+
+ it "should update cloud if wrans is different" do
+ mp = Mp.make
+ mock_twfy_for_mp mp, "Here is the first text"
+ mp.get_cloud
+ mock_twfy_for_mp mp, "Here is some different text"
+ mp.get_cloud
+ Cloud.count.should == 2
+ end
+
+ it "should not update cloud if wrans is same" do
+ Cloud.delete_all
+ mp = Mp.make
+ mock_twfy_for_mp mp, "Always the same"
+ mp.get_cloud
+ mp.reload
+ mp.get_cloud
+ Cloud.count.should == 1
+ end
+
+ it "should store written questions text" do
+ mp = Mp.make
+ mp.written_questions_text.should be_nil
+ mock_twfy_for_mp mp
+ mp.get_cloud
+ stored = Mp.find mp.id
+ stored.written_questions_text.should == 'wibble'
+ end
+
+ it "should store cloud" do
+ Cloud.delete_all
+ mp = Mp.make
+ mock_twfy_for_mp mp
+ mp.get_cloud
+ Cloud.count.should == 1
+ end
+
+ it "should fetch cloud from twfy" do
+ mp = Mp.make
+ mock_twfy_for_mp mp
+ mp.get_cloud
+ end
+
+ it "should require unique person_id" do
+ mp = Mp.new @params
+ mp.should be_valid
+
+ Mp.make :person_id => 1234
+ mp = Mp.new @params
+ mp.should_not be_valid
+ mp.should have(1).error_on(:person_id)
+ end
+
+ it "should require numeric person_id" do
+ ['wibble', 123.45].each do |invalid|
+ mp = Mp.new @params.merge(:person_id => invalid)
+ mp.should_not be_valid
+ mp.should have(1).error_on(:person_id)
+ end
+ end
+
+ it "should require parameters" do
+ [:full_name, :person_id].each do |missing|
+ mp = Mp.new @params.except(missing)
+ mp.should_not be_valid
+ [mp.errors[missing]].flatten.size.should >= 1
+ end
+ end
+
+ it "should save in the db, after fetching from twfy" do
+ Mp.delete_all
+ constituency = mock "Constituency", :name => "Someplace"
+ twfy = mock "MP", @params.merge(:name => nil, :constituency => constituency)
+ Mp.find_or_create_from_twfy twfy
+ Mp.count.should == 1
+ end
+
+ it "should fetch list from twfy" do
+ twfy = mock_model Twfy::Client
+ Twfy::Client.stub!(:new).and_return twfy
+ twfy.should_receive(:mps).and_return []
+
+ Mp.fetch_list
+ end
+
+end
+
+def mock_twfy_for_mp(mp, text = 'wibble')
+ TagExtractor.stub!(:extract).and_return(sample_text_terms)
+ twfy = mock_model Twfy::Client
+ Twfy::Client.stub!(:new).and_return twfy
+ dummy = mock "Answers", :rows => [{'body' => text}]
+ twfy.stub!(:wrans).with(:person => mp.person_id).and_return(dummy)
+end
+
diff --git a/spec/models/term_spec.rb b/spec/models/term_spec.rb
new file mode 100644
index 0000000..a82781d
--- /dev/null
+++ b/spec/models/term_spec.rb
@@ -0,0 +1,33 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Term do
+
+ it "should have same coordinates for different values" do
+ foo = Term.new :text => 'foo', :value => 10
+ bar = Term.new :text => 'foo', :value => 5
+ foo.x.should == bar.x
+ foo.y.should == bar.y
+ end
+
+ it "should have different coordinates for different terms" do
+ foo = Term.new :text => 'foo', :value => 10
+ bar = Term.new :text => 'bar', :value => 10
+ foo.x.should_not == bar.x
+ foo.y.should_not == bar.y
+ end
+
+ it "should have same coordinates for same terms" do
+ foo = Term.new :text => 'foo', :value => 10
+ bar = Term.new :text => 'foo', :value => 10
+ foo.x.should == bar.x
+ foo.y.should == bar.y
+ end
+
+ it "should have coordinates" do
+ foo = Term.new :text => 'foo', :value => 10
+ foo.x.should == 482
+ foo.y.should == 227
+ end
+
+end
+
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 0d10446..c307ad3 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -4,6 +4,7 @@
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
require 'spec/autorun'
require 'spec/rails'
+require File.join(File.dirname(__FILE__), 'blueprint')
Spec::Runner.configure do |config|
# If you're not using ActiveRecord you should remove these
@@ -45,3 +46,33 @@
#
# For more information take a look at Spec::Runner::Configuration and Spec::Runner
end
+
+def sample_text_terms
+ [
+ 'personal development courses',
+ 'scotland office',
+ 'communications team',
+ 's media',
+ 'departmental staff',
+ 'environment food',
+ 'rural affairs',
+ 'government departments',
+ 'vaccinations',
+ 'northern ireland',
+ 'vaccination',
+ 'launch',
+ 'fertility',
+ 'cattle',
+ '12 months',
+ 'bulls',
+ 'sheep',
+ 'salary',
+ 'journals'
+ ]
+end
+
+def sample_text
+ <<-TEXT
+ To ask the Secretary of State for Scotland what the purpose of his visit to Iceland in November 2008 was; how much the visit cost in each category of expenditure; what meetings he attended; what matters were discussed; and how many officials from (a) the Scotland Office and (b) other Government departments accompanied him.To ask the Secretary of State for Scotland which (a) newspapers, (b) magazines and (c) journals his Department has subscriptions to.To ask the Secretary of State for Scotland how many hits his Department's blog has received in each month since its launch.To ask the Secretary of State for Scotland what steps his Department has taken to publicise its blog; and what the cost of such activity was.To ask the Secretary of State for Scotland how many overseas visits Ministers in his Department have undertaken in the last 12 months; to which destinations; and how many departmental staff accompanied the Minister on each occasion.To ask the Secretary of State for Scotland what (a) training, (b) coaching and (c) personal development courses each Minister in his Department has received since 2005; and what the cost of providing this training was.To ask the Secretary of State for Scotland when he plans to answer Questions (a) 248515 and (b) 248516 tabled on 13 January 2009, on his Department's blog.To ask the Secretary of State for Environment, Food and Rural Affairs what recent assessment he has made of the effectiveness of vaccinations against bluetongue disease among (a) sheep and (b) cattle.To ask the Secretary of State for Environment, Food and Rural Affairs what information his Department holds on the effects of bluetongue vaccination on fertility in bulls. [R]To ask the Secretary of State for Northern Ireland how many members of staff are employed in his Department's media and communications team; when each member of staff was recruited; what the responsibilities of each member of staff are; and what the salary of each member of staff is.To ask the Secretary of State for Wales how many members of staff are employed in his Department's media and communications team; when each member of staff was recruited; what the responsibilities of each member of staff are; and what the salary of each member of staff is.To ask the Chancellor of the Exchequer if he will meet representatives of UK nationals who were depositors with Landsbanki Guernsey to discuss their position.
+ TEXT
+end
diff --git a/test/fixtures/clouds.yml b/test/fixtures/clouds.yml
deleted file mode 100644
index 5bf0293..0000000
--- a/test/fixtures/clouds.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
-
-# one:
-# column: value
-#
-# two:
-# column: value
diff --git a/test/unit/cloud_test.rb b/test/unit/cloud_test.rb
deleted file mode 100644
index e352c3a..0000000
--- a/test/unit/cloud_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-require 'test_helper'
-
-class CloudTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- def test_truth
- assert true
- end
-end
diff --git a/vendor/plugins/machinist/.autotest b/vendor/plugins/machinist/.autotest
new file mode 100644
index 0000000..f969129
--- /dev/null
+++ b/vendor/plugins/machinist/.autotest
@@ -0,0 +1,7 @@
+Autotest.add_hook :initialize do |at|
+ at.clear_mappings
+
+ at.add_mapping(%r%^spec/(.*)_spec.rb$%) {|filename, _| filename }
+ at.add_mapping(%r%^lib/(.*).rb$%) {|_, match| "spec/#{match[1]}_spec.rb" }
+ at.add_mapping(%r%^spec/spec_helper.rb$%) { at.files_matching(%r%^spec/(.*)_spec.rb$%) }
+end
diff --git a/vendor/plugins/machinist/.gitignore b/vendor/plugins/machinist/.gitignore
new file mode 100644
index 0000000..67430bf
--- /dev/null
+++ b/vendor/plugins/machinist/.gitignore
@@ -0,0 +1,3 @@
+coverage
+doc
+*.gem
diff --git a/vendor/plugins/machinist/MIT-LICENSE b/vendor/plugins/machinist/MIT-LICENSE
new file mode 100644
index 0000000..ac5cb2e
--- /dev/null
+++ b/vendor/plugins/machinist/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Peter Yandell
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/plugins/machinist/README.markdown b/vendor/plugins/machinist/README.markdown
new file mode 100644
index 0000000..caff86f
--- /dev/null
+++ b/vendor/plugins/machinist/README.markdown
@@ -0,0 +1,309 @@
+Machinist
+=========
+
+*Fixtures aren't fun. Machinist is.*
+
+Machinist lets you construct test data on the fly, but instead of doing this:
+
+ describe Comment do
+ before do
+ @user = User.create!(:name => "Test User", :email => "user@example.com")
+ @post = Post.create!(:title => "Test Post", :author => @user, :body => "Lorem ipsum...")
+ @comment = Comment.create!(
+ :post => @post, :author_name => "Test Commenter", :author_email => "commenter@example.com",
+ :spam => true
+ )
+ end
+
+ it "should not include comments marked as spam in the without_spam named scope" do
+ Comment.without_spam.should_not include(@comment)
+ end
+ end
+
+you can just do this:
+
+ describe Comment do
+ before do
+ @comment = Comment.make(:spam => true)
+ end
+
+ it "should not include comments marked as spam in the without_spam named scope" do
+ Comment.without_spam.should_not include(@comment)
+ end
+ end
+
+Machinist generates data for the fields you don't care about, and constructs any necessary associated objects, leaving you to only specify the fields you *do* care about in your tests.
+
+You tell Machinist how to do this with blueprints:
+
+ require 'faker'
+
+ Sham.name { Faker::Name.name }
+ Sham.email { Faker::Internet.email }
+ Sham.title { Faker::Lorem.sentence }
+ Sham.body { Faker::Lorem.paragraph }
+
+ User.blueprint do
+ name
+ email
+ end
+
+ Post.blueprint do
+ title
+ author
+ body
+ end
+
+ Comment.blueprint do
+ post
+ author_name { Sham.name }
+ author_email { Sham.email }
+ body
+ end
+
+
+Installation
+------------
+
+Install the plugin:
+
+ ./script/plugin install git://github.com/notahat/machinist.git
+
+Create a blueprints.rb in your test (or spec) directory, and require it in your test\_helper.rb (or spec\_helper.rb):
+
+ require File.expand_path(File.dirname(__FILE__) + "/blueprints")
+
+Set Sham to reset before each test. In the `class Test::Unit::TestCase` block in your test\_helper.rb, add:
+
+ setup { Sham.reset }
+
+or, if you're on RSpec, in the `Spec::Runner.configure` block in your spec\_helper.rb, add:
+
+ config.before(:each) { Sham.reset }
+
+### Installing as a Gem
+
+If you'd prefer, you can install Machinist as a gem:
+
+ sudo gem install notahat-machinist --source http://gems.github.com
+
+From there, create the blueprints.rb file as described above, and make sure you require machinist and sham.
+
+
+Sham - Generating Attribute Values
+----------------------------------
+
+Sham lets you generate random but repeatable unique attributes values.
+
+For example, you could define a way to generate random names as:
+
+ Sham.name { (1..10).map { ('a'..'z').to_a.rand } }
+
+Then, to generate a name, call:
+
+ Sham.name
+
+So why not just define a helper method to do this? Sham ensures two things for you:
+
+1. You get the same sequence of values each time your test is run
+2. You don't get any duplicate values
+
+Sham works very well with the excellent [Faker gem](http://faker.rubyforge.org/) by Benjamin Curtis. Using this, a much nicer way to generate names is:
+
+ Sham.name { Faker::Name.name }
+
+Sham also supports generating numbered sequences if you prefer.
+
+ Sham.name {|index| "Name #{index}" }
+
+If you want to allow duplicate values for a sham, you can pass the `:unique` option:
+
+ Sham.coin_toss(:unique => false) { rand(2) == 0 ? 'heads' : 'tails' }
+
+You can create a bunch of sham definitions in one hit like this:
+
+ Sham.define do
+ title { Faker::Lorem.words(5).join(' ') }
+ name { Faker::Name.name }
+ body { Faker::Lorem.paragraphs(3).join("\n\n") }
+ end
+
+
+Blueprints - Generating ActiveRecord Objects
+--------------------------------------------
+
+A blueprint describes how to generate an ActiveRecord object. The idea is that you let the blueprint take care of making up values for attributes that you don't care about in your test, leaving you to focus on the just the things that you're testing.
+
+A simple blueprint might look like this:
+
+ Post.blueprint do
+ title { Sham.title }
+ author { Sham.name }
+ body { Sham.body }
+ end
+
+You can then construct a Post from this blueprint with:
+
+ Post.make
+
+When you call `make`, Machinist calls Post.new, then runs through the attributes in your blueprint, calling the block for each attribute to generate a value. It then calls `save!` and `reload` on the Post.
+
+You can override values defined in the blueprint by passing a hash to make:
+
+ Post.make(:title => "A Specific Title")
+
+`make` doesn't call the blueprint blocks of any attributes that are passed in.
+
+If you don't supply a block for an attribute in the blueprint, Machinist will look for a Sham definition with the same name as the attribute, so you can shorten the above blueprint to:
+
+ Post.blueprint do
+ title
+ author { Sham.name }
+ body
+ end
+
+If you want to generate an object without saving it to the database, replace `make` with `make_unsaved`. (`make_unsaved` also ensures that any associated objects that need to be generated are not saved. See the section on associations below.)
+
+
+### Belongs\_to Associations
+
+You can generate an associated object like this:
+
+ Comment.blueprint do
+ post { Post.make }
+ end
+
+Calling `Comment.make` will construct a Comment and its associated Post, and save both.
+
+If you want to override the value for post when constructing the comment, you can:
+
+ post = Post.make(:title => "A particular title)
+ comment = Comment.make(:post => post)
+
+Machinist will not call the blueprint block for the post attribute, so this won't generate two posts.
+
+Machinist is smart enough to look at the association and work out what sort of object it needs to create, so you can shorten the above blueprint to:
+
+ Comment.blueprint do
+ post
+ end
+
+You can refer to already assigned attributes when constructing a new attribute:
+
+ Comment.blueprint do
+ post
+ body { "Comment on " + post.title }
+ end
+
+
+### Other Associations
+
+For has\_many and has\_and\_belongs\_to\_many associations, ActiveRecord insists that the object be saved before any associated objects can be saved. That means you can't generate the associated objects from within the blueprint.
+
+The simplest solution is to write a test helper:
+
+ def make_post_with_comments(attributes = {})
+ post = Post.make(attributes)
+ 3.times { post.comments.make }
+ post
+ end
+
+Note here that you can call `make` on a has\_many association.
+
+Make can take a block, into which it passes the constructed object, so the above can be written as:
+
+ def make_post_with_comments
+ Post.make(attributes) do |post|
+ 3.times { post.comments.make }
+ end
+ end
+
+
+### Using Blueprints in Rails Controller Tests
+
+The `plan` method behaves like `make`, except it returns a hash of attributes, and doesn't save the object. This is useful for passing in to controller tests:
+
+ test "should create post" do
+ assert_difference('Post.count') do
+ post :create, :post => Post.plan
+ end
+ assert_redirected_to post_path(assigns(:post))
+ end
+
+`plan` will save any associated objects. In this example, it will create an Author, and it knows that the controller expects an `author_id` attribute, rather than an `author` attribute, and makes this translation for you.
+
+You can also call plan on has\_many associations, making it easy to test nested controllers:
+
+ test "should create comment" do
+ post = Post.make
+ assert_difference('Comment.count') do
+ post :create, :post_id => post.id, :comment => post.comments.plan
+ end
+ assert_redirected_to post_comment_path(post, assigns(:comment))
+ end
+
+
+### Named Blueprints
+
+Named blueprints let you define variations on an object. For example, suppose some of your Users are administrators:
+
+ User.blueprint do
+ name
+ email
+ end
+
+ User.blueprint(:admin) do
+ name { Sham.name + " (admin)" }
+ admin { true }
+ end
+
+Calling:
+
+ User.make(:admin)
+
+will use the `:admin` blueprint.
+
+Named blueprints call the default blueprint to set any attributes not specifically provided, so in this example the `email` attribute will still be generated even for an admin user.
+
+
+FAQ
+---
+
+### My blueprint is giving me really weird errors. Any ideas?
+
+If your object has an attribute that happens to correspond to a Ruby standard function, it won't work properly in a blueprint.
+
+For example:
+
+ OpeningHours.blueprint do
+ open { Time.now }
+ end
+
+This will result in Machinist attempting to run ruby's open command. To work around this use self.open instead.
+
+ OpeningHours.blueprint do
+ self.open { Time.now }
+ end
+
+
+Credits
+-------
+
+Written by [Pete Yandell](http://notahat.com/).
+
+Contributors:
+
+- [Clinton Forbes](http://github.com/clinton)
+- [Jon Guymon](http://github.com/gnarg)
+- [Evan David Light](http://github.com/elight)
+- [Kyle Neath](http://github.com/kneath)
+- [T.J. Sheehy](http://github.com/tjsheehy)
+- [Roland Swingler](http://github.com/knaveofdiamonds)
+- [Matt Wastrodowski](http://github.com/towski)
+- [Ian White](http://github.com/ianwhite)
+
+Thanks to Thoughtbot's [Factory Girl](http://github.com/thoughtbot/factory_girl/tree/master). Machinist was written because I loved the idea behind Factory Girl, but I thought the philosophy wasn't quite right, and I hated the syntax.
+
+---
+
+Copyright (c) 2008 Peter Yandell, released under the MIT license
diff --git a/vendor/plugins/machinist/Rakefile b/vendor/plugins/machinist/Rakefile
new file mode 100644
index 0000000..bd038d0
--- /dev/null
+++ b/vendor/plugins/machinist/Rakefile
@@ -0,0 +1,14 @@
+require 'rubygems'
+require 'rake'
+require 'rake/clean'
+require 'spec/rake/spectask'
+
+desc 'Default: run specs.'
+task :default => :spec
+
+desc 'Run all the specs for the machinist plugin.'
+Spec::Rake::SpecTask.new do |t|
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.rcov = false
+end
+
diff --git a/vendor/plugins/machinist/init.rb b/vendor/plugins/machinist/init.rb
new file mode 100644
index 0000000..6011f6b
--- /dev/null
+++ b/vendor/plugins/machinist/init.rb
@@ -0,0 +1,2 @@
+require 'machinist' if RAILS_ENV == 'test'
+
diff --git a/vendor/plugins/machinist/lib/machinist.rb b/vendor/plugins/machinist/lib/machinist.rb
new file mode 100644
index 0000000..61df668
--- /dev/null
+++ b/vendor/plugins/machinist/lib/machinist.rb
@@ -0,0 +1,71 @@
+require 'active_support'
+require 'active_record'
+require 'sham'
+require 'machinist/active_record'
+
+module Machinist
+
+ # A Lathe is used to execute the blueprint and construct an object.
+ #
+ # The blueprint is instance_eval'd against the Lathe.
+ class Lathe
+ def self.run(object, *args)
+ blueprint = object.class.blueprint
+ named_blueprint = object.class.blueprint(args.shift) if args.first.is_a?(Symbol)
+ attributes = args.pop || {}
+ raise "No blueprint for class #{object.class}" if blueprint.nil?
+ returning self.new(object, attributes) do |lathe|
+ lathe.instance_eval(&named_blueprint) if named_blueprint
+ lathe.instance_eval(&blueprint)
+ end
+ end
+
+ def initialize(object, attributes = {})
+ @object = object
+ @assigned_attributes = {}
+ attributes.each do |key, value|
+ @object.send("#{key}=", value)
+ @assigned_attributes[key.to_sym] = value
+ end
+ end
+
+ # Undef a couple of methods that are common ActiveRecord attributes.
+ # (Both of these are deprecated in Ruby 1.8 anyway.)
+ undef_method :id if respond_to?(:id)
+ undef_method :type if respond_to?(:type)
+
+ def object
+ yield @object if block_given?
+ @object
+ end
+
+ attr_reader :assigned_attributes
+
+ def method_missing(symbol, *args, &block)
+ if @assigned_attributes.has_key?(symbol)
+ @object.send(symbol)
+ elsif @object.class.reflect_on_association(symbol) && !@object.send(symbol).nil?
+ @object.send(symbol)
+ else
+ @object.send("#{symbol}=", generate_attribute(symbol, args, &block))
+ end
+ end
+
+ def generate_attribute(attribute, args)
+ value = if block_given?
+ yield
+ elsif args.empty?
+ association = @object.class.reflect_on_association(attribute)
+ if association
+ association.class_name.constantize.make(args.first || {})
+ else
+ Sham.send(attribute)
+ end
+ else
+ args.first
+ end
+ @assigned_attributes[attribute] = value
+ end
+
+ end
+end
diff --git a/vendor/plugins/machinist/lib/machinist/active_record.rb b/vendor/plugins/machinist/lib/machinist/active_record.rb
new file mode 100644
index 0000000..74f643e
--- /dev/null
+++ b/vendor/plugins/machinist/lib/machinist/active_record.rb
@@ -0,0 +1,116 @@
+module Machinist
+
+ module ActiveRecord
+
+ # This method takes care of converting any associated objects,
+ # in the hash returned by Lathe#assigned_attributed, into their
+ # object ids.
+ #
+ # For example, let's say we have blueprints like this:
+ #
+ # Post.blueprint { }
+ # Comment.blueprint { post }
+ #
+ # Lathe#assigned_attributes will return { :post => ... }, but
+ # we want to pass { :post_id => 1 } to a controller.
+ #
+ # This method takes care of cleaning this up.
+ def self.assigned_attributes_without_associations(lathe)
+ attributes = {}
+ lathe.assigned_attributes.each_pair do |attribute, value|
+ association = lathe.object.class.reflect_on_association(attribute)
+ if association && association.macro == :belongs_to
+ attributes[association.primary_key_name.to_sym] = value.id
+ else
+ attributes[attribute] = value
+ end
+ end
+ attributes
+ end
+
+ # This sets a flag that stops make from saving objects, so
+ # that calls to make from within a blueprint don't create
+ # anything inside make_unsaved.
+ def self.with_save_nerfed
+ begin
+ @@nerfed = true
+ yield
+ ensure
+ @@nerfed = false
+ end
+ end
+
+ @@nerfed = false
+ def self.nerfed?
+ @@nerfed
+ end
+
+ module Extensions
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def blueprint(name = :master, &blueprint)
+ @blueprints ||= {}
+ @blueprints[name] = blueprint if block_given?
+ @blueprints[name]
+ end
+
+ def named_blueprints
+ @blueprints.reject{|name,_| name == :master }.keys
+ end
+
+ def clear_blueprints!
+ @blueprints = {}
+ end
+
+ def make(*args, &block)
+ lathe = Lathe.run(self.new, *args)
+ unless Machinist::ActiveRecord.nerfed?
+ lathe.object.save!
+ lathe.object.reload
+ end
+ lathe.object(&block)
+ end
+
+ def make_unsaved(*args)
+ returning(Machinist::ActiveRecord.with_save_nerfed { make(*args) }) do |object|
+ yield object if block_given?
+ end
+ end
+
+ def plan(*args)
+ lathe = Lathe.run(self.new, *args)
+ Machinist::ActiveRecord.assigned_attributes_without_associations(lathe)
+ end
+ end
+ end
+
+ module BelongsToExtensions
+ def make(*args, &block)
+ lathe = Lathe.run(self.build, *args)
+ unless Machinist::ActiveRecord.nerfed?
+ lathe.object.save!
+ lathe.object.reload
+ end
+ lathe.object(&block)
+ end
+
+ def plan(*args)
+ lathe = Lathe.run(self.build, *args)
+ Machinist::ActiveRecord.assigned_attributes_without_associations(lathe)
+ end
+ end
+
+ end
+end
+
+
+class ActiveRecord::Base
+ include Machinist::ActiveRecord::Extensions
+end
+
+class ActiveRecord::Associations::BelongsToAssociation
+ include Machinist::ActiveRecord::BelongsToExtensions
+end
diff --git a/vendor/plugins/machinist/lib/sham.rb b/vendor/plugins/machinist/lib/sham.rb
new file mode 100644
index 0000000..f8adbe8
--- /dev/null
+++ b/vendor/plugins/machinist/lib/sham.rb
@@ -0,0 +1,73 @@
+require 'active_support'
+
+class Sham
+ @@shams = {}
+
+ # Over-ride module's built-in name method, so we can re-use it for
+ # generating names. This is a bit of a no-no, but we get away with
+ # it in this context.
+ def self.name(*args, &block)
+ method_missing(:name, *args, &block)
+ end
+
+ def self.method_missing(symbol, *args, &block)
+ if block_given?
+ @@shams[symbol] = Sham.new(symbol, args.pop || {}, &block)
+ else
+ sham = @@shams[symbol]
+ raise "No sham defined for #{symbol}" if sham.nil?
+ sham.fetch_value
+ end
+ end
+
+ def self.clear
+ @@shams = {}
+ end
+
+ def self.reset
+ @@shams.values.each(&:reset)
+ end
+
+ def self.define(&block)
+ Sham.instance_eval(&block)
+ end
+
+ def initialize(name, options = {}, &block)
+ @name = name
+ @generator = block
+ @offset = 0
+ @unique = options.has_key?(:unique) ? options[:unique] : true
+ generate_values(12)
+ end
+
+ def reset
+ @offset = 0
+ end
+
+ def fetch_value
+ # Generate more values if we need them.
+ if @offset >= @values.length
+ generate_values(2 * @values.length)
+ raise "Can't generate more unique values for Sham.#{@name}" if @offset >= @values.length
+ end
+ returning @values[@offset] do
+ @offset += 1
+ end
+ end
+
+private
+
+ def generate_values(count)
+ @values = seeded { (1..count).map(&@generator) }
+ @values.uniq! if @unique
+ end
+
+ def seeded
+ begin
+ srand(1)
+ yield
+ ensure
+ srand
+ end
+ end
+end
diff --git a/vendor/plugins/machinist/machinist.gemspec b/vendor/plugins/machinist/machinist.gemspec
new file mode 100644
index 0000000..d51d535
--- /dev/null
+++ b/vendor/plugins/machinist/machinist.gemspec
@@ -0,0 +1,11 @@
+Gem::Specification.new do |s|
+ s.name = "machinist"
+ s.version = "0.3.1"
+ s.author = "Pete Yandell"
+ s.email = "pete@nothat.com"
+ s.homepage = "http://github.com/notahat/machinist"
+ s.summary = "Fixtures aren't fun. Machinist is."
+ s.files = ["lib/machinist.rb", "lib/sham.rb", "lib/machinist/active_record.rb"]
+ s.require_path = "lib"
+ s.has_rdoc = false
+end
diff --git a/vendor/plugins/machinist/spec/db/.gitignore b/vendor/plugins/machinist/spec/db/.gitignore
new file mode 100644
index 0000000..6061583
--- /dev/null
+++ b/vendor/plugins/machinist/spec/db/.gitignore
@@ -0,0 +1 @@
+*.sqlite3
diff --git a/vendor/plugins/machinist/spec/db/database.yml b/vendor/plugins/machinist/spec/db/database.yml
new file mode 100644
index 0000000..5f0f06e
--- /dev/null
+++ b/vendor/plugins/machinist/spec/db/database.yml
@@ -0,0 +1,3 @@
+test:
+ adapter: sqlite3
+ dbfile: spec/db/test.sqlite3
\ No newline at end of file
diff --git a/vendor/plugins/machinist/spec/db/schema.rb b/vendor/plugins/machinist/spec/db/schema.rb
new file mode 100644
index 0000000..e449a3f
--- /dev/null
+++ b/vendor/plugins/machinist/spec/db/schema.rb
@@ -0,0 +1,20 @@
+ActiveRecord::Schema.define(:version => 0) do
+ create_table :people, :force => true do |t|
+ t.column :name, :string
+ t.column :type, :string
+ t.column :password, :string
+ t.column :admin, :boolean, :default => false
+ end
+
+ create_table :posts, :force => true do |t|
+ t.column :title, :string
+ t.column :body, :text
+ t.column :published, :boolean, :default => true
+ end
+
+ create_table :comments, :force => true do |t|
+ t.column :post_id, :integer
+ t.column :author_id, :integer
+ t.column :body, :text
+ end
+end
diff --git a/vendor/plugins/machinist/spec/log/.gitignore b/vendor/plugins/machinist/spec/log/.gitignore
new file mode 100644
index 0000000..397b4a7
--- /dev/null
+++ b/vendor/plugins/machinist/spec/log/.gitignore
@@ -0,0 +1 @@
+*.log
diff --git a/vendor/plugins/machinist/spec/machinist_spec.rb b/vendor/plugins/machinist/spec/machinist_spec.rb
new file mode 100644
index 0000000..0037dae
--- /dev/null
+++ b/vendor/plugins/machinist/spec/machinist_spec.rb
@@ -0,0 +1,289 @@
+require File.dirname(__FILE__) + '/spec_helper'
+require 'machinist'
+
+
+class Person < ActiveRecord::Base
+ attr_protected :password
+end
+
+class Post < ActiveRecord::Base
+ has_many :comments
+end
+
+class Comment < ActiveRecord::Base
+ belongs_to :post
+ belongs_to :author, :class_name => "Person"
+end
+
+
+describe Machinist do
+
+ after{ Person.clear_blueprints! }
+
+ describe "make method" do
+
+ it "should set an attribute on the constructed object from a constant in the blueprint" do
+ Person.blueprint do
+ name "Fred"
+ end
+ Person.make.name.should == "Fred"
+ end
+
+ it "should set an attribute on the constructed object from a block in the blueprint" do
+ Person.blueprint do
+ name { "Fred" }
+ end
+ Person.make.name.should == "Fred"
+ end
+
+ it "should default to calling Sham for an attribute in the blueprint" do
+ Sham.clear
+ Sham.name { "Fred" }
+ Person.blueprint { name }
+ Person.make.name.should == "Fred"
+ end
+
+ it "should let the blueprint override an attribute with a default value" do
+ Post.blueprint do
+ published { false }
+ end
+ Post.make.published?.should be_false
+ end
+
+ it "should override an attribute from the blueprint with a passed-in attribute" do
+ Person.blueprint do
+ name "Fred"
+ end
+ Person.make(:name => "Bill").name.should == "Bill"
+ end
+
+ it "should allow overridden attribute names to be strings" do
+ Person.blueprint do
+ name "Fred"
+ end
+ Person.make("name" => "Bill").name.should == "Bill"
+ end
+
+ it "should not call a block in the blueprint if that attribute is passed in" do
+ block_called = false
+ Person.blueprint do
+ name { block_called = true; "Fred" }
+ end
+ Person.make(:name => "Bill").name.should == "Bill"
+ block_called.should be_false
+ end
+
+ it "should call a passed-in block with the object being constructed" do
+ Person.blueprint { }
+ block_called = false
+ Person.make do |person|
+ block_called = true
+ person.class.should == Person
+ end
+ block_called.should be_true
+ end
+
+ it "should provide access to the object being constructed from within the blueprint" do
+ person = nil
+ Person.blueprint { person = object }
+ Person.make
+ person.class.should == Person
+ end
+
+ it "should allow reading of a previously assigned attribute from within the blueprint" do
+ Post.blueprint do
+ title "Test"
+ body { title }
+ end
+ Post.make.body.should == "Test"
+ end
+
+ it "should allow setting a protected attribute in the blueprint" do
+ Person.blueprint do
+ password "Test"
+ end
+ Person.make.password.should == "Test"
+ end
+
+ it "should allow overriding a protected attribute" do
+ Person.blueprint do
+ password "Test"
+ end
+ Person.make(:password => "New").password.should == "New"
+ end
+
+ it "should allow setting the id attribute in a blueprint" do
+ Person.blueprint { id 12345 }
+ Person.make.id.should == 12345
+ end
+
+ it "should allow setting the type attribute in a blueprint" do
+ Person.blueprint { type "Person" }
+ Person.make.type.should == "Person"
+ end
+
+ describe "for named blueprints" do
+ before do
+ @block_called = false
+ Person.blueprint do
+ name { "Fred" }
+ admin { @block_called = true; false }
+ end
+ Person.blueprint(:admin) do
+ admin { true }
+ end
+ @person = Person.make(:admin)
+ end
+
+ it "should override an attribute from the parent blueprint in the child blueprint" do
+ @person.admin.should == true
+ end
+
+ it "should not call the block for an attribute from the parent blueprint if that attribute is overridden in the child" do
+ @block_called.should be_false
+ end
+
+ it "should set an attribute defined in the parent blueprint" do
+ @person.name.should == "Fred"
+ end
+ end
+
+ end # make method
+
+
+ describe "ActiveRecord support" do
+
+ describe "make method" do
+ it "should save the constructed object" do
+ Person.blueprint { }
+ person = Person.make
+ person.should_not be_new_record
+ end
+
+ it "should create an object through belongs_to association" do
+ Post.blueprint { }
+ Comment.blueprint { post }
+ Comment.make.post.class.should == Post
+ end
+
+ it "should create an object through belongs_to association with a class_name attribute" do
+ Person.blueprint { }
+ Comment.blueprint { author }
+ Comment.make.author.class.should == Person
+ end
+
+ describe "on a has_many association" do
+ before do
+ Post.blueprint { }
+ Comment.blueprint { post }
+ @post = Post.make
+ @comment = @post.comments.make
+ end
+
+ it "should save the created object" do
+ @comment.should_not be_new_record
+ end
+
+ it "should set the parent association on the created object" do
+ @comment.post.should == @post
+ end
+ end
+ end
+
+ describe "plan method" do
+ it "should not save the constructed object" do
+ person_count = Person.count
+ Person.blueprint { }
+ person = Person.plan
+ Person.count.should == person_count
+ end
+
+ it "should create an object through a belongs_to association, and return its id" do
+ Post.blueprint { }
+ Comment.blueprint { post }
+ post_count = Post.count
+ comment = Comment.plan
+ Post.count.should == post_count + 1
+ comment[:post].should be_nil
+ comment[:post_id].should_not be_nil
+ end
+
+ describe "on a has_many association" do
+ before do
+ Post.blueprint { }
+ Comment.blueprint do
+ post
+ body { "Test" }
+ end
+ @post = Post.make
+ @post_count = Post.count
+ @comment = @post.comments.plan
+ end
+
+ it "should not include the parent in the returned hash" do
+ @comment[:post].should be_nil
+ @comment[:post_id].should be_nil
+ end
+
+ it "should not create an extra parent object" do
+ Post.count.should == @post_count
+ end
+ end
+ end
+
+ describe "make_unsaved method" do
+ it "should not save the constructed object" do
+ Person.blueprint { }
+ person = Person.make_unsaved
+ person.should be_new_record
+ end
+
+ it "should not save associated objects" do
+ Post.blueprint { }
+ Comment.blueprint { post }
+ comment = Comment.make_unsaved
+ comment.post.should be_new_record
+ end
+
+ it "should save objects made within a passed-in block" do
+ Post.blueprint { }
+ Comment.blueprint { }
+ comment = nil
+ post = Post.make_unsaved { comment = Comment.make }
+ post.should be_new_record
+ comment.should_not be_new_record
+ end
+ end
+
+ describe "named_blueprint method" do
+ it "should list all the named blueprints" do
+ Person.blueprint(:foo){}
+ Person.blueprint(:bar){}
+ Person.named_blueprints.to_set.should == [:foo, :bar].to_set
+ end
+
+ it "should not list master blueprint" do
+ Person.blueprint(:foo){}
+ Person.blueprint {} # master
+ Person.named_blueprints.should == [:foo]
+ end
+ end
+
+ describe "clear_blueprints! method" do
+ it "should clear the list of blueprints" do
+ Person.blueprint(:foo){}
+ Person.clear_blueprints!
+ Person.named_blueprints.should == []
+ end
+
+ it "should clear master blueprint too" do
+ Person.blueprint(:foo) {}
+ Person.blueprint {} # master
+ Person.clear_blueprints!
+ lambda { Person.make }.should raise_error(RuntimeError)
+ end
+ end
+
+ end # ActiveRecord support
+
+end
diff --git a/vendor/plugins/machinist/spec/sham_spec.rb b/vendor/plugins/machinist/spec/sham_spec.rb
new file mode 100644
index 0000000..e9f7c74
--- /dev/null
+++ b/vendor/plugins/machinist/spec/sham_spec.rb
@@ -0,0 +1,61 @@
+require File.dirname(__FILE__) + '/spec_helper'
+require 'sham'
+
+describe Sham do
+ it "should ensure generated values are unique" do
+ Sham.clear
+ Sham.half_index {|index| index/2 }
+ values = (1..10).map { Sham.half_index }
+ values.should == (0..9).to_a
+ end
+
+ it "should generate non-unique values when asked" do
+ Sham.clear
+ Sham.coin_toss(:unique => false) {|index| index % 2 == 1 ? 'heads' : 'tails' }
+ values = (1..4).map { Sham.coin_toss }
+ values.should == ['heads', 'tails', 'heads', 'tails']
+ end
+
+ it "should generate more than a dozen values" do
+ Sham.clear
+ Sham.index {|index| index }
+ values = (1..25).map { Sham.index }
+ values.should == (1..25).to_a
+ end
+
+ it "should generate the same sequence of values after a reset" do
+ Sham.clear
+ Sham.random { rand }
+ values1 = (1..10).map { Sham.random }
+ Sham.reset
+ values2 = (1..10).map { Sham.random }
+ values2.should == values1
+ end
+
+ it "should die when it runs out of unique values" do
+ Sham.clear
+ Sham.limited {|index| index%10 }
+ lambda {
+ (1..100).map { Sham.limited }
+ }.should raise_error(RuntimeError)
+ end
+
+ it "should allow over-riding the name method" do
+ Sham.clear
+ Sham.name {|index| index }
+ Sham.name.should == 1
+ end
+
+ describe "define method" do
+ it "should repeat messages in its block to Sham" do
+ block = Proc.new {}
+ Sham.should_receive(:name).with(&block).once.ordered
+ Sham.should_receive(:slug).with(:arg, &block).once.ordered
+ Sham.define do
+ name &block
+ slug :arg, &block
+ end
+ end
+ end
+
+end
diff --git a/vendor/plugins/machinist/spec/spec_helper.rb b/vendor/plugins/machinist/spec/spec_helper.rb
new file mode 100644
index 0000000..0778363
--- /dev/null
+++ b/vendor/plugins/machinist/spec/spec_helper.rb
@@ -0,0 +1,14 @@
+$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
+require 'test/unit'
+require 'spec'
+require 'active_record'
+require 'sham'
+
+config = YAML::load(IO.read(File.dirname(__FILE__) + "/db/database.yml"))
+ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/log/test.log")
+ActiveRecord::Base.establish_connection(config['test'])
+load(File.dirname(__FILE__) + "/db/schema.rb")
+
+Spec::Runner.configure do |config|
+ config.before(:each) { Sham.reset }
+end