Commit 8139c736285a557e836679af39012327f8c3ae7a

Authored by Dhruv Kapadia
1 parent 87857f3f

Refactoring question show code

app/controllers/questions_controller.rb
1 require 'fastercsv' 1 require 'fastercsv'
2 2
3 class QuestionsController < InheritedResources::Base 3 class QuestionsController < InheritedResources::Base
  4 + actions :all, :except => [ :show ]
4 before_filter :authenticate 5 before_filter :authenticate
5 respond_to :xml, :json 6 respond_to :xml, :json
6 respond_to :csv, :only => :export #leave the option for xml export here 7 respond_to :csv, :only => :export #leave the option for xml export here
@@ -55,61 +56,27 @@ class QuestionsController &lt; InheritedResources::Base @@ -55,61 +56,27 @@ class QuestionsController &lt; InheritedResources::Base
55 56
56 def show 57 def show
57 @question = Question.find(params[:id]) 58 @question = Question.find(params[:id])
58 - visitor_identifier = params[:visitor_identifier]  
59 - unless params[:barebones]  
60 -  
61 - @p = @question.choose_prompt(:algorithm => params[:algorithm])  
62 -  
63 - if @p.nil?  
64 - # could not find a prompt to choose  
65 - # I don't know the best http code for the api to respond, but 409 seems the closes  
66 - respond_to do |format|  
67 - format.xml { render :xml => @question, :status => :conflict and return}  
68 - format.json { render :json => @question, :status => :conflict and return}  
69 - end  
70 59
71 - end  
72 - # @question.create_new_appearance - returns appearance, which we can then get the prompt from.  
73 - # At the very least add 'if create_appearance is true,  
74 - # we sometimes request a question when no prompt is displayed  
75 - # TODO It would be a good idea to find these places and treat them like barebones  
76 - if !visitor_identifier.blank?  
77 - visitor = current_user.visitors.find_or_create_by_identifier(visitor_identifier)  
78 - @a = current_user.record_appearance(visitor, @p)  
79 - logger.info("creating appearance!")  
80 - else  
81 - @a = nil  
82 - end  
83 -  
84 - left_choice_text = Proc.new { |options| options[:builder].tag!('left_choice_text', @p.left_choice.item.data) }  
85 - right_choice_text = Proc.new { |options| options[:builder].tag!('right_choice_text', @p.right_choice.item.data) }  
86 - picked_prompt_id = Proc.new { |options| options[:builder].tag!('picked_prompt_id', @p.id) }  
87 - appearance_id = Proc.new { |options| options[:builder].tag!('appearance_id', @a.lookup) }  
88 -  
89 - visitor_votes = Proc.new { |options| options[:builder].tag!('visitor_votes', visitor.votes.count(:conditions => {:question_id => @question.id})) }  
90 - visitor_ideas = Proc.new { |options| options[:builder].tag!('visitor_ideas', visitor.items.count) }  
91 -  
92 - the_procs = [left_choice_text, right_choice_text, picked_prompt_id]  
93 -  
94 - if @a  
95 - the_procs << appearance_id  
96 - the_procs << visitor_votes  
97 - the_procs << visitor_ideas  
98 - end 60 + begin
  61 + @question_optional_information = @question.get_optional_information(params)
  62 + rescue RuntimeError
  63 + respond_to do |format|
  64 + format.xml { render :xml => @question.to_xml, :status => :conflict and return}
  65 + end
  66 + end
99 67
100 - show! do |format|  
101 - session['prompts_ids'] ||= []  
102 - format.xml {  
103 - render :xml => @question.to_xml(:methods => [:item_count], :procs => the_procs)  
104 - }  
105 - end  
106 - else  
107 - show! do |format|  
108 - session['prompts_ids'] ||= []  
109 - format.xml {  
110 - render :xml => @question.to_xml(:methods => [:item_count])  
111 - }  
112 - end 68 + optional_information = []
  69 + @question_optional_information.each do |key, value|
  70 + optional_information << Proc.new { |options| options[:builder].tag!(key, value)}
  71 + end
  72 +
  73 + respond_to do |format|
  74 + format.xml {
  75 + render :xml => @question.to_xml(:methods => [:item_count], :procs => optional_information)
  76 + }
  77 + format.js{
  78 + render :json => @question.to_json(:methods => [:item_count], :procs => optional_information)
  79 + }
113 end 80 end
114 end 81 end
115 82
app/models/question.rb
@@ -33,7 +33,7 @@ class Question &lt; ActiveRecord::Base @@ -33,7 +33,7 @@ class Question &lt; ActiveRecord::Base
33 33
34 # if there is one or fewer active choices, we won't be able to find a prompt 34 # if there is one or fewer active choices, we won't be able to find a prompt
35 if self.choices.size - self.inactive_choices_count <= 1 35 if self.choices.size - self.inactive_choices_count <= 1
36 - return nil 36 + raise RuntimeError, "More than one choice needs to be active"
37 end 37 end
38 38
39 if self.uses_catchup? || options[:algorithm] == "catchup" 39 if self.uses_catchup? || options[:algorithm] == "catchup"
@@ -110,6 +110,37 @@ class Question &lt; ActiveRecord::Base @@ -110,6 +110,37 @@ class Question &lt; ActiveRecord::Base
110 weights 110 weights
111 end 111 end
112 112
  113 + def get_optional_information(params)
  114 +
  115 + return {} if params.nil?
  116 +
  117 + result = {}
  118 + visitor_identifier = params[:visitor_identifier]
  119 + current_user = self.site
  120 +
  121 + if params[:with_prompt]
  122 + @prompt = choose_prompt(:algorithm => params[:algorithm])
  123 + result.merge!({:picked_prompt_id => @prompt.id})
  124 +
  125 + if params[:with_appearance] && visitor_identifier.present?
  126 + visitor = current_user.visitors.find_or_create_by_identifier(visitor_identifier)
  127 + @appearance = current_user.record_appearance(visitor, @prompt)
  128 + result.merge!({:appearance_id => @appearance.lookup})
  129 + else
  130 + # throw some error
  131 + end
  132 + end
  133 +
  134 + if params[:with_visitor_stats]
  135 + visitor = current_user.visitors.find_or_create_by_identifier(visitor_identifier)
  136 + result.merge!(:visitor_votes => visitor.votes.count(:conditions => {:question_id => self.id}))
  137 + result.merge!(:visitor_ideas => visitor.items.count)
  138 + end
  139 +
  140 + return result
  141 +
  142 + end
  143 +
113 def normalize!(weighted) 144 def normalize!(weighted)
114 if weighted.instance_of?(Hash) 145 if weighted.instance_of?(Hash)
115 sum = weighted.inject(0) do |sum, item_and_weight| 146 sum = weighted.inject(0) do |sum, item_and_weight|
spec/controllers/questions_controller_spec.rb
@@ -10,108 +10,29 @@ describe QuestionsController do @@ -10,108 +10,29 @@ describe QuestionsController do
10 end 10 end
11 # 11 #
12 before(:each) do 12 before(:each) do
13 - sign_in_as(@user = Factory(:email_confirmed_user)) 13 + @user = Factory.create(:user, :email => "pius@alum.mit.edu", :password => "password", :password_confirmation => "password", :id => 8)
  14 + sign_in_as(@user = Factory(:email_confirmed_user))
  15 + @question = @user.create_question("foobarbaz", {:name => 'foo'})
14 end 16 end
15 - #  
16 - def mock_question(stubs={})  
17 - @mock_question ||= mock_model(Question, stubs)  
18 - end  
19 -  
20 - def mock_prompt(stubs={})  
21 - @mock_prompt ||= mock_model(Prompt, stubs)  
22 - end  
23 -  
24 - def mock_appearance(stubs={})  
25 - @mock_appearance||= mock_model(Appearance, stubs)  
26 - end  
27 -  
28 - def mock_visitor(stubs={})  
29 - @mock_visitor||= mock_model(Visitor, stubs)  
30 - end  
31 - #  
32 - # describe "GET index" do  
33 - # it "assigns all questions as @questions" do  
34 - # Question.stub!(:find).with(:all).and_return([mock_question])  
35 - # get :index  
36 - # assigns[:questions].should == [mock_question]  
37 - # end  
38 - # end  
39 - #  
40 - describe "GET show normal" do  
41 - before(:each) do  
42 - Question.stub!(:find).with("37").and_return(mock_question)  
43 - end  
44 -  
45 -  
46 - it "assigns the requested question as @question" do  
47 - Question.stub!(:find).with("37").and_return(mock_question)  
48 - mock_question.should_receive(:choose_prompt).and_return(mock_prompt)  
49 - #TODO it shouldn't call this unless we are generating an appearance, right?  
50 -  
51 - get :show, :id => "37"  
52 - assigns[:question].should equal(mock_question)  
53 - assigns[:p].should equal(mock_prompt)  
54 - assigns[:a].should be_nil  
55 - end 17 + it "responds with basic question information" do
  18 + get :show, :id => @question.id, :format => "xml"
56 19
57 -  
58 - it "does not create an appearance when the 'barebones' param is set" do  
59 - get :show, :id => "37", :barebones => true  
60 - assigns[:question].should equal(mock_question)  
61 - assigns[:p].should be_nil  
62 - assigns[:a].should be_nil  
63 - end  
64 -  
65 - describe "creates an appearance" do  
66 - before(:each) do  
67 - @visitor_identifier = "somelongunique32charstring"  
68 - #stub broken  
69 - visitor_list = [mock_visitor]  
70 - @user.stub!(:visitors).and_return(visitor_list)  
71 - visitor_list.stub!(:find_or_create_by_identifier).and_return(mock_visitor)  
72 - @user.stub!(:record_appearance).with(mock_visitor, mock_prompt).and_return(mock_appearance)  
73 - end  
74 -  
75 - #TODO this is not a particularly intutive param to pass in order to create an appearance  
76 - it "creates an appearance when a visitor identifier is a param" do  
77 - mock_question.should_receive(:choose_prompt).and_return(mock_prompt)  
78 - get :show, :id => "37", :visitor_identifier => @visitor_identifier  
79 - assigns[:question].should equal(mock_question)  
80 - assigns[:p].should equal(mock_prompt)  
81 - assigns[:a].should equal(mock_appearance)  
82 -  
83 - end  
84 -  
85 - it "does not create an appearance when the 'barebones' param is set, even when a visitor id is sent" do  
86 - get :show, :id => "37", :barebones => true, :visitor_identifier => @visitor_identifier  
87 - assigns[:question].should equal(mock_question)  
88 - assigns[:p].should be_nil  
89 - assigns[:a].should be_nil  
90 - end  
91 -  
92 - describe "calls catchup algorithm" do 20 + assigns[:question].should == @question
  21 + @response.body.should have_tag("question")
  22 + end
93 23
94 - #TODO Refactor out to use uses_catchup?  
95 - it "should pop prompt from cached queue using the catchup algorithm if params dictate" do  
96 - mock_question.should_receive(:choose_prompt).with(:algorithm => "catchup").and_return(mock_prompt)  
97 24
98 - get :show, :id => "37", :visitor_identifier => @visitor_identifier, :algorithm => "catchup"  
99 - assigns[:question].should equal(mock_question)  
100 - assigns[:p].should equal(mock_prompt)  
101 - assigns[:a].should equal(mock_appearance)  
102 - end 25 + it "responds with question with prompt and appearance and visitor information" do
  26 + get :show, :id => @question.id, :format => "xml", :with_appearance => true, :with_prompt => true, :with_visitor_stats => true, :visitor_identifier => "jim"
103 27
104 - it "should handle cache misses gracefully" do  
105 - mock_question.should_receive(:choose_prompt).with(:algorithm => "catchup").and_return(mock_prompt) 28 + assigns[:question].should == @question
  29 + #@response.body.should be_nil
  30 + @response.body.should have_tag("question")
  31 + @response.body.should have_tag("picked_prompt_id")
  32 + @response.body.should have_tag("appearance_id")
  33 + @response.body.should have_tag("visitor_votes")
  34 + @response.body.should have_tag("visitor_ideas")
106 35
107 - get :show, :id => "37", :visitor_identifier => @visitor_identifier, :algorithm => "catchup"  
108 - assigns[:question].should equal(mock_question)  
109 - assigns[:p].should equal(mock_prompt)  
110 - assigns[:a].should equal(mock_appearance)  
111 - end  
112 - end  
113 - end  
114 end 36 end
115 -  
116 37
117 end 38 end
spec/models/question_spec.rb
@@ -19,6 +19,8 @@ describe Question do @@ -19,6 +19,8 @@ describe Question do
19 :creator => @aoi_clone.default_visitor 19 :creator => @aoi_clone.default_visitor
20 20
21 } 21 }
  22 +
  23 + @question = @aoi_clone.create_question("foobarbaz", {:name => 'foo'})
22 24
23 end 25 end
24 26
@@ -60,7 +62,52 @@ describe Question do @@ -60,7 +62,52 @@ describe Question do
60 62
61 end 63 end
62 64
  65 + it "should return nil if optional parameters are empty" do
  66 + @question_optional_information = @question.get_optional_information(nil)
  67 + @question_optional_information.should be_empty
  68 + end
  69 +
  70 + it "should return nil if optional parameters are nil" do
  71 + params = {"id" => '37'}
  72 + @question_optional_information = @question.get_optional_information(params)
  73 + @question_optional_information.should be_empty
  74 + end
  75 +
  76 + it "should return a hash with an prompt id when optional parameters contains 'with_prompt'" do
  77 + params = {:id => 124, :with_prompt => true}
  78 + @question_optional_information = @question.get_optional_information(params)
  79 + @question_optional_information.should include(:picked_prompt_id)
  80 + @question_optional_information[:picked_prompt_id].should be_an_instance_of(Fixnum)
  81 + end
63 82
  83 + it "should return a hash with an appearance hash when optional parameters contains 'with_appearance'" do
  84 + params = {:id => 124, :with_prompt => true, :with_appearance=> true, :visitor_identifier => 'jim'}
  85 + @question_optional_information = @question.get_optional_information(params)
  86 + @question_optional_information.should include(:appearance_id)
  87 + @question_optional_information[:appearance_id].should be_an_instance_of(String)
  88 + end
  89 +
  90 + it "should return a hash with two visitor stats when optional parameters contains 'with_visitor_stats'" do
  91 + params = {:id => 124, :with_visitor_stats=> true, :visitor_identifier => "jim"}
  92 + @question_optional_information = @question.get_optional_information(params)
  93 + @question_optional_information.should include(:visitor_votes)
  94 + @question_optional_information.should include(:visitor_ideas)
  95 + @question_optional_information[:visitor_votes].should be_an_instance_of(Fixnum)
  96 + @question_optional_information[:visitor_ideas].should be_an_instance_of(Fixnum)
  97 + end
  98 +
  99 + it "should return a hash when optional parameters have more than one optional param " do
  100 + params = {:id => 124, :with_visitor_stats=> true, :visitor_identifier => "jim", :with_prompt => true, :with_appearance => true}
  101 + @question_optional_information = @question.get_optional_information(params)
  102 + @question_optional_information.should include(:visitor_votes)
  103 + @question_optional_information.should include(:visitor_ideas)
  104 + @question_optional_information[:visitor_votes].should be_an_instance_of(Fixnum)
  105 + @question_optional_information[:visitor_ideas].should be_an_instance_of(Fixnum)
  106 + @question_optional_information.should include(:picked_prompt_id)
  107 + @question_optional_information[:picked_prompt_id].should be_an_instance_of(Fixnum)
  108 + @question_optional_information.should include(:appearance_id)
  109 + @question_optional_information[:appearance_id].should be_an_instance_of(String)
  110 + end
64 111
65 context "catchup algorithm" do 112 context "catchup algorithm" do
66 before(:all) do 113 before(:all) do