Commit 29a69b0
Changed files (11)
app
controllers
models
db
spec
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 }