Last active
August 29, 2015 14:20
-
-
Save RallyWithGalli/c66dee6dfb9ab5d338c2 to your computer and use it in GitHub Desktop.
Structure & Gamification for Developing Good Habits
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= simple_form_for(@habit) do |f| %> | |
<%= f.error_notification %> | |
<div class="add-form-padding"> | |
<form> | |
<div class="committed"> | |
<a title="Choose the days of the week you commit to doing your habit every week."> | |
<span class="label label-primary">Committed to:</span> | |
<%= f.collection_check_boxes :committed, Date::ABBR_DAYNAMES, :downcase, :to_s %> | |
</a> | |
</div> | |
<div class="committed"> | |
<span class="label label-primary">Started:</span> | |
<%= f.date_select :date_started, :order => [:month, :day, :year], class: 'date-select' %> | |
</div> | |
<br> | |
<br> | |
<div class="center-buttons-habits"> | |
<div class="form-inline"> | |
<a title="AFTER I... wake up, brush my teeth, leave the house, turn on my computer, eat lunch, take a shower, etc."> | |
<label>After I</label> | |
<%= f.text_field :trigger, class: 'underlining', placeholder: 'Enter Trigger', id: 'centered' %>, | |
</a> | |
<a title="I WILL... read, write, run, study, pushup, exercise, stretch, meditate, walk, etc."> | |
<label>I will</label> | |
<%= f.text_field :action, class: 'underlining', id: "three-gold-standard", placeholder: 'Enter Habit' %> | |
<br> | |
</a> | |
<a title="UNTIL... 2 miles, a chapter, 500 words, 20 min, 15 reps, 10,000 steps, etc."> | |
<label>until</label> | |
<%= f.text_field :target, class: 'underlining', placeholder: 'Enter Target', id: 'centered' %> | |
</a> | |
<a title="SO I MAY... eat dessert, watch TV, drink coffee, go on a cruise, feel more energized & focused, etc."> | |
<label>so I may</label> | |
<%= f.text_field :reward, class: 'underlining', placeholder: 'Enter Reward', id: 'centered' %> . | |
</a> | |
</div> | |
<br> | |
<br> | |
<a title="The placement of your habit in your list of habits. If you want this habit to appear first, put 1, second, put 2, etc. I usually put them in the order I do them each day."> | |
<%= f.number_field :order, class: "form-control", id: "two", placeholder: 'Order' %> | |
</a> | |
<%= f.text_field :tag_list, class: 'form-control', id: "four", placeholder: 'Enter Tags, seperate with comma', :value => @habit.all_tags_list.join(', ') %> | |
<div class="center-buttons"> | |
<%= button_tag(type: 'submit', class: "btn") do %> | |
<span class="glyphicon glyphicon-plus"></span> Submit | |
<% end %> | |
</div> | |
<div class="float-right-box"> | |
<%= button_tag(type: "submit", class: "btn", id: "black", name: "commit", value: "conceal") do %> | |
<span class="glyphicon glyphicon-plus"></span> Conceal | |
<% end %> | |
| |
<%= link_to @habit, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn", id: "red" do %> | |
<span class="glyphicon glyphicon-trash"></span> Delete | |
<% end %> | |
</div> | |
</div> | |
</form> | |
</div> | |
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<table> | |
<tbody> | |
<div class="habit-padding-home"> | |
<% if habit.current_level_strike %> | |
<tr class="shrink-center"> | |
<td class="condense-gold"> | |
LEVEL <%= habit.current_level %>: | |
</td> | |
<td class="stretch"> | |
<div class="progress"> | |
<div class="progress-bar" role="progressbar" aria-valuenow="10" aria-valuemin="0" aria-valuemax="10" style="width: <%= habit.progress_in_percent %>%;"> | |
<% if habit.calculate_days_lost > 0 %> | |
<%= habit.calculate_days_lost %> | |
<% end %> | |
</div> | |
<div class="days-left"> | |
<%= habit.days_left_in_current_level %> | |
</div> | |
</div> | |
</td> | |
<% if habit.current_level_strike.missed_days > 0 %> | |
<td class="condense-red"> | |
STRIKE <%= habit.current_level_strike.missed_days %> | |
</td> | |
<% else %> | |
<td class="condense-gold"> | |
STRIKE <%= habit.current_level_strike.missed_days %> | |
</td> | |
<% end %> | |
</tr> | |
<% end %> | |
<tr class="habit-space"> | |
<td colspan="3"> | |
<%= link_to '<span class="glyphicon glyphicon-ok"></span>'.html_safe, mark_completed_path(habit), remote: true, method: 'put', class: 'update_habit', id: 'home_check' %> | |
<%= link_to '<span class="glyphicon glyphicon-remove"></span>'.html_safe, habit_level_days_missed_index_path({ habit_id: @habit.id, level_id: @level.id }), remote: true, method: 'put', class: 'habit-check' %> | |
</td> | |
<td> | |
<% if habit.current_level_strike %> | |
<%= link_to habit_path(habit) do %> | |
<% if habit.trigger.present? %> | |
After I <%= habit.trigger %>, | |
<% end %> | |
I will <span class="label label-primary"><%= habit.action %></span> | |
<% if habit.target.present? %> | |
until <%= habit.target %> | |
<% end %> | |
<% if habit.reward.present? %> | |
so I may <%= habit.reward %> | |
<% end %>. | |
<% end %> | |
<% else %> | |
<div class="gold-standard"> | |
<%= link_to habit_path(habit) do %> | |
<% if habit.trigger.present? %> | |
After I <%= habit.trigger %>, | |
<% end %> | |
I will <span class="label label-warning"><%= habit.action %></span> | |
<% if habit.target.present? %> | |
until <%= habit.target %> | |
<% end %> | |
<% if habit.reward.present? %> | |
so I may <%= habit.reward %> | |
<% end %>. | |
<% end %> | |
</div> | |
<% end %> | |
</td> | |
</tr> | |
</div> | |
</tbody> | |
</table> | |
<script> | |
$('.update_habit').bind('ajax:success', function() { | |
$(this).closest('table').fadeOut(); | |
}); | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DaysMissedController < ApplicationController | |
before_action :logged_in_user, only: [:create, :destroy] | |
before_action :correct_user, only: [:create, :destroy] | |
def create | |
habit = Habit.find(params[:habit_id]) | |
habit.missed_days = habit.missed_days + 1 | |
@habit.save! | |
level = habit.levels.find(params[:level_id]) | |
level.missed_days = level.missed_days + 1 | |
if level.missed_days == 3 | |
level.missed_days = 0 | |
level.days_lost += habit.calculate_days_lost + 1 | |
end | |
level.save! | |
head :ok # this returns an empty response with a 200 success status code | |
end | |
def destroy | |
habit = Habit.find(params[:habit_id]) | |
habit.missed_days = habit.missed_days - 1 | |
habit.save! | |
level = habit.levels.find(params[:level_id]) | |
level.missed_days = level.missed_days - 1 | |
level.save! | |
head :ok # this returns an empty response with a 200 success status code | |
end | |
private | |
def correct_user | |
@habit = current_user.habits.find(params[:habit_id]) | |
redirect_to habits_path, notice: "Not authorized to edit this habit" if @habit.nil? | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source 'https://rubygems.org' | |
gem 'rails', '4.2.0.rc3' | |
gem 'sass-rails', '5.0.0.beta1' | |
gem 'uglifier', '2.5.3' | |
gem 'coffee-rails', '4.1.0' | |
gem 'jquery-rails', '4.0.0.beta2' | |
gem 'bootstrap-switch-rails' | |
gem 'jquery-ui-rails' | |
#gem 'turbolinks' | |
#gem 'jquery-turbolinks' | |
gem 'jbuilder', '2.2.3' | |
gem 'bcrypt', '3.1.7' | |
gem 'aws-sdk', '< 2.0' | |
gem 'will_paginate', '3.0.7' | |
gem 'bootstrap-will_paginate' | |
gem "letter_opener", :group => :development | |
gem 'bootstrap-sass' | |
gem 'cocoon' | |
gem 'simple_form' | |
gem 'acts-as-taggable-on' | |
gem 'haml' | |
gem 'sdoc', '0.4.0', group: :doc | |
gem 'omniauth-facebook' | |
gem 'koala', '2.0.0' | |
gem 'pg' | |
gem 'alphabetical_paginate' | |
gem 'paperclip' | |
gem 'masonry-rails' | |
group :development, :test do | |
gem 'pry-rails' | |
gem 'byebug', '3.4.0' | |
end | |
group :production do | |
gem 'rails_12factor', '0.0.2' #Needed for Heroku | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(document).ready(function() | |
{ | |
$(".habit-check").change(function() | |
{ | |
var submitUrl = $(this).parents("p").data("submit-url"); | |
var deleteUrl = $(this).parents("p").data("delete-url"); | |
if($(this).is(":checked")) | |
{ | |
$.ajax( | |
{ | |
url: submitUrl, | |
method: "POST" | |
}) | |
.done(function () { | |
location.reload(); | |
}); | |
} | |
else | |
{ | |
$.ajax( | |
{ | |
url: deleteUrl, | |
method: "DELETE" | |
}) | |
.done(function () { | |
location.reload(); | |
}); | |
} | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Habit < ActiveRecord::Base | |
scope :publish, ->{ where(:conceal => false) } | |
belongs_to :user | |
has_many :notes | |
has_many :notifications | |
accepts_nested_attributes_for :notes, :reject_if => :all_blank, :allow_destroy => true | |
has_many :comments | |
has_many :levels, -> { order(:id) } | |
serialize :committed, Array | |
validates :date_started, :action, presence: true | |
before_save :current_level | |
acts_as_taggable | |
has_many :habit_likes | |
has_many :likers, through: :habit_likes, class_name: 'User', source: :liker | |
before_save :set_tag_owner | |
scope :incomplete, -> {where("completed_at is null OR completed_at < ?", Time.current.beginning_of_day)} | |
def set_tag_owner | |
# Set the owner of some tags based on the current tag_list | |
set_owner_tag_list_on(self.user, :tags, self.tag_list) | |
# Clear the list so we don't get duplicate taggings | |
# self.tag_list = nil | |
end | |
attr_accessor :missed_one, :missed_two, :missed_three | |
def completed=(boolean) | |
self.completed_at = boolean ? Time.current : nil | |
end | |
def completed | |
completed_at && completed_at >= Time.current.beginning_of_day | |
end | |
def save_with_current_level | |
self.levels.build | |
self.levels.build | |
self.levels.build | |
self.levels.build | |
self.levels.build | |
self.save | |
end | |
def self.committed_for_today | |
today_name = Date::ABBR_DAYNAMES[Date.today.wday].downcase | |
ids = all.select { |h| h.committed.include? today_name }.map(&:id) | |
where(id: ids) | |
end | |
def current_level_strike | |
levels[current_level - 1] # remember arrays indexes start at 0 | |
end | |
def current_level | |
return 0 unless date_started | |
def committed_wdays | |
committed.map do |day| | |
Date::ABBR_DAYNAMES.index(day.titleize) | |
end | |
end | |
def n_days | |
((date_started.to_date)..Date.yesterday).count do |date| | |
committed_wdays.include? date.wday | |
end - self.real_missed_days | |
end | |
case n_days | |
when 0..9 | |
1 | |
when 10..24 | |
2 | |
when 25..44 | |
3 | |
when 45..69 | |
4 | |
when 70..99 | |
5 | |
else | |
6 | |
end | |
end | |
def real_missed_days | |
value = 0 | |
levels.each do |level| | |
value += level.missed_days + level.days_lost | |
end | |
value | |
end | |
def calculate_days_lost | |
def n_days | |
((date_started.to_date)..Date.yesterday).count do |date| | |
committed_wdays.include? date.wday | |
end - self.real_missed_days | |
end | |
case n_days | |
when 0..9 | |
n_days | |
when 10..24 | |
n_days-10 | |
when 25..44 | |
n_days-25 | |
when 45..69 | |
n_days-45 | |
when 70..99 | |
n_days-70 | |
else | |
n_days-100 | |
end | |
end | |
def days_left_in_current_level | |
def n_days | |
((date_started.to_date)..Date.yesterday).count do |date| | |
committed_wdays.include? date.wday | |
end - self.real_missed_days | |
end | |
case n_days | |
when 0..9 | |
10-n_days | |
when 10..24 | |
25-n_days | |
when 25..44 | |
45-n_days | |
when 45..69 | |
70-n_days | |
when 70..99 | |
100-n_days | |
else | |
0 # No end | |
end | |
end | |
def progress_in_percent | |
calculate_days_lost / (calculate_days_lost + days_left_in_current_level ).to_f * 100 | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class HabitsController < ApplicationController | |
before_action :set_habit, only: [:show, :like] | |
before_action :logged_in_user, only: [:create, :destroy, :mark_completed] | |
before_action :correct_user, only: [:edit, :update, :destroy, :mark_completed] | |
def index | |
if params[:tag] | |
@habits = Habit.tagged_with(params[:tag]) | |
else | |
@habits = current_user.habits.order(:order) | |
end | |
end | |
def show | |
@notable = @habit | |
@notes = @notable.notes | |
@note = Note.new | |
@commentable = @habit | |
@comments = @commentable.comments | |
@comment = Comment.new | |
@correct_user = current_user.habits.find_by(id: params[:id]) | |
end | |
def mark_completed | |
@habit.update(completed_at: Time.now) | |
respond_to do |format| | |
format.html | |
format.js { render :nothing => true } | |
end | |
end | |
def new | |
@habit = current_user.habits.build | |
end | |
def edit | |
end | |
def create | |
@habit = current_user.habits.build(habit_params) | |
if (params[:commit] == 'conceal') | |
@habit.conceal = true | |
@habit.save_with_current_level | |
redirect_to @habit, notice: 'Habit was successfully created. Remember, if you miss a "committed" day give yourself a strike. | |
3 strikes and your current level restarts. Good luck!' | |
elsif | |
@habit.save_with_current_level | |
track_activity @habit | |
redirect_to @habit, notice: 'Habit was successfully published. Remember, if you miss a "committed" day give yourself a strike. | |
3 strikes and your current level restarts. Good luck!' | |
else | |
flash.now[:danger] = 'Required Fields: "Committed to", "Started", and "Enter Habit"' | |
render 'new' | |
end | |
end | |
def update | |
if @habit.update(habit_params) | |
redirect_to @habit, notice: 'Habit was successfully updated.' | |
else | |
render action: 'edit' | |
end | |
end | |
def destroy | |
@habit.destroy | |
redirect_to habits_url | |
end | |
def like | |
@habit_like = current_user.habit_likes.build(habit: @habit) | |
if @habit_like.save | |
@habit.increment!(:likes) | |
flash[:success] = 'Thanks for liking!' | |
else | |
flash[:error] = 'Two many likes' | |
end | |
redirect_to(:back) | |
end | |
private | |
def set_habit | |
@habit = Habit.find(params[:id]) | |
redirect_to root_url unless @habit.user_id == current_user.id or @habit.conceal == false | |
end | |
def correct_user | |
@habit = current_user.habits.find_by(id: params[:id]) | |
redirect_to root_url, notice: "Not authorized to edit this habit" if @habit.nil? | |
end | |
def habit_params | |
params.require(:habit).permit( | |
:user_id, | |
:trigger, | |
:tag_list, | |
:current_level, | |
:missed_days, | |
:target, | |
:reward, | |
:comment, | |
:commentable, | |
:like, | |
:likeable, | |
:action, | |
:order, | |
:date_started, | |
:missed_one, | |
:completed, | |
:completed_at, | |
:notes_text, | |
:notes_date, | |
:notable, | |
:note, | |
:committed => [], | |
levels_attributes: [ | |
:missed_days, | |
:days_lost], notes_attributes: [:notable, :note, :notes_text, :notes_date, :_destroy]) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Level < ActiveRecord::Base | |
belongs_to :habit | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Rails.application.routes.draw do | |
put '/mark_completed/:id', to: 'habits#mark_completed', as: 'mark_completed' | |
put '/mark_accomplished/:id', to: 'goals#mark_accomplished', as: 'mark_accomplished' | |
get 'notes/index' | |
get 'notes/new' | |
get 'notifications/index' | |
get 'auth/:provider/callback', to: 'sessions#facebook' | |
get 'auth/failure', to: redirect('/') | |
get 'signout', to: 'sessions#destroy', as: 'signout' | |
get 'password_resets/new' | |
get 'password_resets/edit' | |
get "/users/:user_id/goals", to: "goals#user_goals", as: "user_goals" | |
shallow do | |
resources :habits do | |
resources :comments | |
resources :notes | |
resources :notifications | |
end | |
resources :valuations do | |
resources :comments | |
resources :notes | |
resources :notifications | |
end | |
resources :goals do | |
resources :comments | |
resources :notes | |
resources :notifications | |
end | |
resources :stats do | |
resources :comments | |
resources :notes | |
resources :notifications | |
end | |
end | |
resources :notes | |
resources :habits do | |
collection { post :sort } | |
resources :notes | |
resources :notifications | |
resources :comments do | |
resources :likes | |
end | |
resources :likes | |
member do | |
post :like | |
post :notifications | |
end | |
resources :levels do | |
# we'll use this route to increment and decrement the missed days | |
resources :days_missed, only: [:create, :destroy] | |
end | |
end | |
resources :goals do | |
resources :notes | |
resources :comments | |
member do | |
post :like | |
end | |
end | |
resources :valuations do | |
resources :notes | |
resources :comments | |
resources :notifications | |
member do | |
post :like | |
post :notifications | |
end | |
end | |
resources :stats do | |
resources :notes | |
resources :comments | |
resources :notifications | |
member do | |
post :like | |
end | |
end | |
resources :results | |
resources :users | |
resources :account_activations, only: [:edit] | |
resources :activities do | |
resources :valuations | |
resources :habits | |
resources :stats | |
resources :goals | |
end | |
resources :notifications do | |
resources :valuations | |
resources :habits | |
resources :stats | |
resources :goals | |
resources :comments | |
end | |
resources :comments do | |
resources :comments | |
resources :notifications | |
member do | |
post :like | |
end | |
end | |
resources :password_resets, only: [:new, :create, :edit, :update] | |
resources :relationships, only: [:create, :destroy] | |
get 'tags/:tag', to: 'pages#home', as: :tag | |
resources :users do | |
member do | |
get :following, :followers | |
end | |
end | |
get 'about' => 'pages#about' | |
get 'signup' => 'users#new' | |
get 'login' => 'sessions#new' | |
post 'login' => 'sessions#create' | |
delete 'logout' => 'sessions#destroy' | |
root 'pages#home' | |
# The priority is based upon order of creation: first created -> highest priority. | |
# See how all your routes lay out with "rake routes". | |
# You can have the root of your site routed with "root" | |
# root 'welcome#index' | |
# Example of regular route: | |
# get 'products/:id' => 'catalog#view' | |
# Example of named route that can be invoked with purchase_url(id: product.id) | |
# get 'products/:id/purchase' => 'catalog#purchase', as: :purchase | |
# Example resource route (maps HTTP verbs to controller actions automatically): | |
# resources :products | |
# Example resource route with options: | |
# resources :products do | |
# member do | |
# get 'short' | |
# post 'toggle' | |
# end | |
# | |
# collection do | |
# get 'sold' | |
# end | |
# end | |
# Example resource route with sub-resources: | |
# resources :products do | |
# resources :comments, :sales | |
# resource :seller | |
# end | |
# Example resource route with more complex sub-resources: | |
# resources :products do | |
# resources :comments | |
# resources :sales do | |
# get 'recent', on: :collection | |
# end | |
# end | |
# Example resource route with concerns: | |
# concern :toggleable do | |
# post 'toggle' | |
# end | |
# resources :posts, concerns: :toggleable | |
# resources :photos, concerns: :toggleable | |
# Example resource route within a namespace: | |
# namespace :admin do | |
# # Directs /admin/products/* to Admin::ProductsController | |
# # (app/controllers/admin/products_controller.rb) | |
# resources :products | |
# end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create_table "habits", force: true do |t| | |
t.boolean "conceal", default: false | |
t.integer "missed_days", default: 0 | |
t.integer "likes" | |
t.text "committed" | |
t.datetime "date_started" | |
t.string "trigger" | |
t.string "action" | |
t.string "target" | |
t.string "reward" | |
t.integer "user_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.integer "order" | |
end | |
add_index "habits", ["user_id", "created_at"], name: "index_habits_on_user_id_and_created_at", using: :btree | |
add_index "habits", ["user_id"], name: "index_habits_on_user_id", using: :btree | |
create_table "levels", force: true do |t| | |
t.integer "habit_id" | |
t.integer "missed_days", default: 0 | |
t.integer "days_lost", default: 0 | |
t.integer "current_level" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
end | |
add_index "levels", ["habit_id"], name: "index_levels_on_habit_id", using: :btree |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment