Commit 25e3e70

mo khan <mo@mokhan.ca>
2016-06-24 04:00:10
refactor model and drop dead code.
1 parent b9daad6
Changed files (58)
app
config
db
spec
app/assets/javascripts/models/google_drive.js.coffee
@@ -1,40 +0,0 @@
-class Stronglifters.GoogleDrive
-  constructor: (options) ->
-    @google = gapi
-    @client_id = options.client_id
-    @drive_upload_path = options.drive_upload_path
-    @scopes = [
-      'https://www.googleapis.com/auth/drive',
-      'https://www.googleapis.com/auth/drive.install',
-      'https://www.googleapis.com/auth/drive.file',
-      'https://www.googleapis.com/auth/drive.readonly',
-      'https://www.googleapis.com/auth/drive.apps.readonly',
-    ]
-
-  syncFile: =>
-    query = "title contains '.stronglifts' and title contains 'backup'"
-    @searchFor query, @uploadFile
-
-  searchFor: (query, callback) =>
-    @loadDrive =>
-      @google.client.drive.files.list({ 'q': query }).execute(callback)
-
-  uploadFile: (response) =>
-    item = response.items[0]
-    $.post(@drive_upload_path, {
-      accessToken: @google.auth.getToken().access_token,
-      data: item
-    }).done (data) ->
-      window.location.reload()
-
-  authorize: (callback) ->
-    @google.auth.authorize({
-      'client_id': @client_id,
-      'scope': @scopes,
-      'immediate': false
-    }, callback)
-
-  loadDrive: (callback) =>
-    @authorize (response) =>
-      @google.load 'drive-share', =>
-        @google.client.load 'drive', 'v2', callback
app/assets/javascripts/templates/exercise_view.ractive
@@ -1,22 +0,0 @@
-<!--
-<div class="panel small-12 columns">
-  <div class="row">
-    <div class="small-6 columns">
-      {{name}}
-    </div>
-    <div class="small-6 columns">
-      <p class="text-right">
-      {{sets}}x{{repetitions}} {{target_weight}}lb
-      </p>
-    </div>
-  </div>
-
-  <div class="row">
-    <div class="small-12 columns">
-    {{#each reps}}
-      <button on-click="updateProgress" class="button small round {{status}}">{{completed}}</button>
-    {{/each}}
-    </div>
-  </div>
-</div>
--->
app/assets/javascripts/templates/google_sync_button.ractive
@@ -1,1 +0,0 @@
-<button on-click='synchronize'><i class='fa {{icon}}'></i> {{text}}</button>
app/assets/javascripts/templates/training_session_view.ractive
@@ -1,33 +1,12 @@
 <div class="row">
   {{#each training_session.exercises}}
   <div class="panel small-12 columns">
-    {{name}}
-    {{#if completed}}
-      <div class="row">
-        <div class="small-12 columns">
-          Good Work!
-        </div>
-      </div>
-    {{else}}
-      <div class="row">
-        <div class="small-12 columns">
-          <p class="text-center">
-            {{#each sets}}
-              <button on-click="updateProgress" class="button small round {{status}}">
-                {{completed}}
-              </button>
-              @ {{weight}} lb
-            {{/each}}
-          </p>
-        </div>
-      </div>
-      <div class="row">
-        <div class="small-12 columns">
-          <button on-click="completeExercise" class="button expand">Done</button>
-        </div>
-      </div>
-    {{/if}}
-
+    <h3>{{name}}</h3>
+    {{#each sets}}
+      <button on-click="updateProgress" class="button small round {{status}}">
+        {{actual_repetitions}}/{{target_repetitions}} @ {{target_weight}} lb
+      </button>
+    {{/each}}
   </div>
   {{/each}}
 </div>
app/assets/javascripts/views/exercise_view.js.coffee
@@ -1,6 +0,0 @@
-#= require templates/exercise_view
-class Stronglifters.ExerciseView extends Ractive
-  template: RactiveTemplates["templates/exercise_view"]
-  data:
-    workout_name: ''
-
app/assets/javascripts/views/google_sync_button.js.coffee
@@ -1,16 +0,0 @@
-#= require models/google_drive
-Stronglifters.GoogleSyncButton = Ractive.extend
-  template: RactiveTemplates["templates/google_sync_button"]
-  oninit: ->
-    @set(text: 'Sync with Google', icon: 'fa-camera-retro')
-    @on 'synchronize', (event) ->
-      @synchronize()
-
-  synchronize: ->
-    @set(text: 'Syncing...', icon: 'fa-spin fa-spinner')
-    @drive().syncFile()
-
-  drive: ->
-    @_drive ||= new Stronglifters.GoogleDrive
-      client_id: @get('client_id')
-      drive_upload_path: @get('drive_upload_path')
app/assets/javascripts/views/training_session_view.js.coffee
@@ -3,44 +3,40 @@ class Stronglifters.TrainingSessionView extends Ractive
 
   oninit: ->
     @on 'updateProgress', (event) -> @updateProgress(event)
-    @on 'completeExercise', (event) -> @completeExercise(event.context)
     @observe 'training_session.exercises.*.sets.*', (newValue, oldValue, keypath) ->
       @refreshStatus(newValue, oldValue, keypath)
 
   updateProgress: (event) ->
-    completed = @get("#{event.keypath}.completed")
+    completed = @get("#{event.keypath}.actual_repetitions")
     if completed == null || completed == 0
-      @set("#{event.keypath}.completed", @get("#{event.keypath}.target"))
+      @set("#{event.keypath}.actual_repetitions", @get("#{event.keypath}.target_repetitions"))
     else
-      @subtract("#{event.keypath}.completed")
+      @subtract("#{event.keypath}.actual_repetitions")
+    @saveSet(@get(event.keypath))
 
-  refreshStatus: (newValue, oldValue, keyPath) ->
-    if @get("#{keyPath}.completed") == 0
-      @set("#{keyPath}.status", "secondary")
-      return
-
-    if @get("#{keyPath}.target") == @get("#{keyPath}.completed")
-      @set("#{keyPath}.status", "success")
-    else
-      @set("#{keyPath}.status", "alert")
-
-  completeExercise: (exercise) ->
-    payload =
-      training_session:
-        exercise_id: exercise.id
-        sets: _.map exercise.sets, (set) ->
-          completed: set.completed
-          weight: exercise.target_weight
-    console.log(payload)
+  saveSet: (set) ->
+    @patch "/exercise_sets/#{set.id}",
+      exercise_set:
+        actual_repetitions: set.actual_repetitions
 
+  patch: (url, payload) ->
     $.ajax
-      url: "/training_sessions/#{@get('training_session.id')}",
+      url: url,
       dataType: 'json',
       type: 'patch',
       contentType: 'application/json',
       data: JSON.stringify(payload),
-      success: (gym, statux, xhr) =>
-        exercise.completed = true
-        @updateModel()
+      success: (data, statux, xhr) =>
+        console.log(data)
       error: (xhr, status, error) ->
         console.log(error)
+
+  refreshStatus: (newValue, oldValue, keypath) ->
+    if @get("#{keypath}.actual_repetitions") == null
+      @set("#{keypath}.status", "secondary")
+      return
+
+    if @get("#{keypath}.target_repetitions") == @get("#{keypath}.actual_repetitions")
+      @set("#{keypath}.status", "success")
+    else
+      @set("#{keypath}.status", "alert")
app/controllers/exercise_sets_controller.rb
@@ -0,0 +1,13 @@
+class ExerciseSetsController < ApplicationController
+  def update
+    exercise_session = current_user.exercise_sets.find(params[:id])
+    exercise_session.update!(secure_params)
+    render json: {}
+  end
+
+  private
+
+  def secure_params
+    params.require(:exercise_set).permit(:actual_repetitions)
+  end
+end
app/controllers/training_sessions_controller.rb
@@ -1,36 +1,23 @@
 class TrainingSessionsController < ApplicationController
-  after_action :allow_google_iframe, only: [:index]
-
   def index
     @training_sessions = paginate(
       current_user.
       training_sessions.
-      includes(:workout, :program, exercise_sessions: [:exercise]).
+      includes(:workout, :program, exercise_sets: [:exercise]).
       order(occurred_at: :desc)
     )
   end
 
   def new
     @workout = current_user.next_workout
-    @training_session = current_user.training_sessions.build(workout: @workout)
-    @workout.exercise_workouts.each do |exercise|
-      exercise_session = @training_session.exercise_sessions.build(exercise_workout: exercise)
-      exercise.sets.times do
-        exercise_session.exercise_sets.build(
-          target_repetitions: exercise.repetitions,
-          target_weight: current_user.next_weight_for(exercise.exercise)
-        )
-      end
-    end
+    @training_session = current_user.next_training_session_for(@workout)
   end
 
   def create
-    secure_params = params.require(:training_session).permit(:workout_id, :body_weight, exercise_sessions_attributes: [
-      :exercise_workout_id,
-      exercise_sets_attributes: [
-        :target_repetitions,
-        :target_weight,
-      ]
+    secure_params = params.require(:training_session).permit(:workout_id, :body_weight, exercise_sets_attributes: [
+      :target_repetitions,
+      :target_weight,
+      :exercise_id
     ])
     workout = Workout.find(secure_params[:workout_id])
     training_session = current_user.begin_workout(
@@ -45,43 +32,4 @@ class TrainingSessionsController < ApplicationController
   def edit
     @training_session = current_user.training_sessions.find(params[:id])
   end
-
-  def update
-    #render json: params and return
-    secure_params = params.require(:training_session).permit(:exercise_id, :weight, sets: [
-      :weight,
-      :completed
-    ])
-    @training_session = current_user.training_sessions.find(params[:id])
-    secure_params[:sets].each_with_index do |set, index|
-      @training_session.train(
-        Exercise.find(secure_params[:exercise_id]),
-        set[:weight],
-        repetitions: set[:completed],
-        set: index
-      )
-    end
-  end
-
-  def upload
-    backup_file = BackupFile.new(current_user, params[:backup])
-
-    if backup_file.valid?
-      backup_file.process_later(Program.stronglifts)
-      redirect_to dashboard_path, notice: t(".success")
-    else
-      redirect_to dashboard_path, alert: t(".failure")
-    end
-  end
-
-  def drive_upload
-    DownloadFromDriveJob.perform_later(current_user, params)
-    redirect_to dashboard_path, notice: t(".success")
-  end
-
-  private
-
-  def allow_google_iframe
-    response.headers["X-Frame-Options"] = "ALLOW-FROM https://drive.google.com"
-  end
 end
app/jobs/download_from_drive_job.rb
@@ -1,9 +0,0 @@
-class DownloadFromDriveJob < ActiveJob::Base
-  queue_as :default
-
-  def perform(user, params)
-    user.google_drive.download(params) do |backup_file|
-      backup_file.process_later(Program.stronglifts)
-    end
-  end
-end
app/jobs/upload_stronglifts_backup_job.rb
@@ -19,8 +19,6 @@ class UploadStrongliftsBackupJob < ActiveJob::Base
 
   def importer_for(directory, user, program)
     [
-      Android::Import.new(user, program),
-      Ios::Import.new(user, program),
       Csv::Import.new(user, program),
       UnknownFile.new
     ].detect { |x| x.can_parse?(directory) }
app/models/android/import.rb
@@ -1,92 +0,0 @@
-class Android::Import
-  attr_reader :user, :program
-
-  def initialize(user, program)
-    @user = user
-    @program = program
-  end
-
-  def can_parse?(directory)
-    File.exist?(database_file(directory))
-  end
-
-  def import_from(directory)
-    database(directory) do |db|
-      ActiveRecord::Base.transaction do
-        db[:workouts].each do |row|
-          import(row)
-        end
-      end
-    end
-  end
-
-  private
-
-  def import(row)
-    create_workout_from(map_from(row))
-  end
-
-  def database_file(dir)
-    "#{dir}/stronglifts.db"
-  end
-
-  def database(directory)
-    yield Sequel.sqlite(database_file(directory))
-  end
-
-  def create_workout_from(workout_row)
-    training_session_for(workout_row) do |training_session, workout|
-      workout.exercise_workouts.each_with_index do |exercise_workout, index|
-        exercise_row = workout_row.exercises[index]
-        next if exercise_row.nil?
-        sets_from(exercise_workout, exercise_row).each_with_index do |reps, set|
-          training_session.train(
-            exercise_workout.exercise,
-            exercise_row["warmup"]["targetWeight"],
-            repetitions: reps,
-            set: set,
-          )
-        end
-      end
-    end
-  end
-
-  def training_session_for(workout_row)
-    workout = program.workouts.find_by(name: workout_row.workout)
-    user.begin_workout(
-      workout,
-      workout_row.date,
-      workout_row.body_weight.to_f
-    ).tap do |training_session|
-      yield training_session, workout
-      training_session
-    end
-  end
-
-  def sets_from(exercise_workout, exercise_row)
-    sets = []
-    1.upto(exercise_workout.sets).each do |n|
-      if exercise_row["set#{n}"].to_i > 0
-        sets << exercise_row["set#{n}"]
-      else
-        sets << 0
-      end
-    end
-    sets
-  end
-
-  def map_from(row)
-    Android::Workout.new(
-      id: row[:id],
-      date: user.time_zone.local_to_utc(row[:date]),
-      workout: row[:workout],
-      exercise_1: JSON.parse(row[:e1]),
-      exercise_2: JSON.parse(row[:e2]),
-      exercise_3: JSON.parse(row[:e3]),
-      note: row[:note],
-      body_weight: row[:bodyWeight],
-      arm_work: row[:armWork].present? ? JSON.parse(row[:armWork]) : nil,
-      temp: row[:temp]
-    )
-  end
-end
app/models/android/workout.rb
@@ -1,19 +0,0 @@
-class Android::Workout
-  attr_accessor :id, :date, :workout
-  attr_accessor :exercise_1, :exercise_2, :exercise_3
-  attr_accessor :note, :body_weight, :arm_work, :temp
-
-  def initialize(attributes = {})
-    attributes.each do |attribute|
-      send("#{attribute.first}=", attribute.last)
-    end
-  end
-
-  def exercises
-    @exercises ||= [
-      exercise_1,
-      exercise_2,
-      exercise_3,
-    ]
-  end
-end
app/models/csv/import.rb
@@ -33,6 +33,7 @@ class Csv::Import
       exercise_row = workout_row.find(exercise)
       next if exercise_row.nil?
       exercise_row.sets.compact.each_with_index do |completed_reps, index|
+        next if completed_reps.blank?
         training_session.train(
           exercise,
           exercise_row.weight_lb,
app/models/ios/import.rb
@@ -1,72 +0,0 @@
-class Ios::Import
-  attr_reader :user, :program
-
-  def initialize(user, program)
-    @user = user
-    @program = program
-  end
-
-  def can_parse?(directory)
-    File.exist?(database_file(directory))
-  end
-
-  def import_from(directory)
-    database(directory) do |db|
-      db[:ZBASEWORKOUT].each do |row|
-        workout_name = row[:ZTYPE] == 1 ? "A" : "B"
-        workout = program.workouts.find_by(name: workout_name)
-        time = row[:ZDATE].to_s.split(' ')
-        date_string = "#{row[:ZLOGDATE]} #{time[1]} #{time[2]}"
-        occurred_at = DateTime.parse(date_string)
-        body_weight = row[:ZBODYWEIGHT].to_f
-        training_session = user.begin_workout(workout, occurred_at, body_weight)
-
-        db[:ZEXERCISESETS].where(ZWORKOUT: row[:Z_PK]).
-          each do |exercise_set_row|
-          db[:ZEXERCISE].where(ZTYPE: exercise_set_row[:ZEXERCISETYPE]).
-            each do |exercise_row|
-            exercise = exercise_from(exercise_row)
-            if exercise
-              db[:ZWEIGHT].
-                where(Z_PK: exercise_set_row[:ZWEIGHT]).each do |weight_row|
-                target_weight = weight_row[:ZVAL]
-                sets_from(exercise_set_row).each_with_index do |reps, set|
-                  training_session.train(exercise, target_weight, repetitions: reps, set: set)
-                end
-              end
-            end
-          end
-        end
-      end
-    end
-  end
-
-  private
-
-  def sets_from(row)
-    (1..5).inject([]) do |memo, n|
-      column = "ZSET#{n}".to_sym
-      memo << row[column] if row[column] && row[column] != -3
-    end || []
-  end
-
-  def database_file(directory)
-    File.join(directory, "SLDB.sqlite")
-  end
-
-  def database(directory)
-    yield Sequel.sqlite(database_file(directory))
-  end
-
-  def exercise_from(exercise_row)
-    mapping = {
-      1 => "Squat",
-      2 => "Bench Press",
-      3 => "Barbell Row",
-      4 => "Overhead Press",
-      5 => "Deadlift",
-    }
-
-    program.exercises.find_by(name: mapping[exercise_row[:ZTYPE]])
-  end
-end
app/models/exercise.rb
@@ -1,7 +1,4 @@
 class Exercise < ActiveRecord::Base
-  has_many :exercise_workouts
-  has_many :workouts, through: :exercise_workouts
-
   def short_name
     name.gsub(/[^A-Z]/, "")
   end
app/models/exercise_session.rb
@@ -1,21 +1,16 @@
-class ExerciseSession < ActiveRecord::Base
-  belongs_to :training_session
-  belongs_to :exercise_workout
-  has_one :exercise, through: :exercise_workout
-  has_many :exercise_sets, dependent: :destroy
-  delegate :name, to: :exercise
-  accepts_nested_attributes_for :exercise_sets
+#class ExerciseSession < ActiveRecord::Base
+  #belongs_to :training_session
+  #belongs_to :exercise_workout
+  #has_one :exercise, through: :exercise_workout
+  #has_many :exercise_sets, dependent: :destroy
+  #delegate :name, to: :exercise
+  #accepts_nested_attributes_for :exercise_sets
 
-  def sets
-    exercise_sets
-  end
-
-  def to_sets
-    sets.map(&:actual_repetitions)
-  end
+  #def sets
+    #exercise_sets
+  #end
 
-  #def target_weight
-    ## shim for migrations
-    #read_attribute(:target_weight) || sets.first.target_weight
+  #def to_sets
+    #sets.map(&:actual_repetitions)
   #end
-end
+#end
app/models/exercise_set.rb
@@ -1,4 +1,4 @@
 class ExerciseSet < ActiveRecord::Base
   belongs_to :exercise_session
-  has_one :exercise, through: :exercise_session
+  belongs_to :exercise
 end
app/models/google_drive.rb
@@ -1,31 +0,0 @@
-class GoogleDrive
-  attr_reader :user
-
-  def initialize(user, referrer_domain: ENV["GOOGLE_REFERRER_DOMAIN"])
-    @user = user
-    @referrer_domain = referrer_domain
-  end
-
-  def download(params)
-    Dir.mktmpdir do |dir|
-      download_path = File.join(dir, params[:data][:title])
-      url = params[:data][:downloadUrl].strip
-      access_token = params[:accessToken]
-      yield BackupFile.new(user, curl(url, download_path, access_token))
-    end
-  end
-
-  private
-
-  def curl(download_url, download_path, access_token)
-    curl = Shell.new("curl")
-    curl << "'#{download_url}'"
-    curl << "-o '#{download_path}'"
-    curl << "-H 'Authorization: Bearer #{access_token}'"
-    curl << "-H 'Referer: #{@referrer_domain}/dashboard'"
-    curl << "-H 'Origin: #{@referrer_domain}'"
-    curl << "--compressed"
-    curl.run
-    File.new(download_path)
-  end
-end
app/models/program.rb
@@ -2,6 +2,8 @@ class Program < ActiveRecord::Base
   STRONG_LIFTS = "StrongLifts 5ร—5"
   has_many :exercises, through: :workouts
   has_many :workouts
+  has_many :exercise_workouts, through: :workouts
+
   before_save do
     self.slug = name.parameterize
   end
@@ -20,6 +22,29 @@ class Program < ActiveRecord::Base
     workouts.where.not(name: workout.name).first
   end
 
+  def prepare_sets_for(user, exercise)
+    recommended_sets_for(user, exercise).times.map do
+      ExerciseSet.new(
+        exercise: exercise,
+        target_repetitions: recommended_reps_for(user, exercise),
+        target_weight: user.next_weight_for(exercise)
+      )
+    end
+  end
+
+  def recommended_sets_for(user, exercise)
+    recommended_sets = user.history_for(exercise).last_target_sets
+    recommended_sets > 0 ? recommended_sets : recommendation_for(user, exercise).sets
+  end
+
+  def recommended_reps_for(user, exercise)
+    recommendation_for(user, exercise).repetitions
+  end
+
+  def recommendation_for(user, exercise)
+    exercise_workouts.find_by(exercise: exercise)
+  end
+
   class << self
     def stronglifts
       Program.find_by(name: STRONG_LIFTS)
app/models/progress.rb
@@ -0,0 +1,20 @@
+class Progress
+  attr_reader :exercise, :training_session
+
+  def initialize(training_session, exercise)
+    @exercise = exercise
+    @training_session = training_session
+  end
+
+  def to_sets
+    sets.pluck(:actual_repetitions)
+  end
+
+  def max_weight
+    sets.maximum(:target_weight)
+  end
+
+  def sets
+    training_session.exercise_sets.where(exercise: exercise).order(:created_at)
+  end
+end
app/models/shell.rb
@@ -1,20 +0,0 @@
-class Shell
-  def initialize(program)
-    @program = program
-    @options = []
-  end
-
-  def <<(option)
-    @options.push(option)
-  end
-
-  def run
-    `#{build_command}`
-  end
-
-  private
-
-  def build_command
-    "#{@program} #{@options.join(' ')}"
-  end
-end
app/models/training_history.rb
@@ -16,18 +16,21 @@ class TrainingHistory
   end
 
   def completed_any?
-    user.
-      exercise_sessions.
-      joins(:exercise).
-      any?
+    user.exercise_sets.where(exercise: exercise).any?
+  end
+
+  def last_target_sets
+    last_training_session = user.last_training_session(exercise)
+    return 0 if last_training_session.blank?
+    last_training_session.exercise_sets.where(exercise: exercise).count
   end
 
   def last_weight
     user.
-      exercise_sessions.
+      exercise_sets.
       joins(:exercise).
-      joins(:training_session).
       where(exercises: { id: exercise.id }).
+      where('actual_repetitions = target_repetitions').
       order('training_sessions.occurred_at').
       last.try(:target_weight).to_i
   end
@@ -37,14 +40,10 @@ class TrainingHistory
   end
 
   def to_line_chart
-    user.
-      exercise_sessions.
-      includes(:training_session).
-      joins(:exercise).
-      where(exercises: { name: exercise.name }).
-      inject({}) do |memo, session|
-        memo[session.training_session.occurred_at] = session.sets.first.target_weight
-        memo
-      end
+    user.training_sessions.inject({}) do |memo, training_session|
+      memo[training_session.occurred_at] =
+        training_session.exercise_sets.where(exercise: exercise).maximum(:target_weight)
+      memo
+    end
   end
 end
app/models/training_session.rb
@@ -2,26 +2,29 @@ class TrainingSession < ActiveRecord::Base
   belongs_to :user
   belongs_to :workout
   has_one :program, through: :workout
-  has_many :exercise_sessions, dependent: :destroy
-  has_many :exercises, through: :exercise_sessions
-  accepts_nested_attributes_for :exercise_sessions
+  has_many :exercises, through: :exercise_sets
+  has_many :exercise_sets, dependent: :destroy
+  accepts_nested_attributes_for :exercise_sets
 
   def train(exercise, target_weight, repetitions:, set: nil)
-    recommendation = workout.exercise_workouts.find_by(exercise: exercise)
-
-    session = exercise_sessions.find_or_create_by(exercise_workout: recommendation)
-    exercise_set = set.present? && session.sets.at(set).present? ? session.sets.at(set) : session.sets.build
-    exercise_set.update!(
-      actual_repetitions: repetitions,
-      target_repetitions: recommendation.repetitions,
-      target_weight: target_weight,
-    )
-    session
+    exercise_set =
+      if set.present? && exercise_sets.where(exercise: exercise).at(set).present?
+        exercise_sets.where(exercise: exercise).at(set)
+      else
+        exercise_sets.build(
+          exercise: exercise,
+          target_repetitions: program.recommended_reps_for(user, exercise)
+        )
+      end
+    exercise_set.update!(actual_repetitions: repetitions, target_weight: target_weight)
+    exercise_set
   end
 
   def progress_for(exercise)
-    exercise_sessions.
-      joins(exercise_workout: :exercise).
-      find_by(exercise_workouts: { exercise_id: exercise.id })
+    Progress.new(self, exercise)
+  end
+
+  def sets
+    exercise_sets
   end
 end
app/models/user.rb
@@ -2,8 +2,7 @@ class User < ActiveRecord::Base
   include Flippable
   has_secure_password
   has_many :training_sessions
-  has_many :exercise_sessions, through: :training_sessions
-  has_many :exercise_sets, through: :exercise_sessions
+  has_many :exercise_sets, through: :training_sessions
   has_many :user_sessions, dependent: :destroy
   has_one :profile
   has_many :received_emails
@@ -89,12 +88,27 @@ class User < ActiveRecord::Base
     last_workout.next_workout
   end
 
-  def current_program
-    Program.stronglifts
+  def next_training_session_for(workout = next_workout)
+    last_body_weight = last_training_session.try(:body_weight)
+    training_sessions.build(workout: workout, body_weight: last_body_weight).tap do |training_session|
+      workout.prepare_sets_for(self, training_session)
+    end
   end
 
-  def google_drive
-    GoogleDrive.new(self)
+  def last_training_session(exercise = nil)
+    if exercise.present?
+      training_sessions.
+        joins(:exercises).
+        where(exercises: { id: exercise.id }).
+        order(:created_at).
+        last
+    else
+      training_sessions.order(:created_at).last
+    end
+  end
+
+  def current_program
+    Program.stronglifts
   end
 
   class << self
app/models/workout.rb
@@ -26,4 +26,10 @@ class Workout < ActiveRecord::Base
   def next_workout
     program.next_workout_after(self)
   end
+
+  def prepare_sets_for(user, training_session)
+    exercises.each do |exercise|
+      training_session.exercise_sets << program.prepare_sets_for(user, exercise)
+    end
+  end
 end
app/views/training_sessions/_training_session.html.erb
@@ -5,8 +5,8 @@
   <td><%= I18n.l training_session.occurred_at, format: :short %></td>
   <td><%= training_session.body_weight %> lbs</td>
   <td>
-  <% training_session.exercise_sessions.each do |exercise_session| %>
-    <strong><%= exercise_session.exercise.name %></strong> @ <%= exercise_session.sets.maximum(:target_weight) %> lbs,
+  <% training_session.exercises.each do |exercise| %>
+    <strong><%= exercise.name %></strong> @ <%= training_session.progress_for(exercise).max_weight %> lbs,
   <% end %>
   </td>
   <td> <%= link_to "edit", edit_training_session_path(training_session) %> </td>
app/views/training_sessions/_training_session.jbuilder
@@ -1,13 +1,13 @@
 json.id training_session.id
 json.body_weight training_session.body_weight
 json.workout_name training_session.workout.name
-json.exercises training_session.exercise_sessions.order(:created_at) do |exercise|
-  json.id exercise.exercise.id
+json.exercises training_session.exercise_sets.order(:created_at).group_by(&:exercise) do |exercise, exercise_sets|
+  json.id exercise.id
   json.name exercise.name
-  json.sets exercise.sets.order(:created_at) do |set|
-    json.weight set.target_weight
-    json.target set.target_repetitions
-    json.completed set.actual_repetitions
+  json.sets exercise_sets.sort_by(&:created_at) do |set|
+    json.id set.id
+    json.target_weight set.target_weight
+    json.target_repetitions set.target_repetitions
+    json.actual_repetitions set.actual_repetitions
   end
-  json.target_weight current_user.next_weight_for(exercise.exercise)
 end
app/views/training_sessions/edit.html.erb
@@ -1,6 +1,5 @@
 <div class="row">
   <div class="large-12 columns">
-<% render partial: 'edit.json.jbuilder', locals: { training_session: @training_session } %>;
     <div id="training-session-view"></div>
     <%= link_to t(:back_html), training_sessions_path %>
   </div>
app/views/training_sessions/new.html.erb
@@ -5,12 +5,11 @@
         <legend><%= TrainingSession.human_attribute_name(:body_weight) %></legend>
         <%= f.number_field :body_weight %>
       </fieldset>
-      <% @training_session.exercise_sessions.each do |exercise_session| %>
-        <%= f.fields_for :exercise_sessions, exercise_session do |e| %>
+      <% @training_session.exercise_sets.group_by(&:exercise).each do |exercise, exercise_sets| %>
           <fieldset>
-            <legend><%= e.object.exercise.name %></legend>
-            <% exercise_session.exercise_sets.each.with_index(1) do |set, index| %>
-              <%= e.fields_for :exercise_sets, set do |s| %>
+            <legend><%= exercise.name %></legend>
+            <% exercise_sets.each.with_index(1) do |set, index| %>
+              <%= f.fields_for :exercise_sets, set do |s| %>
                 <fieldset>
                   <legend>Set <%= index %></legend>
                   <div class="row">
@@ -24,13 +23,13 @@
                       <%= s.number_field :target_weight %>
                     </div>
                   </div>
+                  <%= s.hidden_field :exercise_id %>
                 </fieldset>
               <% end %>
             <% end %>
 
-            <%= e.hidden_field :exercise_workout_id %>
+            <%= f.hidden_field :workout_id %>
           </fieldset>
-        <% end %>
       <% end %>
       <%= f.hidden_field :workout_id %>
       <%= f.submit "Start", class: "button round right" %>
config/locales/en.yml
@@ -93,8 +93,6 @@ en:
       login_button: "Login"
       register_link: "Create an account"
   training_sessions:
-    drive_upload:
-      success: 'Our minions have been summoned to fetch your backup.'
     import_address_modal:
       blurb: This is your custom email address for sending CSV exports from the <a href="http://stronglifts.com/apps/">StrongLifts 5x5 mobile application</a>.
       copy: Copy
@@ -102,8 +100,5 @@ en:
       lead: Your custom email address.
       title: Send-to-Stronglifters.
       warning: To ensure your workouts are imported correctly we suggest that you update your timezone.
-    upload:
-      success: "Our minions have been summoned to unpack your backup."
-      failure: "We don't know how to unpack this type of file."
   footer:
     copyright: "ยฉ Copyright www.stronglifters.com. All rights reserved."
config/routes.rb
@@ -2,12 +2,8 @@ Rails.application.routes.draw do
   root "sessions#new"
   resources :sessions, only: [:new, :create, :destroy]
   resources :registrations, only: [:new, :create]
-  resources :training_sessions, only: [:index, :new, :create, :edit, :update] do
-    collection do
-      post :upload
-      post :drive_upload
-    end
-  end
+  resources :exercise_sets, only: [:update]
+  resources :training_sessions, only: [:index, :new, :create, :edit]
   resources :programs, only: [:show]
   resources :profiles, only: [:new, :create, :show, :edit, :update], constraints: { id: /[^\/]+/ }
   resources :gyms, only: [:index, :show, :new, :create]
db/migrate/20160623202628_add_exercise_id_to_exercise_sets.rb
@@ -0,0 +1,5 @@
+class AddExerciseIdToExerciseSets < ActiveRecord::Migration
+  def change
+    add_column :exercise_sets, :exercise_id, :uuid
+  end
+end
db/migrate/20160623202817_migrate_exercise_id_to_exercise_sets.rb
@@ -0,0 +1,8 @@
+class MigrateExerciseIdToExerciseSets < ActiveRecord::Migration
+  def change
+    ExerciseSet.where(exercise_id: nil).find_each do |set|
+      set.exercise = set.exercise_session.exercise
+      set.save!
+    end
+  end
+end
db/migrate/20160623203043_make_exercise_id_not_null.rb
@@ -0,0 +1,5 @@
+class MakeExerciseIdNotNull < ActiveRecord::Migration
+  def change
+    change_column :exercise_sets, :exercise_id, :uuid, null: false
+  end
+end
db/migrate/20160623205223_add_training_session_id_to_exercise_sets.rb
@@ -0,0 +1,5 @@
+class AddTrainingSessionIdToExerciseSets < ActiveRecord::Migration
+  def change
+    add_column :exercise_sets, :training_session_id, :uuid
+  end
+end
db/migrate/20160623205309_migrate_training_session_id_to_exercise_sets.rb
@@ -0,0 +1,8 @@
+class MigrateTrainingSessionIdToExerciseSets < ActiveRecord::Migration
+  def change
+    ExerciseSet.where(training_session_id: nil).find_each do |set|
+      set.training_session_id = set.exercise_session.training_session.id
+      set.save!
+    end
+  end
+end
db/migrate/20160624002057_drop_exercise_session_id_from_exercise_sets.rb
@@ -0,0 +1,5 @@
+class DropExerciseSessionIdFromExerciseSets < ActiveRecord::Migration
+  def change
+    remove_column :exercise_sets, :exercise_session_id
+  end
+end
db/migrate/20160624034644_remove_exercise_sessions.rb
@@ -0,0 +1,5 @@
+class RemoveExerciseSessions < ActiveRecord::Migration
+  def change
+    drop_table :exercise_sessions
+  end
+end
db/schema.rb
@@ -11,26 +11,20 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160611171913) do
+ActiveRecord::Schema.define(version: 20160624034644) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
   enable_extension "uuid-ossp"
 
-  create_table "exercise_sessions", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
-    t.uuid     "training_session_id", null: false
-    t.uuid     "exercise_workout_id", null: false
-    t.datetime "created_at",          null: false
-    t.datetime "updated_at",          null: false
-  end
-
   create_table "exercise_sets", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
-    t.uuid     "exercise_session_id", null: false
-    t.integer  "target_repetitions"
+    t.integer  "target_repetitions",  null: false
     t.integer  "actual_repetitions"
-    t.float    "target_weight"
+    t.float    "target_weight",       null: false
     t.datetime "created_at",          null: false
     t.datetime "updated_at",          null: false
+    t.uuid     "exercise_id",         null: false
+    t.uuid     "training_session_id"
   end
 
   create_table "exercise_workouts", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
spec/controllers/exercise_sets_controller_spec.rb
@@ -0,0 +1,26 @@
+require "rails_helper"
+
+describe ExerciseSetsController do
+  let(:user) { create(:user) }
+
+  before :each do
+    http_login(user)
+  end
+
+  describe "#update" do
+    include_context "stronglifts_program"
+    let(:training_session) { user.next_training_session_for(workout_a) }
+
+    it "records the exercise" do
+      training_session.update!(occurred_at: DateTime.now, body_weight: 225)
+      exercise_set = training_session.exercise_sets.first
+
+      xhr :patch, :update, id: exercise_set.id, exercise_set: {
+        actual_weight: 315,
+        actual_repetitions: 5,
+      }
+      expect(response).to have_http_status(:ok)
+      expect(exercise_set.reload.actual_repetitions).to eql(5)
+    end
+  end
+end
spec/controllers/training_sessions_controller_spec.rb
@@ -16,74 +16,6 @@ describe TrainingSessionsController do
       get :index
       expect(assigns(:training_sessions)).to match_array([training_session_a, training_session_b])
     end
-
-    it "allows iframes from google for the google drive popup" do
-      get :index
-      allowed_url = "ALLOW-FROM https://drive.google.com"
-      expect(response.headers["X-Frame-Options"]).to eql(allowed_url)
-    end
-  end
-
-  describe "#upload" do
-    include_context "stronglifts_program"
-    let(:backup_file) { fixture_file_upload("backup.android.stronglifts") }
-    let(:translation) { I18n.translate("training_sessions.upload.success") }
-
-    before :each do
-      allow(UploadStrongliftsBackupJob).to receive(:perform_later)
-    end
-
-    it "uploads a new backup" do
-      post :upload, backup: backup_file
-      expect(UploadStrongliftsBackupJob).to have_received(:perform_later)
-    end
-
-    it "redirects to the dashboard" do
-      post :upload, backup: backup_file
-      expect(response).to redirect_to(dashboard_path)
-    end
-
-    it "displays a friendly message" do
-      post :upload, backup: backup_file
-      expect(flash[:notice]).to eql(translation)
-    end
-
-    context "when the file is not a backup file" do
-      let(:unknown_file) { fixture_file_upload("unknown.file") }
-
-      it "displays an error" do
-        post :upload, backup: unknown_file
-        translation = I18n.translate("training_sessions.upload.failure")
-        expect(flash[:alert]).to eql(translation)
-        expect(response).to redirect_to(dashboard_path)
-      end
-    end
-  end
-
-  describe "#drive_upload" do
-    let(:params) { {} }
-    let(:success_message) do
-      I18n.translate("training_sessions.drive_upload.success")
-    end
-
-    before :each do
-      allow(DownloadFromDriveJob).to receive(:perform_later)
-    end
-
-    it "schedules a job to suck down the latest backup from google drive" do
-      post :drive_upload, params
-      expect(DownloadFromDriveJob).to have_received(:perform_later)
-    end
-
-    it "redirects to the dashboard" do
-      post :drive_upload, params
-      expect(response).to redirect_to(dashboard_path)
-    end
-
-    it "displays a success message" do
-      post :drive_upload, params
-      expect(flash[:notice]).to eql(success_message)
-    end
   end
 
   describe "#new" do
@@ -123,25 +55,19 @@ describe TrainingSessionsController do
       post :create, training_session: {
         workout_id: workout_b.id,
         body_weight: body_weight,
-        exercise_sessions_attributes: [
-          {
-            exercise_workout_id: workout_b.exercise_workouts.first.id,
-            #target_repetitions: 4,
-            #target_sets: 3,
-            #target_weight: 275.0,
-          }
-        ]
+        exercise_sets_attributes: [{
+          exercise_id: squat.id,
+          target_repetitions: 5,
+          target_weight: 200,
+        }]
       }
 
       expect(user.reload.training_sessions.count).to eql(1)
       expect(user.last_workout).to eql(workout_b)
       training_session = user.training_sessions.last
       expect(training_session.body_weight).to eql(body_weight.to_f)
-      expect(training_session.exercise_sessions.count).to eql(1)
-      exercise_session = training_session.exercise_sessions.first
-      #expect(exercise_session.target_sets).to eql(3)
-      #expect(exercise_session.target_repetitions).to eql(4)
-      #expect(exercise_session.target_weight).to eql(275.0)
+      expect(training_session.workout).to eql(workout_b)
+      expect(training_session.exercise_sets.count).to eql(1)
       expect(response).to redirect_to(edit_training_session_path(user.training_sessions.last))
     end
   end
@@ -154,20 +80,4 @@ describe TrainingSessionsController do
       expect(assigns(:training_session)).to eql(training_session)
     end
   end
-
-  describe "#update" do
-    include_context "stronglifts_program"
-    let(:training_session) { create(:training_session, user: user, workout: workout_a) }
-
-    it "records the exercise" do
-      xhr :patch, :update, id: training_session.id, training_session: {
-        exercise_id: squat.id,
-        weight: 315,
-        sets: [5, 5, 5],
-      }
-      expect(training_session.exercises).to include(squat)
-      expect(training_session.progress_for(squat).sets.first.target_weight).to eql(315.to_f)
-      expect(training_session.progress_for(squat).to_sets).to eql([5, 5, 5])
-    end
-  end
 end
spec/fixtures/backup.android.stronglifts
Binary file
spec/fixtures/backup.ios.stronglifts
Binary file
spec/javascripts/views/google_sync_button_spec.js.coffee
@@ -1,27 +0,0 @@
-#= require views/google_sync_button
-describe "GoogleSyncButton", ->
-  beforeEach ->
-    @subject = new Stronglifters.GoogleSyncButton()
-
-  it 'displays the correct button text', ->
-    expect(@subject.get('text')).toEqual("Sync with Google")
-
-  describe "synchronize", ->
-    beforeEach ->
-      @drive = { syncFile: null }
-      spyOn(@drive, 'syncFile')
-      spyOn(@subject, 'drive').and.returnValue(@drive)
-
-    it 'synchronizes the drive', ->
-      @subject.synchronize(@drive)
-      expect(@drive.syncFile).toHaveBeenCalled()
-
-    it 'changes the text on the button', ->
-      @subject.synchronize(@drive)
-      expect(@subject.get('text')).toEqual('Syncing...')
-
-    it 'changes the button icon', ->
-      @subject.synchronize(@drive)
-      expect(@subject.get('icon')).toContain('fa-spin')
-      expect(@subject.get('icon')).toContain('fa-spinner')
-
spec/jobs/download_from_drive_job_spec.rb
@@ -1,17 +0,0 @@
-require "rails_helper"
-
-RSpec.describe DownloadFromDriveJob, type: :job do
-  describe "#perform" do
-    let(:user) { build(:user) }
-    let(:params) { {} }
-    let(:backup_file) { double(process_later: true) }
-    let(:drive) { instance_double(GoogleDrive) }
-
-    it "downloads the file for further processing" do
-      allow(user).to receive(:google_drive).and_return(drive)
-      allow(drive).to receive(:download).with(params).and_yield(backup_file)
-      subject.perform(user, params)
-      expect(backup_file).to have_received(:process_later).with(Program.stronglifts)
-    end
-  end
-end
spec/jobs/upload_stronglifts_backup_job_spec.rb
@@ -5,30 +5,6 @@ describe UploadStrongliftsBackupJob, type: :job do
   let(:user) { create(:user) }
 
   describe "#perform" do
-    context "android backup" do
-      let(:backup_file) do
-        Rails.root.join("spec", "fixtures", "backup.android.stronglifts").to_s
-      end
-
-      it "adds each workout to the list of training sessions for the user" do
-        subject.perform(user, backup_file, program)
-
-        expect(user.training_sessions.count).to eql(31)
-      end
-    end
-
-    context "ios backup" do
-      let(:backup_file) do
-        Rails.root.join("spec", "fixtures", "backup.ios.stronglifts").to_s
-      end
-
-      it "adds each workout to the list of training sessions for the user" do
-        subject.perform(user, backup_file, program)
-
-        expect(user.training_sessions.count).to eql(9)
-      end
-    end
-
     context "csv export" do
       let(:backup_file) do
         Rails.root.join("spec", "fixtures", "spreadsheet-stronglifts.csv").to_s
spec/models/android/import_spec.rb
@@ -1,69 +0,0 @@
-require "rails_helper"
-
-describe Android::Import do
-  include_context "stronglifts_program"
-  subject { Android::Import.new(user, program) }
-  let(:user) { create(:user) }
-
-  describe "#import_from" do
-    let(:directory) { Dir.mktmpdir }
-    let(:backup_file) do
-      Rails.root.join("spec", "fixtures", "backup.android.stronglifts")
-    end
-
-    before :each do
-      `unzip #{backup_file} -d #{directory}`
-    end
-
-    after :each do
-      FileUtils.remove_entry(directory)
-    end
-
-    it "imports each training session" do
-      mountain_time = ActiveSupport::TimeZone["America/Denver"]
-      allow(user).to receive(:time_zone).and_return(mountain_time)
-      subject.import_from(directory)
-      training_session = user.training_sessions.order(:occurred_at).first
-
-      expected_date = DateTime.new(2015, 03, 02, 16, 28, 26)
-      expect(training_session.occurred_at.to_i).to eql(expected_date.to_i)
-      expect(training_session.workout).to eql(workout_a)
-      expect(training_session.body_weight).to eql(205.0)
-      expect(training_session.exercise_sessions.count).to eql(3)
-      expect(
-        training_session.exercise_sessions.map { |x| x.exercise.name }
-      ).to match_array(["Squat", "Bench Press", "Barbell Row"])
-    end
-
-    it "imports the completed squat exercise" do
-      subject.import_from(directory)
-
-      training_session = user.training_sessions.order(:occurred_at).first
-      squat_session = training_session.progress_for(squat)
-
-      expect(squat_session.to_sets).to eql([5, 5, 5, 5, 5])
-    end
-
-    it "imports the completed bench exercise" do
-      subject.import_from(directory)
-
-      training_session = user.training_sessions.order(:occurred_at).first
-      bench_session = training_session.progress_for(bench_press)
-      expect(bench_session.to_sets).to eql([5, 5, 5, 5, 5])
-    end
-
-    it "imports the completed barbell row exercise" do
-      subject.import_from(directory)
-
-      training_session = user.training_sessions.order(:occurred_at).first
-      row_session = training_session.progress_for(barbell_row)
-      expect(row_session.to_sets).to eql([5, 5, 5, 5, 5])
-    end
-
-    it "excludes items that have already been imported" do
-      subject.import_from(directory)
-      subject.import_from(directory)
-      expect(user.training_sessions.count).to eql(31)
-    end
-  end
-end
spec/models/csv/import_spec.rb
@@ -48,9 +48,9 @@ describe Csv::Import do
       expect(training_session.occurred_at).to eql(expected_date)
       expect(training_session.workout).to eql(workout_a)
       expect(training_session.body_weight).to eql(205.0)
-      expect(training_session.exercise_sessions.count).to eql(3)
+      expect(training_session.exercise_sets.count).to eql(15)
       expect(
-        training_session.exercise_sessions.map { |x| x.exercise.name }
+        training_session.exercise_sets.map { |x| x.exercise.name }.uniq
       ).to match_array(["Squat", "Bench Press", "Barbell Row"])
     end
 
@@ -103,9 +103,9 @@ describe Csv::Import do
       subject.import(row)
 
       training_session = user.training_sessions.first
-      session = training_session.progress_for(dips)
-      expect(session).to_not be_nil
-      expect(session.to_sets).to eql([5, 5, 5])
+      progress = training_session.progress_for(dips)
+      expect(progress).to_not be_nil
+      expect(progress.to_sets).to eql([5, 5, 5])
     end
 
     it "imports chinups" do
@@ -113,18 +113,30 @@ describe Csv::Import do
       subject.import(row)
 
       training_session = user.training_sessions.first
-      session = training_session.progress_for(chinups)
-      expect(session).to_not be_nil
-      expect(session.to_sets).to eql([5, 3, 2])
-      expect(session.sets.at(0).target_weight).to eql(0.0)
-      expect(session.sets.at(0).target_repetitions).to eql(5)
-      expect(session.sets.at(0).actual_repetitions).to eql(5)
-      expect(session.sets.at(1).target_weight).to eql(0.0)
-      expect(session.sets.at(1).target_repetitions).to eql(5)
-      expect(session.sets.at(1).actual_repetitions).to eql(3)
-      expect(session.sets.at(2).target_weight).to eql(0.0)
-      expect(session.sets.at(2).target_repetitions).to eql(5)
-      expect(session.sets.at(2).actual_repetitions).to eql(2)
+      progress = training_session.progress_for(chinups)
+      expect(progress).to_not be_nil
+      expect(progress.to_sets).to eql([5, 3, 2])
+      expect(progress.sets.at(0).target_weight).to eql(0.0)
+      expect(progress.sets.at(0).target_repetitions).to eql(5)
+      expect(progress.sets.at(0).actual_repetitions).to eql(5)
+      expect(progress.sets.at(1).target_weight).to eql(0.0)
+      expect(progress.sets.at(1).target_repetitions).to eql(5)
+      expect(progress.sets.at(1).actual_repetitions).to eql(3)
+      expect(progress.sets.at(2).target_weight).to eql(0.0)
+      expect(progress.sets.at(2).target_repetitions).to eql(5)
+      expect(progress.sets.at(2).actual_repetitions).to eql(2)
+    end
+
+    it 'imports the correct number of sets' do
+      row = ["06/05/16", "", "B", "101.93", "225", "Squat", "125", "275", "5", "5", "5", "", "", "Overhead press", "57.5", "125", "5", "5", "5", "5", "5", "Deadlift", "127.5", "285", "5", "", "", "" , "", "", "", "", "", ""]
+
+      subject.import(row)
+      training_session = user.training_sessions.first
+      expect(training_session.progress_for(squat).sets.count).to eql(3)
+      expect(training_session.progress_for(squat).to_sets).to eql([5, 5, 5])
+
+      expect(training_session.progress_for(deadlift).sets.count).to eql(1)
+      expect(training_session.progress_for(deadlift).to_sets).to eql([5])
     end
   end
 end
spec/models/ios/import_spec.rb
@@ -1,95 +0,0 @@
-require "rails_helper"
-
-describe Ios::Import do
-  include_context "stronglifts_program"
-  subject { Ios::Import.new(user, program) }
-  let(:user) { create(:user) }
-
-  describe "#can_parse?" do
-    let(:directory) { Dir.mktmpdir }
-
-    context "when the directory contains the ios backup db" do
-      before :each do
-        FileUtils.touch("#{directory}/SLDB.sqlite")
-      end
-
-      it "returns true" do
-        expect(subject.can_parse?(directory)).to be_truthy
-      end
-    end
-
-    context "when the directory does not have the ios backup db" do
-      it "returns false" do
-        expect(subject.can_parse?(directory)).to be_falsey
-      end
-    end
-  end
-
-  describe "#import_from" do
-    let(:directory) { Dir.mktmpdir }
-    let(:backup_file) do 
-      Rails.root.join("spec", "fixtures", "backup.ios.stronglifts")
-    end
-
-    before :each do
-      `unzip #{backup_file} -d #{directory}`
-    end
-
-    after :each do
-      FileUtils.remove_entry(directory)
-    end
-
-    it "imports each workout" do
-      subject.import_from(directory)
-      expect(user.training_sessions.count).to eql(9)
-    end
-
-    it "imports each training session" do
-      subject.import_from(directory)
-      training_sessions = user.training_sessions.order(:occurred_at)
-      training_session = training_sessions.first
-      first_exercises = training_session.exercise_sessions
-      expect(first_exercises.count).to eql(3)
-      expect(first_exercises.first.to_sets).to eql([5, 5, 5, 5, 5])
-
-      expected_date = DateTime.new(2015, 05, 02, 23, 27, 05)
-      expect(training_session.occurred_at.to_i).to eql(expected_date.to_i)
-      expect(training_session.workout).to eql(workout_a)
-      expect(training_session.body_weight).to eql(160.0)
-      expect(training_session.exercise_sessions.count).to eql(3)
-      expect(
-        training_session.exercise_sessions.map { |x| x.exercise.name }
-      ).to match_array(["Squat", "Bench Press", "Barbell Row"])
-    end
-
-    it "imports the completed squat exercise" do
-      subject.import_from(directory)
-
-      training_session = user.training_sessions.order(:occurred_at).first
-      squat_session = training_session.progress_for(squat)
-      expect(squat_session.to_sets).to eql([5, 5, 5, 5, 5])
-    end
-
-    it "imports the completed bench exercise" do
-      subject.import_from(directory)
-
-      training_session = user.training_sessions.order(:occurred_at).first
-      bench_session = training_session.progress_for(bench_press)
-      expect(bench_session.to_sets).to eql([5, 5, 5, 5, 5])
-    end
-
-    it "imports the completed barbell row exercise" do
-      subject.import_from(directory)
-
-      training_session = user.training_sessions.order(:occurred_at).first
-      row_session = training_session.progress_for(barbell_row)
-      expect(row_session.to_sets).to eql([5, 5, 5, 5, 5])
-    end
-
-    it "excludes items that have already been imported" do
-      subject.import_from(directory)
-      subject.import_from(directory)
-      expect(user.training_sessions.count).to eql(9)
-    end
-  end
-end
spec/models/backup_file_spec.rb
@@ -8,11 +8,6 @@ describe BackupFile do
   end
 
   describe "#valid?" do
-    it "returns true" do
-      subject = BackupFile.new(user, fixture_file("backup.android.stronglifts"))
-      expect(subject).to be_valid
-    end
-
     it "returns true for csv exports" do
       csv_export = fixture_file("spreadsheet-stronglifts.csv")
       subject = BackupFile.new(user, csv_export)
@@ -30,7 +25,7 @@ describe BackupFile do
 
     it "creates a job to process later" do
       allow(UploadStrongliftsBackupJob).to receive(:perform_later)
-      subject = BackupFile.new(user, fixture_file("backup.ios.stronglifts"))
+      subject = BackupFile.new(user, fixture_file("spreadsheet-stronglifts.csv"))
       subject.process_later(program)
       expect(UploadStrongliftsBackupJob).to have_received(:perform_later)
     end
spec/models/google_drive_spec.rb
@@ -1,32 +0,0 @@
-require "rails_helper"
-
-describe GoogleDrive do
-  subject { GoogleDrive.new(user) }
-  let(:user) { build(:user) }
-  let(:referrer_domain) { "https://www.stronglifters.com" }
-
-  describe "#download" do
-    let(:params) do
-      {
-        accessToken: access_token,
-        data: {
-          title: filename,
-          downloadUrl: "https://www.google.com/"
-        },
-      }
-    end
-
-    let(:filename) { "#{FFaker::Internet.user_name}.html" }
-    let(:access_token) { FFaker::Internet.user_name }
-
-    it "downloads the specified google drive file", skip: true do
-      result = nil
-      subject.download(params) do |backup_file|
-        result = backup_file
-      end
-      expect(result).to_not be_nil
-      expect(result.user).to eql(user)
-      expect(result.backup_file.path).to end_with(filename)
-    end
-  end
-end
spec/models/training_history_spec.rb
@@ -14,6 +14,7 @@ describe TrainingHistory do
       session = user.begin_workout(workout_a, date, body_weight)
       5.times do
         session.train(squat, target_weight, repetitions: 5)
+        session.train(bench_press, target_weight + 10, repetitions: 5)
       end
     end
 
@@ -21,6 +22,7 @@ describe TrainingHistory do
       result = subject.to_line_chart
       expect(result).to_not be_nil
       expect(result.keys.first.to_i).to eql(date.to_i)
+      expect(result.keys.count).to eql(1)
       expect(result.values.first).to eql(target_weight.to_f)
     end
   end
spec/models/training_session_spec.rb
@@ -17,11 +17,11 @@ describe TrainingSession, type: :model do
 
       expect(result).to be_persisted
       expect(result.exercise).to eql(squat)
-      expect(result.to_sets).to eql([5])
-      expect(result.sets.at(0).exercise).to eql(squat)
-      expect(result.sets.at(0).target_weight).to eql(target_weight.to_f)
-      expect(result.sets.at(0).target_repetitions).to eql(5)
-      expect(result.sets.at(0).actual_repetitions).to eql(5)
+      expect(subject.progress_for(squat).to_sets).to eql([5])
+      expect(subject.sets.at(0).exercise).to eql(squat)
+      expect(subject.sets.at(0).target_weight).to eql(target_weight.to_f)
+      expect(subject.sets.at(0).target_repetitions).to eql(5)
+      expect(subject.sets.at(0).actual_repetitions).to eql(5)
     end
 
     it 'records the next set' do
@@ -30,15 +30,15 @@ describe TrainingSession, type: :model do
 
       expect(result).to be_persisted
       expect(result.exercise).to eql(squat)
-      expect(result.to_sets).to eql([5, 3])
-      expect(result.sets.at(0).exercise).to eql(squat)
-      expect(result.sets.at(0).target_weight).to eql(target_weight.to_f)
-      expect(result.sets.at(0).target_repetitions).to eql(5)
-      expect(result.sets.at(0).actual_repetitions).to eql(5)
-      expect(result.sets.at(1).exercise).to eql(squat)
-      expect(result.sets.at(1).target_weight).to eql(target_weight.to_f)
-      expect(result.sets.at(1).target_repetitions).to eql(5)
-      expect(result.sets.at(1).actual_repetitions).to eql(3)
+      expect(subject.progress_for(squat).to_sets).to eql([5, 3])
+      expect(subject.sets.at(0).exercise).to eql(squat)
+      expect(subject.sets.at(0).target_weight).to eql(target_weight.to_f)
+      expect(subject.sets.at(0).target_repetitions).to eql(5)
+      expect(subject.sets.at(0).actual_repetitions).to eql(5)
+      expect(subject.sets.at(1).exercise).to eql(squat)
+      expect(subject.sets.at(1).target_weight).to eql(target_weight.to_f)
+      expect(subject.sets.at(1).target_repetitions).to eql(5)
+      expect(subject.sets.at(1).actual_repetitions).to eql(3)
     end
 
     it "updates a completed exercise" do
@@ -51,37 +51,40 @@ describe TrainingSession, type: :model do
 
       expect(result).to be_persisted
       expect(result.exercise).to eql(squat)
-      expect(result.to_sets).to eql([5, 3, 5])
-      expect(result.sets.at(0).exercise).to eql(squat)
-      expect(result.sets.at(0).target_weight).to eql(target_weight.to_f)
-      expect(result.sets.at(0).target_repetitions).to eql(5)
-      expect(result.sets.at(0).actual_repetitions).to eql(5)
-      expect(result.sets.at(1).exercise).to eql(squat)
-      expect(result.sets.at(1).target_weight).to eql(new_weight.to_f)
-      expect(result.sets.at(1).target_repetitions).to eql(5)
-      expect(result.sets.at(1).actual_repetitions).to eql(3)
-      expect(result.sets.at(2).exercise).to eql(squat)
-      expect(result.sets.at(2).target_weight).to eql(target_weight.to_f)
-      expect(result.sets.at(2).target_repetitions).to eql(5)
-      expect(result.sets.at(2).actual_repetitions).to eql(5)
+      progress = subject.progress_for(squat)
+      expect(progress.to_sets).to eql([5, 3, 5])
+      expect(progress.sets.at(0).exercise).to eql(squat)
+      expect(progress.sets.at(0).target_weight).to eql(target_weight.to_f)
+      expect(progress.sets.at(0).target_repetitions).to eql(5)
+      expect(progress.sets.at(0).actual_repetitions).to eql(5)
+      expect(progress.sets.at(1).exercise).to eql(squat)
+      expect(progress.sets.at(1).target_weight).to eql(new_weight.to_f)
+      expect(progress.sets.at(1).target_repetitions).to eql(5)
+      expect(progress.sets.at(1).actual_repetitions).to eql(3)
+      expect(progress.sets.at(2).exercise).to eql(squat)
+      expect(progress.sets.at(2).target_weight).to eql(target_weight.to_f)
+      expect(progress.sets.at(2).target_repetitions).to eql(5)
+      expect(progress.sets.at(2).actual_repetitions).to eql(5)
     end
 
     it "cannot save a duplicate exercise" do
-      result = subject.train(squat, target_weight, repetitions: 5)
-      subject.train(squat, target_weight, repetitions: 5)
+      subject.train(squat, target_weight, repetitions: 5, set: 0)
+      subject.train(squat, target_weight, repetitions: 5, set: 0)
 
-      expect(subject.exercise_sessions.count).to eql(1)
-      expect(subject.exercise_sessions).to match_array([result])
+      expect(subject.exercise_sets.count).to eql(1)
     end
   end
 
   describe "#progress_for" do
     let(:exercise) { create(:exercise) }
+    let(:other_exercise) { create(:exercise) }
     let(:workout) { subject.workout }
 
     before :each do
       workout.add_exercise(exercise)
+      workout.add_exercise(other_exercise)
       subject.train(exercise, 100, repetitions: 5)
+      subject.train(other_exercise, 100, repetitions: 5)
       subject.train(exercise, 100, repetitions: 5)
     end
 
spec/models/user_spec.rb
@@ -157,14 +157,6 @@ describe User do
     end
   end
 
-  describe "#google_drive" do
-    it "returns the users google drive" do
-      result = subject.google_drive
-      expect(result).to be_instance_of(GoogleDrive)
-      expect(result.user).to eql(subject)
-    end
-  end
-
   describe "when destroying a training session" do
     include_context "stronglifts_program"
     subject { create(:user) }
@@ -180,7 +172,7 @@ describe User do
       subject.training_sessions.destroy_all
 
       expect(TrainingSession.all).to be_empty
-      expect(ExerciseSession.all).to be_empty
+      expect(ExerciseSet.all).to be_empty
     end
   end
 
.env.example
@@ -1,6 +1,4 @@
 DEFAULT_FROM_ADDRESS=''
-GOOGLE_CLIENT_ID=''
-GOOGLE_REFERRER_DOMAIN=http://stronglifters.dev
 SMTP_DOMAIN='stronglifters.com'
 SMTP_HOST=''
 SMTP_PASSWORD=''
Gemfile
@@ -53,14 +53,12 @@ source "https://rubygems.org" do
   gem "sass-rails", "~> 5.0"
   gem "scale"
   gem "sdoc", "~> 0.4", group: :doc
-  gem "sequel"
   gem "sidekiq"
   gem "spring", group: [:development, :test]
   gem "spring-commands-cucumber", group: :test
   gem "spring-commands-rspec", group: :test
   gem "spring-commands-teaspoon", group: :test
   gem "sprockets-rails", "2.3.3"
-  gem "sqlite3"
   gem "stackprof", group: :development
   gem "teaspoon-jasmine", group: :test
   gem "therubyracer", platforms: :ruby
Gemfile.lock
@@ -352,7 +352,6 @@ GEM
     sdoc (0.4.1)
       json (~> 1.7, >= 1.7.7)
       rdoc (~> 4.0)
-    sequel (4.33.0)
     sexp_processor (4.7.0)
     sidekiq (4.1.1)
       concurrent-ruby (~> 1.0)
@@ -381,7 +380,6 @@ GEM
       actionpack (>= 3.0)
       activesupport (>= 3.0)
       sprockets (>= 2.8, < 4.0)
-    sqlite3 (1.3.11)
     sshkit (1.10.0)
       net-scp (>= 1.1.2)
       net-ssh (>= 2.8.0)
@@ -497,14 +495,12 @@ DEPENDENCIES
   sass-rails (~> 5.0)!
   scale!
   sdoc (~> 0.4)!
-  sequel!
   sidekiq!
   spring!
   spring-commands-cucumber!
   spring-commands-rspec!
   spring-commands-teaspoon!
   sprockets-rails (= 2.3.3)!
-  sqlite3!
   stackprof!
   teaspoon-jasmine!
   therubyracer!