diff --git a/app/controllers/api/v1/puzzles_controller.rb b/app/controllers/api/v1/puzzles_controller.rb index b909142..0120dc7 100644 --- a/app/controllers/api/v1/puzzles_controller.rb +++ b/app/controllers/api/v1/puzzles_controller.rb @@ -2,15 +2,24 @@ class Api::V1::PuzzlesController < ApplicationController skip_before_action :set_current_user, only: [:index] def index - zip_code = params[:zip_code] - users = User.where(zip_code:) if zip_code.present? + find_puzzles(params[:zip_code]) + raise NoPuzzlesException if @puzzles_in_zip_code.empty? + render json: PuzzleSerializer.new(@puzzles_in_zip_code) + end + + private - if users != [] - puzzles = Puzzle.where(user_id: users.pluck(:id)) - current_puzzles = puzzles.where.not(status: 3) - render json: PuzzleSerializer.new(current_puzzles) - elsif puzzles == [] || users == [] - render json: { error: "Puzzles not found in this area" }, status: "404" - end + def find_puzzles(zip_code) + @puzzles_in_zip_code = Puzzle.find_by_zip_code(zip_code) end end + +# Note to self: Saving this to remember process that lead me to final version: +#def index + # puzzles_in_zip_code = Puzzle.find_by_zip_code(params[:zip_code]) + # if @puzzles_in_zip_code.any? + # render json: PuzzleSerializer.new(@puzzles_in_zip_code) + # else + # raise NoPuzzlesException + # end +# end \ No newline at end of file diff --git a/app/controllers/api/v1/users/puzzles_controller.rb b/app/controllers/api/v1/users/puzzles_controller.rb index d2e60ac..7ca2e49 100644 --- a/app/controllers/api/v1/users/puzzles_controller.rb +++ b/app/controllers/api/v1/users/puzzles_controller.rb @@ -1,34 +1,37 @@ class Api::V1::Users::PuzzlesController < ApplicationController before_action :find_user + before_action :find_puzzle, only: [:update] def index render json: PuzzleSerializer.new(@user.puzzles) end def show - puzzle = @user.puzzles.find(params[:puzzle_id]) - render json: PuzzleSerializer.new(puzzle) + show_puzzle = @user.puzzles.find(params[:puzzle_id]) + render json: PuzzleSerializer.new(show_puzzle) end def create - puzzle = @user.puzzles.new(puzzle_params) - render json: PuzzleSerializer.new(puzzle), status: 201 if puzzle.save + new_puzzle = @user.puzzles.new(puzzle_params) + render json: PuzzleSerializer.new(new_puzzle), status: 201 if new_puzzle.save end def update - puzzle = Puzzle.find(params[:puzzle_id]) - - puzzle.update(puzzle_params) - render json: PuzzleSerializer.new(puzzle) + @puzzle.update(puzzle_params) + render json: PuzzleSerializer.new(@puzzle) end private def puzzle_params - params.permit(:status, :title, :description, :total_pieces, :notes, :puzzle_image_url) # did not include user_id + params.permit(:status, :title, :description, :total_pieces, :notes, :puzzle_image_url) # did not include user_id since this will not be updated & is available when created already end def find_user @user = User.find(params[:user_id]) end + + def find_puzzle + @puzzle = Puzzle.find(params[:puzzle_id]) + end end diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index cf6fc2c..54a4d4a 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -1,21 +1,22 @@ class Api::V1::UsersController < ApplicationController + before_action :find_user, only: [:show, :dashboard] + def show - render json: UserSerializer.new(User.find(params[:user_id])) + render json: UserSerializer.new(@user) end def dashboard - dashboard = User.find(params[:user_id]).find_dashboard_info + dashboard = @user.find_dashboard_info render json: DashboardSerializer.new(dashboard) end def create new_user = User.new(user_params) - new_user.email.downcase - new_user.format_phone_number - return unless new_user.save - - session[:user_id] = new_user.id - render json: UserSerializer.new(new_user), status: :created + new_user.format_attributes + if new_user.save + session[:user_id] = new_user.id + render json: UserSerializer.new(new_user), status: :created + end end private @@ -23,4 +24,8 @@ def create def user_params params.permit(:full_name, :password, :password_confirmation, :email, :zip_code, :phone_number) end + + def find_user + @user = User.find(params[:user_id]) + end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c069e96..71434b7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,12 +3,24 @@ class ApplicationController < ActionController::API before_action :set_current_user rescue_from ActiveRecord::RecordNotFound, with: :record_not_found + rescue_from NoPuzzlesException, with: :no_puzzles def record_not_found(exception) render json: ErrorSerializer.new(exception, 404).serializable_hash, status: :not_found # 404 end + def no_puzzles + error = NoPuzzlesException.new("No puzzles found in this area.") + render json: ErrorSerializer.new(error, :not_found).serializable_hash, status: :not_found # 404 + end + def set_current_user @current_user ||= User.find_by(id: session[:user_id]) end end + +# Note to self: Saving this to remember process that lead me to final version: +# def no_puzzles + # render status: :no_content, json: {} # 204 + # render json: { error: "No puzzles found in this area." }, status: :not_found # 404 +# end \ No newline at end of file diff --git a/app/exceptions/no_puzzles_exception.rb b/app/exceptions/no_puzzles_exception.rb new file mode 100644 index 0000000..8b33004 --- /dev/null +++ b/app/exceptions/no_puzzles_exception.rb @@ -0,0 +1,7 @@ +class NoPuzzlesException < StandardError +end + + + + + diff --git a/app/models/puzzle.rb b/app/models/puzzle.rb index 1898acb..d6bd05c 100644 --- a/app/models/puzzle.rb +++ b/app/models/puzzle.rb @@ -6,4 +6,10 @@ class Puzzle < ApplicationRecord validates_numericality_of :total_pieces enum status: { 'Available' => 0, 'Pending' => 1, 'Not Available' => 2, "Permanently Removed" => 3 } + + def self.find_by_zip_code(zip_code) + joins(:user) + .where(users: { zip_code: zip_code }) + .where.not(status: 3) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 050ed80..7e92339 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -11,8 +11,9 @@ class User < ApplicationRecord has_secure_password - def format_phone_number - phone_number.insert(0, '(').insert(4, ')').insert(5, " ").insert(9, "-") + def format_attributes + self.email = format_email + self.phone_number = format_phone_number end # This method needs a HUGE refactor: @@ -64,4 +65,14 @@ def find_dashboard_info OpenStruct.new(dashboard_info) end + + private + + def format_email + email.downcase + end + + def format_phone_number + phone_number.insert(0, '(').insert(4, ')').insert(5, " ").insert(9, "-") + end end diff --git a/spec/models/puzzle_spec.rb b/spec/models/puzzle_spec.rb index 1e534e0..6dfc0f6 100644 --- a/spec/models/puzzle_spec.rb +++ b/spec/models/puzzle_spec.rb @@ -17,4 +17,44 @@ it { should define_enum_for(:status).with_values(["Available", "Pending", "Not Available", "Permanently Removed"]) } end + + describe "class methods" do + describe "::find_by_zip_code" do + before(:each) do + @user_1 = create(:user, id: 1, zip_code: 12345) + @user_2 = create(:user, id: 2, zip_code: 12345) + @user_3 = create(:user, id: 3, zip_code: 54321) + + @puzzle_1 = create(:puzzle, user: @user_1) + @puzzle_2 = create(:puzzle, user: @user_1) + + @puzzle_3 = create(:puzzle, user: @user_2) + @puzzle_4 = create(:puzzle, user: @user_2) + + @puzzle_5 = create(:puzzle, user: @user_3) + @puzzle_6 = create(:puzzle, user: @user_3) + end + + it "returns all puzzles within a zip code" do + puzzles_in_zip_code = Puzzle.find_by_zip_code(12345) + + expect(puzzles_in_zip_code).to eq([@puzzle_1, @puzzle_2, @puzzle_3, @puzzle_4]) + expect(puzzles_in_zip_code).to_not include([@puzzle_5, @puzzle_6]) + end + + it "returns an empty array if no puzzles are found in a zip code" do + puzzles_in_zip_code = Puzzle.find_by_zip_code(10101) + + expect(puzzles_in_zip_code).to eq([]) + expect(puzzles_in_zip_code).to_not include([@puzzle_1, @puzzle_2, @puzzle_3, @puzzle_4, @puzzle_5, @puzzle_6]) + end + + it "returns an empty array if nil is sent in place of a zip code" do + puzzles_in_zip_code = Puzzle.find_by_zip_code(nil) + + expect(puzzles_in_zip_code).to eq([]) + expect(puzzles_in_zip_code).to_not include([@puzzle_1, @puzzle_2, @puzzle_3, @puzzle_4, @puzzle_5, @puzzle_6]) + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a3f52c7..d8d9f95 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -18,6 +18,24 @@ end describe "instance methods" do + describe "#format_attributes" do + it "can format the email & phone number attributes of a user" do + user = User.create( + full_name: "Diana Puzzler", + password: "PuzzleQueen1", + password_confirmation: "PuzzleQueen1", + email: "DpuZZler@My-Email.coM", + zip_code: 12345, + phone_number: "1011110000" + ) + + user.format_attributes + + expect(user.email).to eq("dpuzzler@my-email.com") + expect(user.phone_number).to eq("(101) 111-0000") + end + end + describe "#find_dashboard_info" do before(:each) do @user_1 = create(:user, id: 1) diff --git a/spec/requests/api/v1/puzzles_request_spec.rb b/spec/requests/api/v1/puzzles_request_spec.rb index aea082a..dbc2d52 100644 --- a/spec/requests/api/v1/puzzles_request_spec.rb +++ b/spec/requests/api/v1/puzzles_request_spec.rb @@ -5,108 +5,162 @@ RSpec.describe "PuzzlesController", type: :request do describe "#index" do context 'when successful' do + before(:each) do + @user_1 = create(:user, id: 1, zip_code: 12345) + @user_2 = create(:user, id: 2, zip_code: 12345) + @user_3 = create(:user, id: 3, zip_code: 54321) + @user_4 = create(:user, id: 4, zip_code: 54321) + + @puzzle_1 = create(:puzzle, id: 1, user: @user_1) + @puzzle_2 = create(:puzzle, id: 2, user: @user_1) + + @puzzle_3 = create(:puzzle, id: 3, user: @user_2) + @puzzle_4 = create(:puzzle, id: 4, user: @user_2) + + @puzzle_5 = create(:puzzle, id: 5, user: @user_3) + @puzzle_6 = create(:puzzle, id: 6, user: @user_3, status: 1) + + @puzzle_7 = create(:puzzle, id: 7, user: @user_4, status: 2) + @puzzle_8 = create(:puzzle, id: 8, user: @user_4, status: 3) + end + it "returns all puzzles from a zipcode" do zip_code = 12345 - - user_1 = create(:user, id: 1, zip_code:) - user_2 = create(:user, id: 2, zip_code:) - user_3 = create(:user, id: 3, zip_code: 54321) - puzzle_1 = create(:puzzle, user: user_1) - create(:puzzle, user: user_1) - puzzle_3 = create(:puzzle, user: user_2) - create(:puzzle, user: user_3) - - zipcode_params = { zip_code: 12345 } + zipcode_params = { zip_code: zip_code } headers = { 'CONTENT_TYPE' => 'application/json' } put "/api/v1/puzzles", headers:, params: JSON.generate(zipcode_params) expect(response).to have_http_status(200) - parsed_data = JSON.parse(response.body, symbolize_names: true) expect(parsed_data).to be_a(Hash) expect(parsed_data.keys).to eq([:data]) expect(parsed_data[:data]).to be_an(Array) + expect(parsed_data[:data].size).to eq(4) # Only puzzles 1-4 are in this zip code expect(parsed_data[:data][0]).to be_a(Hash) expect(parsed_data[:data][0].keys).to eq([:id, :type, :attributes]) + expect(parsed_data[:data][0][:id]).to eq(@puzzle_1.id.to_s) + expect(parsed_data[:data][1][:id]).to eq(@puzzle_2.id.to_s) + expect(parsed_data[:data][2][:id]).to eq(@puzzle_3.id.to_s) + expect(parsed_data[:data][3][:id]).to eq(@puzzle_4.id.to_s) + expect(parsed_data[:data][0][:attributes]).to be_a(Hash) expect(parsed_data[:data][0][:attributes].keys).to eq([:user_id, :status, :title, :description, :total_pieces, :notes, :puzzle_image_url]) - expect(parsed_data[:data][0][:attributes][:user_id]).to eq(puzzle_1.user_id) - expect(parsed_data[:data][0][:attributes][:status]).to eq(puzzle_1.status) - expect(parsed_data[:data][0][:attributes][:title]).to eq(puzzle_1.title) - expect(parsed_data[:data][0][:attributes][:description]).to eq(puzzle_1.description) - expect(parsed_data[:data][0][:attributes][:total_pieces]).to eq(puzzle_1.total_pieces) - expect(parsed_data[:data][0][:attributes][:notes]).to eq(puzzle_1.notes) - expect(parsed_data[:data][0][:attributes][:puzzle_image_url]).to eq(puzzle_1.puzzle_image_url) - - # NOTE: Could refactor tests to not see a puzzle_4 in the test because it's in a diff zipcode - - expect(parsed_data[:data][2][:attributes]).to be_a(Hash) - expect(parsed_data[:data][2][:attributes].keys).to eq([:user_id, :status, :title, :description, :total_pieces, :notes, :puzzle_image_url]) - expect(parsed_data[:data][2][:attributes][:user_id]).to eq(puzzle_3.user_id) - expect(parsed_data[:data][2][:attributes][:status]).to eq(puzzle_3.status) - expect(parsed_data[:data][2][:attributes][:title]).to eq(puzzle_3.title) - expect(parsed_data[:data][2][:attributes][:description]).to eq(puzzle_3.description) - expect(parsed_data[:data][2][:attributes][:total_pieces]).to eq(puzzle_3.total_pieces) - expect(parsed_data[:data][2][:attributes][:notes]).to eq(puzzle_3.notes) - expect(parsed_data[:data][2][:attributes][:puzzle_image_url]).to eq(puzzle_3.puzzle_image_url) + expect(parsed_data[:data][0][:attributes][:user_id]).to eq(@puzzle_1.user_id) + expect(parsed_data[:data][0][:attributes][:status]).to eq(@puzzle_1.status) + expect(parsed_data[:data][0][:attributes][:title]).to eq(@puzzle_1.title) + expect(parsed_data[:data][0][:attributes][:description]).to eq(@puzzle_1.description) + expect(parsed_data[:data][0][:attributes][:total_pieces]).to eq(@puzzle_1.total_pieces) + expect(parsed_data[:data][0][:attributes][:notes]).to eq(@puzzle_1.notes) + expect(parsed_data[:data][0][:attributes][:puzzle_image_url]).to eq(@puzzle_1.puzzle_image_url) end it "does not return any puzzles with a status 3='Permanently Removed' from a zipcode" do - zip_code = 12345 - - user_1 = create(:user, id: 1, zip_code:) - user_2 = create(:user, id: 2, zip_code:) - user_3 = create(:user, id: 3, zip_code: 54321) - puzzle_1 = create(:puzzle, user: user_1) - puzzle_2 = create(:puzzle, user: user_1, status: 3) # This will NOT be returned since it is 'Permanently Removed' - puzzle_3 = create(:puzzle, user: user_2) - - zipcode_params = { zip_code: 12345 } + zip_code = 54321 + zipcode_params = { zip_code: zip_code } headers = { 'CONTENT_TYPE' => 'application/json' } put "/api/v1/puzzles", headers:, params: JSON.generate(zipcode_params) expect(response).to have_http_status(200) - parsed_data = JSON.parse(response.body, symbolize_names: true) expect(parsed_data).to be_a(Hash) expect(parsed_data.keys).to eq([:data]) expect(parsed_data[:data]).to be_an(Array) + expect(parsed_data[:data].size).to eq(3) # Only puzzles 5-7 are in this zip code without a status=3 expect(parsed_data[:data][0]).to be_a(Hash) expect(parsed_data[:data][0].keys).to eq([:id, :type, :attributes]) - expect(parsed_data[:data][0][:attributes].keys).to eq([:user_id, :status, :title, :description, :total_pieces, :notes, :puzzle_image_url]) - expect(parsed_data[:data].size).to eq(2) - expect(parsed_data[:data][0][:attributes][:user_id]).to eq(puzzle_1.user_id) - expect(parsed_data[:data][1][:attributes][:user_id]).to eq(puzzle_3.user_id) + expect(parsed_data[:data][0][:id]).to eq(@puzzle_5.id.to_s) + expect(parsed_data[:data][1][:id]).to eq(@puzzle_6.id.to_s) + expect(parsed_data[:data][2][:id]).to eq(@puzzle_7.id.to_s) + + expect(parsed_data[:data][0][:attributes]).to be_a(Hash) + expect(parsed_data[:data][0][:attributes].keys).to eq([:user_id, :status, :title, :description, :total_pieces, :notes, :puzzle_image_url]) + expect(parsed_data[:data][0][:attributes][:user_id]).to eq(@puzzle_5.user_id) + expect(parsed_data[:data][0][:attributes][:status]).to eq(@puzzle_5.status) + expect(parsed_data[:data][0][:attributes][:title]).to eq(@puzzle_5.title) + expect(parsed_data[:data][0][:attributes][:description]).to eq(@puzzle_5.description) + expect(parsed_data[:data][0][:attributes][:total_pieces]).to eq(@puzzle_5.total_pieces) + expect(parsed_data[:data][0][:attributes][:notes]).to eq(@puzzle_5.notes) + expect(parsed_data[:data][0][:attributes][:puzzle_image_url]).to eq(@puzzle_5.puzzle_image_url) end end context 'when NOT successful' do - it 'returns an error message when zipcode is not found' do - user_1 = create(:user, id: 1, zip_code: 54321) - user_2 = create(:user, id: 2, zip_code: 12346) - create(:puzzle, user: user_1) - create(:puzzle, user: user_1) - create(:puzzle, user: user_2) - create(:puzzle, user: user_2) + before(:each) do + @user_1 = create(:user, id: 1, zip_code: 12345) + @user_2 = create(:user, id: 2, zip_code: 12345) + @user_3 = create(:user, id: 3, zip_code: 54321) + + @puzzle_1 = create(:puzzle, id: 1, user: @user_1) + @puzzle_2 = create(:puzzle, id: 2, user: @user_1) + + @puzzle_3 = create(:puzzle, id: 3, user: @user_2) + @puzzle_4 = create(:puzzle, id: 4, user: @user_2) + end + + it 'returns a NoPuzzlesException error when zipcode is not found' do + zip_code = 10101 + zipcode_params = { zip_code: zip_code } + + headers = { 'CONTENT_TYPE' => 'application/json' } + put "/api/v1/puzzles", headers:, params: JSON.generate(zipcode_params) - zipcode_params = { zip_code: 0o0000 } + expect(response).to have_http_status(404) + parsed_error_data = JSON.parse(response.body, symbolize_names: true) + + expect(parsed_error_data).to be_a(Hash) + expect(parsed_error_data.keys).to eq([:errors]) + expect(parsed_error_data[:errors]).to be_an(Array) + expect(parsed_error_data[:errors][0]).to be_a(Hash) + expect(parsed_error_data[:errors][0].keys).to eq([:status, :title, :detail]) + expect(parsed_error_data[:errors][0][:status]).to eq("not_found") + expect(parsed_error_data[:errors][0][:title]).to eq("NoPuzzlesException") + expect(parsed_error_data[:errors][0][:detail]).to eq("No puzzles found in this area.") + end + + it 'returns a NoPuzzlesException error when no puzzles are found in a zip code that does exists in the database' do + zip_code = 54321 # A user exists in this zip code but has no puzzles + zipcode_params = { zip_code: zip_code } headers = { 'CONTENT_TYPE' => 'application/json' } put "/api/v1/puzzles", headers:, params: JSON.generate(zipcode_params) expect(response).to have_http_status(404) + parsed_error_data = JSON.parse(response.body, symbolize_names: true) + expect(parsed_error_data).to be_a(Hash) + expect(parsed_error_data.keys).to eq([:errors]) + expect(parsed_error_data[:errors]).to be_an(Array) + expect(parsed_error_data[:errors][0]).to be_a(Hash) + expect(parsed_error_data[:errors][0].keys).to eq([:status, :title, :detail]) + expect(parsed_error_data[:errors][0][:status]).to eq("not_found") + expect(parsed_error_data[:errors][0][:title]).to eq("NoPuzzlesException") + expect(parsed_error_data[:errors][0][:detail]).to eq("No puzzles found in this area.") + end + + it 'returns a NoPuzzlesException error when nil is sent as zip code' do + zip_code = nil + zipcode_params = { zip_code: zip_code } + + headers = { 'CONTENT_TYPE' => 'application/json' } + put "/api/v1/puzzles", headers:, params: JSON.generate(zipcode_params) + + expect(response).to have_http_status(404) parsed_error_data = JSON.parse(response.body, symbolize_names: true) expect(parsed_error_data).to be_a(Hash) - expect(parsed_error_data.keys).to eq([:error]) - expect(parsed_error_data[:error]).to eq("Puzzles not found in this area") + expect(parsed_error_data.keys).to eq([:errors]) + expect(parsed_error_data[:errors]).to be_an(Array) + expect(parsed_error_data[:errors][0]).to be_a(Hash) + expect(parsed_error_data[:errors][0].keys).to eq([:status, :title, :detail]) + expect(parsed_error_data[:errors][0][:status]).to eq("not_found") + expect(parsed_error_data[:errors][0][:title]).to eq("NoPuzzlesException") + expect(parsed_error_data[:errors][0][:detail]).to eq("No puzzles found in this area.") end end end