diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb new file mode 100644 index 0000000..6785d41 --- /dev/null +++ b/app/controllers/dashboard_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# DashboardController +class DashboardController < ApplicationController + + def index + @goals = Goal.includes(:goal_progresses).order("goal_progresses.updated_at desc") + end + +end diff --git a/app/controllers/goal_progresses_controller.rb b/app/controllers/goal_progresses_controller.rb new file mode 100644 index 0000000..1ccfcd5 --- /dev/null +++ b/app/controllers/goal_progresses_controller.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +# GoalProgressesController +class GoalProgressesController < ApplicationController + before_action :set_goal_progress, only: %i[show edit update destroy] + + # GET /goal_progresses + # GET /goal_progresses.json + def index + @goal_progresses = GoalProgress.all + end + + # GET /goal_progresses/1 + # GET /goal_progresses/1.json + def show; end + + # GET /goal_progresses/new + def new + @goal_progress = GoalProgress.new + end + + # GET /goal_progresses/1/edit + def edit; end + + # POST /goal_progresses + # POST /goal_progresses.json + def create + @goal_progress = GoalProgress.new(goal_progress_params) + + respond_to do |format| + if @goal_progress.save + format.html { redirect_to @goal_progress, notice: 'Progress was successfully created.' } + format.json { render :show, status: :created, location: @goal_progress } + else + format.html { render :new } + format.json { render json: @goal_progress.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /goal_progresses/1 + # PATCH/PUT /goal_progresses/1.json + def update + respond_to do |format| + if @goal_progress.update(goal_progress_params) + format.html { redirect_to @goal_progress, notice: 'Progress was successfully updated.' } + format.json { render :show, status: :ok, location: @goal_progress } + else + format.html { render :edit } + format.json { render json: @goal_progress.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /goal_progresses/1 + # DELETE /goal_progresses/1.json + def destroy + @goal_progress.destroy + respond_to do |format| + format.html { redirect_to goal_progresses_url, notice: 'Progress was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + + # Use callbacks to share common setup or constraints between actions. + def set_goal_progress + @goal_progress = GoalProgress.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def goal_progress_params + params.require(:goal_progress).permit(:goal_id, :achieve, :achieve_at) + end +end diff --git a/app/controllers/goals_controller.rb b/app/controllers/goals_controller.rb index 1d0c165..4695f75 100644 --- a/app/controllers/goals_controller.rb +++ b/app/controllers/goals_controller.rb @@ -71,6 +71,6 @@ def set_goal # Never trust parameters from the scary internet, only allow the white list through. def goal_params - params.require(:goal).permit(:name) + params.require(:goal).permit(:name, :benchmark) end end diff --git a/app/models/goal.rb b/app/models/goal.rb index 6b767b0..027a7b4 100644 --- a/app/models/goal.rb +++ b/app/models/goal.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true class Goal < ApplicationRecord + validates :name, :benchmark, presence: true + has_many :goal_progresses end diff --git a/app/models/goal_progress.rb b/app/models/goal_progress.rb new file mode 100644 index 0000000..bb13ed4 --- /dev/null +++ b/app/models/goal_progress.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class GoalProgress < ApplicationRecord + validates :achieve, :achieve_at, presence: true + belongs_to :goal +end diff --git a/app/views/dashboard/index.html.slim b/app/views/dashboard/index.html.slim new file mode 100644 index 0000000..6851c9c --- /dev/null +++ b/app/views/dashboard/index.html.slim @@ -0,0 +1,29 @@ +h1.title Your personal dashboard + +table.table + thead + tr + th Goal + th Monday + th Tuesday + th Wednesday + th Thursday + th Friday + + tbody + - @goals.each do |goal| + tr + td = goal.name + - progresses = goal.goal_progresses.where("goal_progresses.created_at >= ?", 1.week.ago) + td = progresses.select { |p| p.created_at.monday? }.last.try(:achieve) || "-" + td = progresses.select { |p| p.created_at.tuesday? }.last.try(:achieve) || "-" + td = progresses.select { |p| p.created_at.wednesday? }.last.try(:achieve) || "-" + td = progresses.select { |p| p.created_at.thursday? }.last.try(:achieve) || "-" + td = progresses.select { |p| p.created_at.friday? }.last.try(:achieve) || "-" +br + +ul + li + = link_to 'New Goal', new_goal_path + li + = link_to 'New Progress', new_goal_progress_path diff --git a/app/views/goal_progresses/_form.html.slim b/app/views/goal_progresses/_form.html.slim new file mode 100644 index 0000000..b3f23fc --- /dev/null +++ b/app/views/goal_progresses/_form.html.slim @@ -0,0 +1,25 @@ += form_for [@goal_progress] do |f| + = error_messages_for f.object + + .field + = f.label :goal, class: 'label' + .control= f.collection_select(:goal_id, Goal.all, :id, :name) + + .field + = f.label :achieve_at, class: 'label' + .control= f.date_field :achieve_at, placeholder: f.object.class.human_attribute_name(:achieve_at), class: 'input', required: true + + .field + = f.label :achieve, class: 'label' + .control= f.number_field :achieve, placeholder: f.object.class.human_attribute_name(:achieve), class: 'input', required: true + + .field.is-grouped + p.control + = submit_tag t('.save'), class: 'button is-primary', data: {disable_with: "Saving..."} + = link_to "Cancel", [:goal_progresses], class: 'button is-text' + + p.control.has-text-right + - if f.object.persisted? # and policy(f.object).destroy? + = link_to [f.object], method: :delete, class: 'button is-danger is-inverted', data: {confirm: t('.destroy.title')} do + i.fa.fa-trash + = t('.destroy.button') diff --git a/app/views/goal_progresses/edit.html.slim b/app/views/goal_progresses/edit.html.slim new file mode 100644 index 0000000..24e2164 --- /dev/null +++ b/app/views/goal_progresses/edit.html.slim @@ -0,0 +1,7 @@ +h1 Editing progress + +== render 'form' + +=> link_to 'Show', @goal_progress +'| +=< link_to 'Back', goal_progresses_path diff --git a/app/views/goal_progresses/index.html.slim b/app/views/goal_progresses/index.html.slim new file mode 100644 index 0000000..467355f --- /dev/null +++ b/app/views/goal_progresses/index.html.slim @@ -0,0 +1,25 @@ +h1.title Listing progresses + +table.table + thead + tr + th Goal + th Achieve At + th Achieve + th + th + th + + tbody + - @goal_progresses.each do |progress| + tr + td = progress.goal.name + td = progress.achieve_at + td = progress.achieve + td = link_to 'Show', progress + td = link_to 'Edit', edit_goal_progress_path(progress) + td = link_to 'Destroy', progress, data: { confirm: 'Are you sure?' }, method: :delete + +br + += link_to 'New Progress', new_goal_progress_path diff --git a/app/views/goal_progresses/new.html.slim b/app/views/goal_progresses/new.html.slim new file mode 100644 index 0000000..bfdaff3 --- /dev/null +++ b/app/views/goal_progresses/new.html.slim @@ -0,0 +1,5 @@ +h1 New progress + +== render 'form' + += link_to 'Back', goal_progresses_path diff --git a/app/views/goal_progresses/show.html.slim b/app/views/goal_progresses/show.html.slim new file mode 100644 index 0000000..9feaec4 --- /dev/null +++ b/app/views/goal_progresses/show.html.slim @@ -0,0 +1,16 @@ +p#notice = notice + +p + strong Goal: + = @goal_progress.goal.name +p + strong achieve_at: + = @goal_progress.achieve_at + +p + strong achieve_at: + = @goal_progress.achieve + +=> link_to 'Edit', edit_goal_progress_path(@goal_progress) +'| +=< link_to 'Back', goal_progresses_path diff --git a/app/views/goals/_form.html.slim b/app/views/goals/_form.html.slim index 0abe0e7..0faa907 100644 --- a/app/views/goals/_form.html.slim +++ b/app/views/goals/_form.html.slim @@ -3,7 +3,11 @@ .field = f.label :name, class: 'label' - .control= f.text_field :name, placeholder: f.object.class.human_attribute_name(:name), class: 'input' + .control= f.text_field :name, placeholder: f.object.class.human_attribute_name(:name), class: 'input', required: true + + .field + = f.label :benchmark, class: 'label' + .control= f.number_field :benchmark, placeholder: f.object.class.human_attribute_name(:benchmark), class: 'input', required: true .field.is-grouped p.control @@ -16,4 +20,3 @@ = link_to [f.object], method: :delete, class: 'button is-danger is-inverted', data: {confirm: t('.destroy.title')} do i.fa.fa-trash = t('.destroy.button') - diff --git a/app/views/goals/_goal.json.jbuilder b/app/views/goals/_goal.json.jbuilder index 333520f..5796912 100644 --- a/app/views/goals/_goal.json.jbuilder +++ b/app/views/goals/_goal.json.jbuilder @@ -1,4 +1,4 @@ # frozen_string_literal: true -json.extract! goal, :id, :name, :created_at, :updated_at +json.extract! goal, :id, :name, :benchmark, :created_at, :updated_at json.url goal_url(goal, format: :json) diff --git a/app/views/goals/index.html.slim b/app/views/goals/index.html.slim index f6664e3..1ce77fb 100644 --- a/app/views/goals/index.html.slim +++ b/app/views/goals/index.html.slim @@ -4,6 +4,7 @@ table.table thead tr th Name + th Benchmark th th th @@ -12,6 +13,7 @@ table.table - @goals.each do |goal| tr td = goal.name + td = goal.benchmark td = link_to 'Show', goal td = link_to 'Edit', edit_goal_path(goal) td = link_to 'Destroy', goal, data: { confirm: 'Are you sure?' }, method: :delete diff --git a/app/views/goals/show.html.slim b/app/views/goals/show.html.slim index df7a00a..e32f768 100644 --- a/app/views/goals/show.html.slim +++ b/app/views/goals/show.html.slim @@ -3,6 +3,9 @@ p#notice = notice p strong Name: = @goal.name +p + strong Benchmark: + = @goal.benchmark => link_to 'Edit', edit_goal_path(@goal) '| diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index 3585222..38f348d 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -29,6 +29,8 @@ html .navbar-menu#navMenu .navbar-end = link_to "My Goals", goals_path, class: ['navbar-item', 'is-tab', active_if(controller?('goals'))] + .navbar-end + = link_to "My Progress", goal_progresses_path, class: ['navbar-item', 'is-tab', active_if(controller?('goal_progresses'))] - flash.select{|k,v| %w(notice alert).include?(k)}.each do |type, message| section.section @@ -45,6 +47,3 @@ html ' Booster Stage ' © = Date.today.year - - - diff --git a/config/locales/en.yml b/config/locales/en.yml index c2927fe..2b65d76 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -35,3 +35,6 @@ en: goals: form: save: Save Goal + goal_progresses: + form: + save: Save Progress diff --git a/config/routes.rb b/config/routes.rb index 3364751..53acee9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,5 +2,6 @@ Rails.application.routes.draw do resources :goals - root to: 'goals#index' + resources :goal_progresses + root to: 'dashboard#index' end diff --git a/db/migrate/20190228173758_add_benchmark_to_goals.rb b/db/migrate/20190228173758_add_benchmark_to_goals.rb new file mode 100644 index 0000000..c648b5d --- /dev/null +++ b/db/migrate/20190228173758_add_benchmark_to_goals.rb @@ -0,0 +1,5 @@ +class AddBenchmarkToGoals < ActiveRecord::Migration[5.2] + def change + add_column :goals, :benchmark, :integer, required: true + end +end diff --git a/db/migrate/20190228180507_create_goal_progress.rb b/db/migrate/20190228180507_create_goal_progress.rb new file mode 100644 index 0000000..48a131f --- /dev/null +++ b/db/migrate/20190228180507_create_goal_progress.rb @@ -0,0 +1,11 @@ +class CreateGoalProgress < ActiveRecord::Migration[5.2] + def change + create_table :goal_progresses do |t| + t.references :goal, index: true + t.integer :achieve, null: false + t.date :achieve_at, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e206fae..4dcf852 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -12,10 +10,22 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20_180_815_015_756) do - create_table 'goals', force: :cascade do |t| - t.string 'name' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false +ActiveRecord::Schema.define(version: 2019_02_28_180507) do + + create_table "goal_progresses", force: :cascade do |t| + t.integer "goal_id" + t.integer "achieve", null: false + t.date "achieve_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["goal_id"], name: "index_goal_progresses_on_goal_id" end + + create_table "goals", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "benchmark" + end + end diff --git a/test/controllers/goal_progresses_controller_test.rb b/test/controllers/goal_progresses_controller_test.rb new file mode 100644 index 0000000..9b8c273 --- /dev/null +++ b/test/controllers/goal_progresses_controller_test.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'test_helper' + +class GoalProgressesControllerTest < ActionDispatch::IntegrationTest + setup do + @goal_progress = goal_progresses(:one) + end + + test 'should get index' do + get goal_progresses_url + assert_response :success + end + + test 'should get new' do + get new_goal_progress_url + assert_response :success + end + + test 'should create goal progress' do + assert_difference('GoalProgress.count') do + post goal_progresses_url, params: { goal_progress: { goal_id: @goal_progress.goal_id, achieve_at: @goal_progress.achieve_at, achieve: @goal_progress.achieve } } + end + + assert_redirected_to goal_progress_url(GoalProgress.last) + end + + test 'should show goal progress' do + get goal_progress_url(@goal_progress) + assert_response :success + end + + test 'should get edit' do + get edit_goal_progress_url(@goal_progress) + assert_response :success + end + + test 'should update goal progress' do + patch goal_progress_url(@goal_progress), params: { goal_progress: { goal_id: @goal_progress.goal_id, achieve_at: @goal_progress.achieve_at, achieve: @goal_progress.achieve } } + assert_redirected_to goal_progress_url(@goal_progress) + end + + test 'should destroy goal progress' do + assert_difference('GoalProgress.count', -1) do + delete goal_progress_url(@goal_progress) + end + + assert_redirected_to goal_progresses_url + end +end diff --git a/test/controllers/goals_controller_test.rb b/test/controllers/goals_controller_test.rb index b9005a4..9429e59 100644 --- a/test/controllers/goals_controller_test.rb +++ b/test/controllers/goals_controller_test.rb @@ -19,7 +19,7 @@ class GoalsControllerTest < ActionDispatch::IntegrationTest test 'should create goal' do assert_difference('Goal.count') do - post goals_url, params: { goal: { name: @goal.name } } + post goals_url, params: { goal: { name: @goal.name, benchmark: @goal.benchmark } } end assert_redirected_to goal_url(Goal.last) @@ -36,7 +36,7 @@ class GoalsControllerTest < ActionDispatch::IntegrationTest end test 'should update goal' do - patch goal_url(@goal), params: { goal: { name: @goal.name } } + patch goal_url(@goal), params: { goal: { name: @goal.name, benchmark: @goal.benchmark } } assert_redirected_to goal_url(@goal) end diff --git a/test/fixtures/goal_progresses.yml b/test/fixtures/goal_progresses.yml new file mode 100644 index 0000000..cea1aa8 --- /dev/null +++ b/test/fixtures/goal_progresses.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + goal_id: 1 + achieve_at: "2018/10/02" + achieve: 20 + +two: + goal_id: 1 + achieve_at: "2018/10/02" + achieve: 20 diff --git a/test/fixtures/goals.yml b/test/fixtures/goals.yml index 56066c6..88632dd 100644 --- a/test/fixtures/goals.yml +++ b/test/fixtures/goals.yml @@ -1,7 +1,11 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: + id: 1 name: MyString + benchmark: 20 two: + id: 2 name: MyString + benchmark: 20 diff --git a/test/models/goal_progress_test.rb b/test/models/goal_progress_test.rb new file mode 100644 index 0000000..08129a9 --- /dev/null +++ b/test/models/goal_progress_test.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'test_helper' + +class GoalProgressTest < ActiveSupport::TestCase + + setup do + @goal_progress = goal_progresses(:one) + end + + test "valid goal progress" do + assert @goal_progress.valid? + end + + test "invalid without goal" do + @goal_progress.goal = nil + refute @goal_progress.valid? + assert_not_nil @goal_progress.errors[:goal_id] + end + + test "invalid without achieve_at" do + @goal_progress.achieve_at = nil + refute @goal_progress.valid? + assert_not_nil @goal_progress.errors[:achieve_at] + end + + test "invalid without achieve" do + @goal_progress.achieve = nil + refute @goal_progress.valid? + assert_not_nil @goal_progress.errors[:achieve] + end +end diff --git a/test/models/goal_test.rb b/test/models/goal_test.rb index a56ef6c..9b12bd1 100644 --- a/test/models/goal_test.rb +++ b/test/models/goal_test.rb @@ -3,7 +3,20 @@ require 'test_helper' class GoalTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + test "valid goal" do + goal = Goal.new(name: "20 Push-ups per day", benchmark: 20) + assert goal.valid? + end + + test "invalid without name" do + goal = Goal.new(benchmark: 20) + refute goal.valid? + assert_not_nil goal.errors[:name] + end + + test "invalid without benchmark" do + goal = Goal.new(name: "20 Push-ups per day") + refute goal.valid? + assert_not_nil goal.errors[:benchmark] + end end diff --git a/test/system/goals_test.rb b/test/system/goals_test.rb index 054d158..ed0dea5 100644 --- a/test/system/goals_test.rb +++ b/test/system/goals_test.rb @@ -17,6 +17,7 @@ class GoalsTest < ApplicationSystemTestCase click_on 'New Goal' fill_in 'Name', with: @goal.name + fill_in 'Benchmark', with: @goal.benchmark click_on 'Save Goal' assert_text 'Goal was successfully created' @@ -28,6 +29,7 @@ class GoalsTest < ApplicationSystemTestCase click_on 'Edit', match: :first fill_in 'Name', with: @goal.name + fill_in 'Benchmark', with: @goal.benchmark click_on 'Save Goal' assert_text 'Goal was successfully updated'