Skip to content

Instantly share code, notes, and snippets.

@RallyWithGalli
Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save RallyWithGalli/c66dee6dfb9ab5d338c2 to your computer and use it in GitHub Desktop.
Save RallyWithGalli/c66dee6dfb9ab5d338c2 to your computer and use it in GitHub Desktop.
Structure & Gamification for Developing Good Habits
<%= 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' %>, &nbsp;
</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' %> &nbsp;
</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 %>
&nbsp;
<%= 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 %>
<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>
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
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
$(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();
});
}
});
});
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
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
class Level < ActiveRecord::Base
belongs_to :habit
end
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
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