From e2f442889eceb0c78d8b40622f7b00b792d2b36f Mon Sep 17 00:00:00 2001 From: Jason Denney Date: Sun, 9 Mar 2014 22:30:41 -0400 Subject: [PATCH] Added tasks controller, spec, JS, apitesthelper Had an issue with calling render_views in the specs to render the jbuilder json templates and also getting the devise sign_in method to work, ended up adding a helper to add the username/password for a user --- app/assets/javascripts/task_lists.js | 32 ++++++ app/controllers/api/v1/tasks_controller.rb | 64 +++++++++++ app/views/api/v1/tasks/update.json.jbuilder | 3 + config/routes.rb | 2 + spec/controllers/api/bikes_controller_spec.rb | 1 - spec/controllers/api/tasks_controller.rb | 100 ++++++++++++++++++ spec/factories/tasks.rb | 7 ++ spec/factories/users.rb | 4 + spec/spec_helper.rb | 2 +- spec/support/api_test_helpers.rb | 5 + 10 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/task_lists.js create mode 100644 app/controllers/api/v1/tasks_controller.rb create mode 100644 app/views/api/v1/tasks/update.json.jbuilder create mode 100644 spec/controllers/api/tasks_controller.rb create mode 100644 spec/factories/tasks.rb create mode 100644 spec/support/api_test_helpers.rb diff --git a/app/assets/javascripts/task_lists.js b/app/assets/javascripts/task_lists.js new file mode 100644 index 0000000..b17d8d4 --- /dev/null +++ b/app/assets/javascripts/task_lists.js @@ -0,0 +1,32 @@ +$(".task_list_task").click(function(){ + $("#update_tasks_submit").removeClass("disabled"); +}); + +$("#update_tasks_submit").click(function(){ + + tasks = []; + $(".task_list_task").each(function(){ + tasks.push({ + id: parseInt($(this).data("id")), + done: $(this).is(":checked") + }); + }); + + json_data = { tasks: tasks }; + + $.ajax({ + url: $("#update_tasks_submit").data("url"), + type: "PUT", + data: JSON.stringify(json_data), + contentType: 'application/json', + dataType: "json", + success: function(data, status, xhr){ + //should re-render via JS, but for now reload + location.reload(); + }, + error: function(data, status ){ + alert("An error occured updating tasks"); + //displayFormErrors(data.responseJSON); + } + }); +}); diff --git a/app/controllers/api/v1/tasks_controller.rb b/app/controllers/api/v1/tasks_controller.rb new file mode 100644 index 0000000..9bb8562 --- /dev/null +++ b/app/controllers/api/v1/tasks_controller.rb @@ -0,0 +1,64 @@ +class Api::V1::TasksController < Api::V1::BaseController + EXPECTED_TASKS = "Expected a list of tasks in submitted data." + CANNOT_MANAGE = "You do not have permission to manage this task." + NOT_FOUND = "The task could not be found." + + before_filter :validate_params + before_filter :get_tasks + before_filter :check_task_permission, except: :show + + def update + errors = [] + @tasks.each do |task_hash| + task = task_hash[:record] + attrs = task_hash[:new_attributes] + task.update_attributes(attrs) + if !task.errors.empty? + errors << { id: task.id, errors: task.errors } + end + end + + if !errors.empty? + render json: { errors: errors }, status: 422 and return + end + end + + private + def validate_params + if params[:tasks].nil? and not params[:tasks].kind_of?(Array) + render json: { errors: [EXPECTED_TASKS]}, status: 422 and return + end + end + + def get_tasks + @tasks = [] + errors = [] + + params[:tasks].each do |task| + t = Task.find_by_id(task[:id]) + if t.nil? + errors << { id: task[:id], error: NOT_FOUND } + else + @tasks << { record: t, new_attributes: task } + end + end + + if !errors.empty? + render json: { errors: errors }, status: 404 and return + end + end + + def check_task_permission + errors = [] + @tasks.each do |task_hash| + task = task_hash[:record] + if task.task_list.item != current_user.bike + errors << { id: task[:id], error: CANNOT_MANAGE } + end + end + + if cannot? :manage, Bike and !errors.empty? + render json: { errors: errors}, status: 403 and return + end + end +end diff --git a/app/views/api/v1/tasks/update.json.jbuilder b/app/views/api/v1/tasks/update.json.jbuilder new file mode 100644 index 0000000..0dffca7 --- /dev/null +++ b/app/views/api/v1/tasks/update.json.jbuilder @@ -0,0 +1,3 @@ +json.tasks [@tasks.map{|x| x[:record]}] do |task| + json.array! task +end diff --git a/config/routes.rb b/config/routes.rb index 48f8990..a9d9627 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,6 +24,8 @@ Velocipede::Application.routes.draw do post 'bikes/create' => "bikes#create", as: "api_create_bike" get 'task_lists/:id' => "task_lists#show", as: "api_task_list" + + put 'tasks/update' => "tasks#update", as: "api_update_task" end end diff --git a/spec/controllers/api/bikes_controller_spec.rb b/spec/controllers/api/bikes_controller_spec.rb index e9e2338..3706aa7 100644 --- a/spec/controllers/api/bikes_controller_spec.rb +++ b/spec/controllers/api/bikes_controller_spec.rb @@ -57,7 +57,6 @@ describe Api::V1::BikesController do it "returns 200" do post :create, @submit_json - puts @response.inspect expect(@response.code.to_i).to eql 200 end diff --git a/spec/controllers/api/tasks_controller.rb b/spec/controllers/api/tasks_controller.rb new file mode 100644 index 0000000..cc0e219 --- /dev/null +++ b/spec/controllers/api/tasks_controller.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe Api::V1::TasksController do + render_views + + describe "#update" do + context "as a user with a bike" do + before(:each) do + @user = FactoryGirl.create(:user_with_bike) + sign_in @user + end + + context "with no tasks in json data" do + it "returns 400" do + put :update + expect(@response.code.to_i).to eql 422 + end + + it "returns an error message" do + put :update + json = JSON.parse(@response.body) + expect(json["errors"].first).to eql Api::V1::TasksController::EXPECTED_TASKS + end + end + + context "with non existing task in data" do + before(:each) do + @submit_json = { tasks: [ + { id: 9999, done: false }, + ]} + end + + it "returns 404" do + put :update, @submit_json + expect(@response.code.to_i).to eql 404 + end + + it "returns an error message" do + put :update, @submit_json + json = JSON.parse(@response.body) + expect(json["errors"].to_s).to include(Api::V1::TasksController::NOT_FOUND) + end + end + + context "with valid tasks json data" do + before(:each) do + @task = FactoryGirl.create(:task, done: false, task_list_id: @user.bike.task_list.id) + task_data = { tasks: [ + { id: @task.id, done: true}, + ]} + #this is necessary because render_views does not work with sign_in devise helper + @submit_json = api_submit_json(@user, task_data) + #not sure why format: :json not working + request.accept = 'application/json' + end + + it "returns 200" do + put :update, @submit_json + expect(@response.code.to_i).to eql 200 + end + + it "returns the updated task json" do + put :update, @submit_json + json = JSON.parse(@response.body) + expect(json).to have_key("tasks") + expect(json.to_s).to include(Task.find_by_id(@task.id).done.to_s) + end + + it "updates the task" do + expect{put :update, @submit_json}. + to change{ Task.find_by_id(@task.id).done }. + from(false).to(true) + end + end + end + + context "as a user without a bike" do + before(:each) do + @user = FactoryGirl.create(:user, bike_id: nil) + sign_in @user + + task = FactoryGirl.create(:task) + @submit_json = { tasks: [ + { id: task.id, done: false }, + ]} + end + + it "should return 403" do + put :update, @submit_json + expect(@response.code.to_i).to eql 403 + end + + it "returns an error message" do + put :update, @submit_json + json = JSON.parse(@response.body) + expect(json["errors"].to_s).to include(Api::V1::TasksController::CANNOT_MANAGE) + end + end + end +end diff --git a/spec/factories/tasks.rb b/spec/factories/tasks.rb new file mode 100644 index 0000000..1b83704 --- /dev/null +++ b/spec/factories/tasks.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :task do + task_list { FactoryGirl.create(:task_list) } + done false + task { Faker::Lorem.words(7).join(" ")} + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index e29025d..b75147c 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -36,5 +36,9 @@ FactoryGirl.define do end end + factory :user_with_bike do + bike { FactoryGirl.create(:bike) } + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 797cabf..669eb41 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -68,6 +68,7 @@ Spork.prefork do config.infer_base_class_for_anonymous_controllers = false config.include FactoryGirlStepHelpers config.include FactoryGirl::Syntax::Methods + config.include ApiTestHelpers config.include Devise::TestHelpers, type: :controller #allows :focus tag to only run specific tests @@ -80,7 +81,6 @@ end Spork.each_run do # This code will be run each time you run your specs. - #Reload Factories #FYI make sure that Factories use strings instead of class constants FactoryGirl.reload diff --git a/spec/support/api_test_helpers.rb b/spec/support/api_test_helpers.rb new file mode 100644 index 0000000..c164fb1 --- /dev/null +++ b/spec/support/api_test_helpers.rb @@ -0,0 +1,5 @@ +module ApiTestHelpers + def api_submit_json(user, json_hash) + json_hash.merge({username: user.email, password: user.password}) + end +end