Commit 782e8f5ce33e7dfb6f2d7a2bc870d973a2639ddd

Authored by Pius Uzamere
1 parent c68e0352

assorted bugs, plus support for http auth

README.markdown
1 -Heroku's Suspenders  
2 --------------------  
3 -  
4 -thoughtbot's Suspenders modified for Heroku.  
5 -  
6 - git clone git://github.com/dancroak/heroku_suspenders.git  
7 - cd heroku_suspenders  
8 - ./script/create_project project_name  
9 -  
10 -This will create a Rails 2.3.2 app with Heroku-recommended code:  
11 -  
12 -* Paperclip for file uploads, set for Amazon S3  
13 -* Gmail SMTP for email  
14 -* Delayed Job for background processing  
15 -* Hoptoad Notifier for exception notification  
16 -* Google Analytics for usage analytics  
17 -  
18 -... and some other opinions:  
19 -  
20 -* jQuery for Javascript and Ajax  
21 -* Clearance for authentication  
22 -* Active Merchant for payment processing  
23 -* Cucumber, Shoulda, Factory Girl, Mocha, Fakeweb, & Timecop for testing  
24 -* Inherited Resources for RESTful controllers  
25 -* Formtastic for form builders  
26 -* Flutie for CSS framework  
27 -* Blitz for features, model, controller, & helper generators  
28 -  
29 -If you don't have all the necessary gems, they will be installed.  
30 -  
31 -Get the latest & greatest at anytime with:  
32 -  
33 - rake suspenders:pull  
34 -  
35 -A helper rake task will prompt you for all your production config vars (S3  
36 -keys, GMail account, Hoptoad API key...) and set them on your Heroku app:  
37 -  
38 - rake heroku:setup  
39 -  
40 -More details available in doc/README_FOR_TEMPLATE.  
41 -  
42 -Mascot  
43 -------  
44 -  
45 -The official Suspenders mascot is Suspenders Boy:  
46 -  
47 -![Suspenders Boy](http://media.tumblr.com/1TEAMALpseh5xzf0Jt6bcwSMo1_400.png) 1 +Pairwise 2.0
  2 +====================
48 3
  4 +Clean version, test-driven.
49 \ No newline at end of file 5 \ No newline at end of file
app/controllers/questions_controller.rb
1 -class QuestionsController < ApplicationController  
2 - # GET /questions  
3 - # GET /questions.xml  
4 - def index  
5 - @questions = Question.all 1 +class QuestionsController < InheritedResources::Base
  2 + respond_to :xml, :json
  3 + belongs_to :site, :optional => true
  4 + #has_scope :voted_on_by
6 5
7 - respond_to do |format|  
8 - format.html # index.html.erb  
9 - format.xml { render :xml => @questions }  
10 - end  
11 - end  
12 -  
13 - # GET /questions/1  
14 - # GET /questions/1.xml  
15 def show 6 def show
16 - @question = Question.find(params[:id])  
17 -  
18 - respond_to do |format|  
19 - format.html # show.html.erb  
20 - format.xml { render :xml => @question }  
21 - end  
22 - end  
23 -  
24 - # GET /questions/new  
25 - # GET /questions/new.xml  
26 - def new  
27 - @question = Question.new  
28 -  
29 - respond_to do |format|  
30 - format.html # new.html.erb  
31 - format.xml { render :xml => @question } 7 + show! do |format|
  8 + session['prompts_ids'] ||= []
  9 + format.xml {
  10 + render :xml => @question.to_xml(:methods => [:item_count, :left_choice_text, :right_choice_text, :picked_prompt_id, :votes_count, :creator_id])
  11 + }
32 end 12 end
33 end 13 end
34 -  
35 - # GET /questions/1/edit  
36 - def edit  
37 - @question = Question.find(params[:id])  
38 - end  
39 -  
40 - # POST /questions  
41 - # POST /questions.xml 14 +
42 def create 15 def create
43 - @question = Question.new(params[:question])  
44 -  
45 - respond_to do |format|  
46 - if @question.save  
47 - flash[:notice] = 'Question was successfully created.'  
48 - format.html { redirect_to(@question) }  
49 - format.xml { render :xml => @question, :status => :created, :location => @question }  
50 - else  
51 - format.html { render :action => "new" }  
52 - format.xml { render :xml => @question.errors, :status => :unprocessable_entity } 16 + authenticate
  17 + if @question = current_user.create_question(params[:visitor_identifier], params.except[:visitor_identifier])
  18 + respond_to do |format|
  19 + format.xml { render :xml => @question.to_xml}
53 end 20 end
54 - end  
55 - end  
56 -  
57 - # PUT /questions/1  
58 - # PUT /questions/1.xml  
59 - def update  
60 - @question = Question.find(params[:id])  
61 -  
62 - respond_to do |format|  
63 - if @question.update_attributes(params[:question])  
64 - flash[:notice] = 'Question was successfully updated.'  
65 - format.html { redirect_to(@question) }  
66 - format.xml { head :ok }  
67 - else  
68 - format.html { render :action => "edit" }  
69 - format.xml { render :xml => @question.errors, :status => :unprocessable_entity } 21 + else
  22 + respond_to do |format|
  23 + format.xml { render :xml => @question.errors.to_xml}
70 end 24 end
71 end 25 end
72 end 26 end
73 -  
74 - # DELETE /questions/1  
75 - # DELETE /questions/1.xml  
76 - def destroy  
77 - @question = Question.find(params[:id])  
78 - @question.destroy  
79 -  
80 - respond_to do |format|  
81 - format.html { redirect_to(questions_url) }  
82 - format.xml { head :ok }  
83 - end  
84 - end 27 +
85 end 28 end
app/models/choice.rb
@@ -11,8 +11,10 @@ class Choice &lt; ActiveRecord::Base @@ -11,8 +11,10 @@ class Choice &lt; ActiveRecord::Base
11 11
12 after_create :generate_prompts 12 after_create :generate_prompts
13 def before_create 13 def before_create
14 - @item = Item.create!(:creator => creator, :data => data)  
15 - self.item = @item 14 + unless item
  15 + @item = Item.create!(:creator => creator, :data => data)
  16 + self.item = @item
  17 + end
16 end 18 end
17 19
18 protected 20 protected
app/models/item.rb
@@ -13,4 +13,5 @@ class Item &lt; ActiveRecord::Base @@ -13,4 +13,5 @@ class Item &lt; ActiveRecord::Base
13 # has_and_belongs_to_many :prompt_requests 13 # has_and_belongs_to_many :prompt_requests
14 14
15 validates_presence_of :creator_id 15 validates_presence_of :creator_id
  16 + validates_presence_of :data, :on => :create, :message => "can't be blank"
16 end 17 end
app/models/question.rb
@@ -2,7 +2,7 @@ class Question &lt; ActiveRecord::Base @@ -2,7 +2,7 @@ class Question &lt; ActiveRecord::Base
2 belongs_to :creator, :class_name => "Visitor", :foreign_key => "creator_id" 2 belongs_to :creator, :class_name => "Visitor", :foreign_key => "creator_id"
3 belongs_to :site, :class_name => "User", :foreign_key => "site_id" 3 belongs_to :site, :class_name => "User", :foreign_key => "site_id"
4 4
5 - has_many :choices 5 + has_many :choices, :order => 'score DESC'
6 has_many :prompts do 6 has_many :prompts do
7 def pick(algorithm = nil) 7 def pick(algorithm = nil)
8 if algorithm 8 if algorithm
@@ -12,7 +12,45 @@ class Question &lt; ActiveRecord::Base @@ -12,7 +12,45 @@ class Question &lt; ActiveRecord::Base
12 end 12 end
13 end 13 end
14 end 14 end
  15 + has_many :votes, :as => :voteable
  16 +
15 after_save :ensure_at_least_two_choices 17 after_save :ensure_at_least_two_choices
  18 +
  19 + def item_count
  20 + choice_count
  21 + end
  22 +
  23 + def choice_count
  24 + Choice.count(:all, :conditions => {:question_id => id})
  25 + end
  26 +
  27 + def votes_count
  28 + Vote.count(:all, :conditions => {:voteable_id => id, :voteable_type => 'Question'})
  29 + end
  30 +
  31 + def picked_prompt
  32 + prompts[rand(prompts.count-1)]#Prompt.find(picked_prompt_id)
  33 + end
  34 +
  35 + def left_choice_text(prompt = nil)
  36 + prompt ||= prompts.first#prompts.pick
  37 + picked_prompt.left_choice.item.data
  38 + end
  39 +
  40 + def right_choice_text(prompt = nil)
  41 + prompt ||= prompts.first
  42 + picked_prompt.right_choice.item.data
  43 + end
  44 +
  45 + def self.voted_on_by(u)
  46 + select {|z| z.voted_on_by_user?(u)}
  47 + end
  48 +
  49 + def voted_on_by_user?(u)
  50 + u.questions_voted_on.include? self
  51 + end
  52 +
  53 +
16 54
17 validates_presence_of :site, :on => :create, :message => "can't be blank" 55 validates_presence_of :site, :on => :create, :message => "can't be blank"
18 validates_presence_of :creator, :on => :create, :message => "can't be blank" 56 validates_presence_of :creator, :on => :create, :message => "can't be blank"
lib/clearance_http_auth.rb 0 → 100644
@@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
  1 +#http://gist.github.com/159604
  2 +
  3 +# Overload Clearance's `deny_access` methods to allow authentication with HTTP-Auth for eg. API access
  4 +# Modeled after technoweenie's restful_authentication
  5 +# http://github.com/technoweenie/restful-authentication/blob/7235d9150e8beb80a819923a4c871ef4069c6759/generators/authenticated/templates/authenticated_system.rb#L74-76
  6 +#
  7 +# In lib/clearance_http_auth.rb
  8 +
  9 +module Clearance
  10 + module Authentication
  11 +
  12 + module InstanceMethods
  13 +
  14 + def deny_access(flash_message = nil, opts = {})
  15 + store_location
  16 + flash[:failure] = flash_message if flash_message
  17 + respond_to do |format|
  18 + format.html { redirect_to new_session_url }
  19 + format.any(:json, :xml) do
  20 + authenticate_or_request_with_http_basic('Pairwise API') do |login, password|
  21 + @_current_user = ::User.authenticate(login, password)
  22 + end
  23 + end
  24 + end
  25 + end
  26 +
  27 + end
  28 +
  29 + end
  30 +end
0 \ No newline at end of file 31 \ No newline at end of file
spec/controllers/questions_controller_spec.rb
1 require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 1 require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2 2
3 describe QuestionsController do 3 describe QuestionsController do
4 -  
5 - def mock_question(stubs={})  
6 - @mock_question ||= mock_model(Question, stubs)  
7 - end  
8 -  
9 - describe "GET index" do  
10 - it "assigns all questions as @questions" do  
11 - Question.stub!(:find).with(:all).and_return([mock_question])  
12 - get :index  
13 - assigns[:questions].should == [mock_question]  
14 - end  
15 - end  
16 -  
17 - describe "GET show" do  
18 - it "assigns the requested question as @question" do  
19 - Question.stub!(:find).with("37").and_return(mock_question)  
20 - get :show, :id => "37"  
21 - assigns[:question].should equal(mock_question)  
22 - end  
23 - end  
24 -  
25 - describe "GET new" do  
26 - it "assigns a new question as @question" do  
27 - Question.stub!(:new).and_return(mock_question)  
28 - get :new  
29 - assigns[:question].should equal(mock_question)  
30 - end  
31 - end  
32 -  
33 - describe "GET edit" do  
34 - it "assigns the requested question as @question" do  
35 - Question.stub!(:find).with("37").and_return(mock_question)  
36 - get :edit, :id => "37"  
37 - assigns[:question].should equal(mock_question)  
38 - end  
39 - end  
40 -  
41 - describe "POST create" do  
42 -  
43 - describe "with valid params" do  
44 - it "assigns a newly created question as @question" do  
45 - Question.stub!(:new).with({'these' => 'params'}).and_return(mock_question(:save => true))  
46 - post :create, :question => {:these => 'params'}  
47 - assigns[:question].should equal(mock_question)  
48 - end  
49 -  
50 - it "redirects to the created question" do  
51 - Question.stub!(:new).and_return(mock_question(:save => true))  
52 - post :create, :question => {}  
53 - response.should redirect_to(question_url(mock_question))  
54 - end  
55 - end  
56 -  
57 - describe "with invalid params" do  
58 - it "assigns a newly created but unsaved question as @question" do  
59 - Question.stub!(:new).with({'these' => 'params'}).and_return(mock_question(:save => false))  
60 - post :create, :question => {:these => 'params'}  
61 - assigns[:question].should equal(mock_question)  
62 - end  
63 -  
64 - it "re-renders the 'new' template" do  
65 - Question.stub!(:new).and_return(mock_question(:save => false))  
66 - post :create, :question => {}  
67 - response.should render_template('new')  
68 - end  
69 - end  
70 -  
71 - end  
72 -  
73 - describe "PUT update" do  
74 -  
75 - describe "with valid params" do  
76 - it "updates the requested question" do  
77 - Question.should_receive(:find).with("37").and_return(mock_question)  
78 - mock_question.should_receive(:update_attributes).with({'these' => 'params'})  
79 - put :update, :id => "37", :question => {:these => 'params'}  
80 - end  
81 -  
82 - it "assigns the requested question as @question" do  
83 - Question.stub!(:find).and_return(mock_question(:update_attributes => true))  
84 - put :update, :id => "1"  
85 - assigns[:question].should equal(mock_question)  
86 - end  
87 -  
88 - it "redirects to the question" do  
89 - Question.stub!(:find).and_return(mock_question(:update_attributes => true))  
90 - put :update, :id => "1"  
91 - response.should redirect_to(question_url(mock_question))  
92 - end  
93 - end  
94 -  
95 - describe "with invalid params" do  
96 - it "updates the requested question" do  
97 - Question.should_receive(:find).with("37").and_return(mock_question)  
98 - mock_question.should_receive(:update_attributes).with({'these' => 'params'})  
99 - put :update, :id => "37", :question => {:these => 'params'}  
100 - end  
101 -  
102 - it "assigns the question as @question" do  
103 - Question.stub!(:find).and_return(mock_question(:update_attributes => false))  
104 - put :update, :id => "1"  
105 - assigns[:question].should equal(mock_question)  
106 - end  
107 -  
108 - it "re-renders the 'edit' template" do  
109 - Question.stub!(:find).and_return(mock_question(:update_attributes => false))  
110 - put :update, :id => "1"  
111 - response.should render_template('edit')  
112 - end  
113 - end  
114 -  
115 - end  
116 -  
117 - describe "DELETE destroy" do  
118 - it "destroys the requested question" do  
119 - Question.should_receive(:find).with("37").and_return(mock_question)  
120 - mock_question.should_receive(:destroy)  
121 - delete :destroy, :id => "37"  
122 - end  
123 -  
124 - it "redirects to the questions list" do  
125 - Question.stub!(:find).and_return(mock_question(:destroy => true))  
126 - delete :destroy, :id => "1"  
127 - response.should redirect_to(questions_url)  
128 - end  
129 - end  
130 - 4 +
  5 + # integrate_views
  6 + #
  7 + # def sign_in_as(user)
  8 + # @controller.current_user = user
  9 + # return user
  10 + # end
  11 + #
  12 + # before(:each) do
  13 + # sign_in_as(@user = Factory(:email_confirmed_user))
  14 + # end
  15 + #
  16 + # def mock_question(stubs={})
  17 + # @mock_question ||= mock_model(Question, stubs)
  18 + # end
  19 + #
  20 + # describe "GET index" do
  21 + # it "assigns all questions as @questions" do
  22 + # Question.stub!(:find).with(:all).and_return([mock_question])
  23 + # get :index
  24 + # assigns[:questions].should == [mock_question]
  25 + # end
  26 + # end
  27 + #
  28 + # describe "GET show" do
  29 + # it "assigns the requested question as @question" do
  30 + # Question.stub!(:find).with("37").and_return(mock_question)
  31 + # get :show, :id => "37"
  32 + # assigns[:question].should equal(mock_question)
  33 + # end
  34 + # end
  35 + #
  36 + # describe "GET new" do
  37 + # it "assigns a new question as @question" do
  38 + # Question.stub!(:new).and_return(mock_question)
  39 + # get :new
  40 + # assigns[:question].should equal(mock_question)
  41 + # end
  42 + # end
  43 + #
  44 + # describe "GET edit" do
  45 + # it "assigns the requested question as @question" do
  46 + # Question.stub!(:find).with("37").and_return(mock_question)
  47 + # get :edit, :id => "37"
  48 + # assigns[:question].should equal(mock_question)
  49 + # end
  50 + # end
  51 + #
  52 + # describe "POST create" do
  53 + #
  54 + # describe "with valid params" do
  55 + # it "assigns a newly created question as @question" do
  56 + # Question.stub!(:new).with({'these' => 'params'}).and_return(mock_question(:save => true))
  57 + # post :create, :question => {:these => 'params'}
  58 + # assigns[:question].should equal(mock_question)
  59 + # end
  60 + #
  61 + # it "redirects to the created question" do
  62 + # Question.stub!(:new).and_return(mock_question(:save => true))
  63 + # post :create, :question => {}
  64 + # response.should redirect_to(question_url(mock_question))
  65 + # end
  66 + # end
  67 + #
  68 + # describe "with invalid params" do
  69 + # it "assigns a newly created but unsaved question as @question" do
  70 + # Question.stub!(:new).with({'these' => 'params'}).and_return(mock_question(:save => false))
  71 + # post :create, :question => {:these => 'params'}
  72 + # assigns[:question].should equal(mock_question)
  73 + # end
  74 + #
  75 + # it "re-renders the 'new' template" do
  76 + # Question.stub!(:new).and_return(mock_question(:save => false))
  77 + # post :create, :question => {}
  78 + # response.should render_template('new')
  79 + # end
  80 + # end
  81 + #
  82 + # end
  83 + #
  84 + # describe "PUT update" do
  85 + #
  86 + # describe "with valid params" do
  87 + # it "updates the requested question" do
  88 + # Question.should_receive(:find).with("37").and_return(mock_question)
  89 + # mock_question.should_receive(:update_attributes).with({'these' => 'params'})
  90 + # put :update, :id => "37", :question => {:these => 'params'}
  91 + # end
  92 + #
  93 + # it "assigns the requested question as @question" do
  94 + # Question.stub!(:find).and_return(mock_question(:update_attributes => true))
  95 + # put :update, :id => "1"
  96 + # assigns[:question].should equal(mock_question)
  97 + # end
  98 + #
  99 + # it "redirects to the question" do
  100 + # Question.stub!(:find).and_return(mock_question(:update_attributes => true))
  101 + # put :update, :id => "1"
  102 + # response.should redirect_to(question_url(mock_question))
  103 + # end
  104 + # end
  105 + #
  106 + # describe "with invalid params" do
  107 + # it "updates the requested question" do
  108 + # Question.should_receive(:find).with("37").and_return(mock_question)
  109 + # mock_question.should_receive(:update_attributes).with({'these' => 'params'})
  110 + # put :update, :id => "37", :question => {:these => 'params'}
  111 + # end
  112 + #
  113 + # it "assigns the question as @question" do
  114 + # Question.stub!(:find).and_return(mock_question(:update_attributes => false))
  115 + # put :update, :id => "1"
  116 + # assigns[:question].should equal(mock_question)
  117 + # end
  118 + #
  119 + # it "re-renders the 'edit' template" do
  120 + # Question.stub!(:find).and_return(mock_question(:update_attributes => false))
  121 + # put :update, :id => "1"
  122 + # response.should render_template('edit')
  123 + # end
  124 + # end
  125 + #
  126 + # end
  127 + #
  128 + # describe "DELETE destroy" do
  129 + # it "destroys the requested question" do
  130 + # Question.should_receive(:find).with("37").and_return(mock_question)
  131 + # mock_question.should_receive(:destroy)
  132 + # delete :destroy, :id => "37"
  133 + # end
  134 + #
  135 + # it "redirects to the questions list" do
  136 + # Question.stub!(:find).and_return(mock_question(:destroy => true))
  137 + # delete :destroy, :id => "1"
  138 + # response.should redirect_to(questions_url)
  139 + # end
  140 + # end
  141 +
131 end 142 end
spec/models/choice_spec.rb
@@ -13,7 +13,8 @@ describe Choice do @@ -13,7 +13,8 @@ describe Choice do
13 13
14 @valid_attributes = { 14 @valid_attributes = {
15 :creator => @johndoe, 15 :creator => @johndoe,
16 - :question => @question 16 + :question => @question,
  17 + :data => 'hi there'
17 } 18 }
18 end 19 end
19 20
spec/models/item_spec.rb
@@ -3,13 +3,15 @@ require File.expand_path(File.dirname(__FILE__) + &#39;/../spec_helper&#39;) @@ -3,13 +3,15 @@ require File.expand_path(File.dirname(__FILE__) + &#39;/../spec_helper&#39;)
3 describe Item do 3 describe Item do
4 it {should belong_to :creator} 4 it {should belong_to :creator}
5 it {should belong_to :site} 5 it {should belong_to :site}
  6 + it {should validate_presence_of :data}
6 7
7 before(:each) do 8 before(:each) do
8 @aoi_clone = Factory.create(:user, :email => "pius@alum.mit.edu", :password => "password", :password_confirmation => "password", :id => 8) 9 @aoi_clone = Factory.create(:user, :email => "pius@alum.mit.edu", :password => "password", :password_confirmation => "password", :id => 8)
9 @johndoe = Factory.create(:visitor, :identifier => 'johndoe', :site => @aoi_clone) 10 @johndoe = Factory.create(:visitor, :identifier => 'johndoe', :site => @aoi_clone)
10 @valid_attributes = { 11 @valid_attributes = {
11 :site => @aoi_clone, 12 :site => @aoi_clone,
12 - :creator => @johndoe 13 + :creator => @johndoe,
  14 + :data => 'a widget'
13 } 15 }
14 end 16 end
15 17