Commit 29a69b0

mo khan <mo@mokhan.ca>
2016-04-30 16:26:50
add user_session model.
1 parent d352a86
app/controllers/sessions_controller.rb
@@ -1,6 +1,6 @@
 class SessionsController < PublicController
   def create
-    if user_session = UserSession.authenticate(params[:user][:username], params[:user][:password])
+    if user_session = UserSession.login(params[:user][:username], params[:user][:password])
       session[:user_id] = user_session.id
       redirect_to dashboard_path
     else
app/models/gym.rb
@@ -1,5 +1,6 @@
 class Gym < ActiveRecord::Base
   validates_presence_of :name
+  has_one :location, as: :locatable
   before_save :assign_location
 
   scope :closest_to, ->(user) { all }
@@ -7,6 +8,6 @@ class Gym < ActiveRecord::Base
   private
 
   def assign_location
-    self.latitude, self.longitude = Location.from(address, city, region, country)
+    #self.latitude, self.longitude = Location.from(address, city, region, country)
   end
 end
app/models/location.rb
@@ -1,4 +1,7 @@
-class Location
+class Location < ActiveRecord::Base
+  def self.build_from_ip(ip)
+  end
+
   def self.from(address, city, region, country)
     results = Geocoder.search("#{address}, #{city}, #{region}, #{country}")
     results.any? ? results.first.coordinates : [nil, nil]
app/models/user_session.rb
@@ -1,12 +1,36 @@
-class UserSession
-  def self.authenticate(username, password)
-    user = User.find_by(
-      "email = :email OR username = :username",
-      username: username.downcase,
-      email: username.downcase
-    )
-    if user.present?
-      user.authenticate(password)
+class UserSession < ActiveRecord::Base
+  has_one :location, as: :locatable
+  belongs_to :user
+  scope :active, -> do
+    where('accessed_at > ?', 2.weeks.ago).where(revoked_at: nil)
+  end
+
+  def revoke!
+    update_attribute(:revoked_at, Time.current)
+  end
+
+  def access(request)
+    self.accessed_at = Time.current
+    self.ip = request.ip
+    self.user_agent = request.user_agent
+    self.location = Location.build_from_ip(request.ip)
+    id
+  end
+
+  class << self
+    def authenticate(id)
+      active.find_by(id: id)
+    end
+
+    def login(username, password)
+      user = User.find_by(
+        "email = :email OR username = :username",
+        username: username.downcase,
+        email: username.downcase
+      )
+      if user.present?
+        user.authenticate(password)
+      end
     end
   end
 end
db/migrate/20160430041813_create_gyms.rb
@@ -2,13 +2,6 @@ class CreateGyms < ActiveRecord::Migration
   def change
     create_table :gyms, id: :uuid do |t|
       t.string :name, null: false
-      t.string :address
-      t.string :city
-      t.string :region
-      t.string :country
-      t.string :postal_code
-      t.decimal :latitude, precision: 10, scale: 6
-      t.decimal :longitude, precision: 10, scale: 6
       t.timestamps null: false
     end
   end
db/migrate/20160430155050_create_locations.rb
@@ -0,0 +1,19 @@
+class CreateLocations < ActiveRecord::Migration
+  def change
+    create_table :locations, id: :uuid do |t|
+      t.uuid :locatable_id
+      t.string :locatable_type
+      t.decimal :latitude, precision: 10, scale: 6
+      t.decimal :longitude, precision: 10, scale: 6
+      t.string :address
+      t.string :city
+      t.string :region
+      t.string :country
+      t.string :postal_code
+
+      t.timestamps null: false
+    end
+
+    add_index :locations, [:locatable_id, :locatable_type]
+  end
+end
db/migrate/20160430155822_create_user_sessions.rb
@@ -0,0 +1,13 @@
+class CreateUserSessions < ActiveRecord::Migration
+  def change
+    create_table :user_sessions, id: :uuid do |t|
+      t.belongs_to :user, foreign_key: true, type: :uuid, index: true
+      t.string :ip
+      t.text :user_agent
+      t.datetime :accessed_at
+      t.datetime :revoked_at
+
+      t.timestamps null: false
+    end
+  end
+end
db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160430041813) do
+ActiveRecord::Schema.define(version: 20160430155822) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -42,18 +42,27 @@ ActiveRecord::Schema.define(version: 20160430041813) do
   end
 
   create_table "gyms", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
-    t.string   "name",                                 null: false
+    t.string   "name",       null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+  end
+
+  create_table "locations", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
+    t.uuid     "locatable_id"
+    t.string   "locatable_type"
+    t.decimal  "latitude",       precision: 10, scale: 6
+    t.decimal  "longitude",      precision: 10, scale: 6
     t.string   "address"
     t.string   "city"
     t.string   "region"
     t.string   "country"
     t.string   "postal_code"
-    t.decimal  "latitude",    precision: 10, scale: 6
-    t.decimal  "longitude",   precision: 10, scale: 6
-    t.datetime "created_at",                           null: false
-    t.datetime "updated_at",                           null: false
+    t.datetime "created_at",                              null: false
+    t.datetime "updated_at",                              null: false
   end
 
+  add_index "locations", ["locatable_id", "locatable_type"], name: "index_locations_on_locatable_id_and_locatable_type", using: :btree
+
   create_table "profiles", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
     t.uuid     "user_id",          null: false
     t.integer  "gender"
@@ -84,6 +93,18 @@ ActiveRecord::Schema.define(version: 20160430041813) do
 
   add_index "training_sessions", ["user_id"], name: "index_training_sessions_on_user_id", using: :btree
 
+  create_table "user_sessions", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
+    t.uuid     "user_id"
+    t.string   "ip"
+    t.text     "user_agent"
+    t.datetime "accessed_at"
+    t.datetime "revoked_at"
+    t.datetime "created_at",  null: false
+    t.datetime "updated_at",  null: false
+  end
+
+  add_index "user_sessions", ["user_id"], name: "index_user_sessions_on_user_id", using: :btree
+
   create_table "users", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
     t.string   "username",        null: false
     t.string   "email",           null: false
@@ -99,4 +120,5 @@ ActiveRecord::Schema.define(version: 20160430041813) do
     t.datetime "updated_at", null: false
   end
 
+  add_foreign_key "user_sessions", "users"
 end
spec/models/user_session.rb
@@ -1,33 +0,0 @@
-require 'rails_helper'
-
-describe UserSession do
-  describe "#authenticate" do
-    context "when credentials are correct" do
-      it "returns true" do
-        user = create(:user, password: "password", password_confirmation: "password")
-        expect(UserSession.authenticate(user.email.upcase, "password")).to eql(user)
-      end
-
-      it "is case in-sensitive for username" do
-        user = create(:user,
-                      username: "upcase",
-                      password: "password",
-                      password_confirmation: "password"
-                     )
-        expect(UserSession.authenticate("UPcase", "password")).to eql(user)
-      end
-    end
-
-    context "when the email is not registered" do
-      it "returns nil" do
-        expect(UserSession.authenticate("sofake@noteven.com", "password")).to be_nil
-      end
-    end
-
-    context "when the username is not registered" do
-      it "returns nil" do
-        expect(UserSession.authenticate("sofake", "password")).to be_nil
-      end
-    end
-  end
-end
spec/models/user_session_spec.rb
@@ -0,0 +1,81 @@
+require "rails_helper"
+
+describe UserSession do
+  subject { build(:user_session) }
+
+  describe "#revoke!" do
+    it "records the time the session was revoked" do
+      subject.revoke!
+      expect(subject.revoked_at).to_not be_nil
+    end
+  end
+
+  describe "#access" do
+    let(:request) { double(ip: '192.168.1.1', user_agent: 'blah') }
+    let(:location) { build(:location) }
+    let(:because) {  subject.access(request) }
+
+    before :each do
+      allow(Location).to receive(:build_from_ip).with('192.168.1.1').and_return(location)
+      because
+    end
+
+    it { expect(subject.accessed_at).to_not be_nil }
+    it { expect(subject.ip).to eql(request.ip) }
+    it { expect(subject.user_agent).to eql(request.user_agent) }
+    it { expect(subject.location).to_not be_nil }
+    it { expect(because).to eql(subject.id) }
+  end
+
+  describe ".active" do
+    let!(:active_session) { create(:user_session, accessed_at: Time.now) }
+    let!(:expired_session) { create(:user_session, accessed_at: 15.days.ago) }
+    let!(:revoked_session) { create(:user_session, accessed_at: 1.day.ago, revoked_at: Time.now) }
+
+    it "returns active sessions" do
+      expect(UserSession.active).to include(active_session)
+    end
+
+    it "excludes expired sessions" do
+      expect(UserSession.active).to_not include(expired_session)
+    end
+
+    it "excludes revoked sessions" do
+      expect(UserSession.active).to_not include(revoked_session)
+    end
+  end
+
+  describe ".authenticate" do
+    let!(:active_session) { create(:user_session, accessed_at: Time.now) }
+
+    it "returns the session if the id is active" do
+      expect(UserSession.authenticate(active_session.id)).to eql(active_session)
+    end
+
+    it "returns nil if the id is not active" do
+      expect(UserSession.authenticate('blah')).to be_nil
+    end
+  end
+
+  describe "#login" do
+    context "when credentials are correct" do
+      it "returns true" do
+        user = create(:user, password: "password", password_confirmation: "password")
+        expect(UserSession.login(user.email.upcase, "password")).to eql(user)
+      end
+
+      it "is case in-sensitive for username" do
+        user = create(:user, username: "upcase", password: "password", password_confirmation: "password")
+        expect(UserSession.login("UPcase", "password")).to eql(user)
+      end
+    end
+
+    context "when the email is not registered" do
+      it { expect(UserSession.login("sofake@noteven.com", "password")).to be_nil }
+    end
+
+    context "when the username is not registered" do
+      it { expect(UserSession.login("sofake", "password")).to be_nil }
+    end
+  end
+end
spec/factories.rb
@@ -55,6 +55,19 @@ FactoryGirl.define do
   end
   factory :gym do
     name { FFaker::Internet.user_name }
+  end
+
+  factory :user_session, class: UserSession do
+    association :user
+    ip FFaker::Internet.ip_v4_address
+    factory :active_session do
+      accessed_at Time.current
+    end
+  end
+
+  factory :location do
+    latitude { rand(90.0) }
+    longitude { rand(180.0) }
     address { FFaker::Address.street_address }
     city { FFaker::AddressCA.city }
     region { FFaker::AddressCA.province }