diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000..276cbf9
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+2.3.0
diff --git a/Gemfile b/Gemfile
index 86c8b99..d0eed19 100644
--- a/Gemfile
+++ b/Gemfile
@@ -21,7 +21,7 @@ gem 'cocoon', '~> 1.2.6'
gem 'unitwise', '~> 2.0.0'
# Use ActiveModel has_secure_password
-# gem 'bcrypt', '~> 3.1.7'
+gem 'bcrypt', '~> 3.1.7'
# Use Unicorn as the app server
# gem 'unicorn'
diff --git a/Gemfile.lock b/Gemfile.lock
index be65b38..7323d14 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -37,9 +37,10 @@ GEM
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
arel (6.0.3)
- autoprefixer-rails (6.2.3)
+ autoprefixer-rails (6.3.1)
execjs
json
+ bcrypt (3.1.10)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
blankslate (3.1.3)
@@ -73,7 +74,7 @@ GEM
jbuilder (2.4.0)
activesupport (>= 3.0.0, < 5.1)
multi_json (~> 1.2)
- jquery-rails (4.0.5)
+ jquery-rails (4.1.0)
rails-dom-testing (~> 1.0)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
@@ -122,7 +123,7 @@ GEM
activesupport (= 4.2.5)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rake (10.4.2)
+ rake (10.5.0)
ref (2.0.0)
rspec-core (3.4.1)
rspec-support (~> 3.4.0)
@@ -185,6 +186,7 @@ PLATFORMS
ruby
DEPENDENCIES
+ bcrypt (~> 3.1.7)
bootstrap-sass (~> 3.3.6)
byebug
cocoon (~> 1.2.6)
@@ -202,3 +204,6 @@ DEPENDENCIES
uglifier (>= 1.3.0)
unitwise (~> 2.0.0)
web-console (~> 2.0)
+
+BUNDLED WITH
+ 1.11.2
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d83690e..c3b0f23 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -2,4 +2,31 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
+
+ def ensure_valid_user
+ if current_user.nil?
+ flash[:warning] = "You must login"
+ redirect_to login_path
+ end
+ end
+
+ def ensure_admin_user
+ unless current_user && current_user.is_admin?
+ flash[:warning] = "You must login as an admin"
+ redirect_to login_path
+ end
+ end
+
+ def current_user
+ @current_user ||= User.find(session[:user_id]) if session[:user_id]
+ end
+ helper_method :current_user
+
+ def set_current_user(user)
+ if user
+ session[:user_id] = user.id
+ else
+ session[:user_id] = nil
+ end
+ end
end
diff --git a/app/controllers/ingredients_controller.rb b/app/controllers/ingredients_controller.rb
index 7d50dab..2afab75 100644
--- a/app/controllers/ingredients_controller.rb
+++ b/app/controllers/ingredients_controller.rb
@@ -1,6 +1,9 @@
class IngredientsController < ApplicationController
+
before_action :set_ingredient, only: [:edit, :update, :destroy]
+ before_filter :ensure_valid_user, only: [:new, :edit, :create, :update, :destroy]
+
# GET /ingredients
# GET /ingredients.json
def index
diff --git a/app/controllers/recipes_controller.rb b/app/controllers/recipes_controller.rb
index 0f19340..88c1fd8 100644
--- a/app/controllers/recipes_controller.rb
+++ b/app/controllers/recipes_controller.rb
@@ -1,8 +1,10 @@
class RecipesController < ApplicationController
+
before_action :set_recipe, only: [:show, :edit, :update, :destroy, :scale]
+ before_filter :ensure_valid_user, only: [:new, :edit, :create, :update, :destroy]
+
# GET /recipes
- # GET /recipes.json
def index
@recipes = Recipe.active
end
@@ -29,48 +31,34 @@ class RecipesController < ApplicationController
end
# POST /recipes
- # POST /recipes.json
def create
@recipe = Recipe.new(recipe_params)
+ @recipe.user = current_user
- respond_to do |format|
- if @recipe.save
- format.html { redirect_to @recipe, notice: 'Recipe was successfully created.' }
- format.json { render :show, status: :created, location: @recipe }
- else
- format.html { render :new }
- format.json { render json: @recipe.errors, status: :unprocessable_entity }
- end
+ if @recipe.save
+ redirect_to @recipe, notice: 'Recipe was successfully created.'
+ else
+ render :new
end
end
# PATCH/PUT /recipes/1
- # PATCH/PUT /recipes/1.json
def update
- respond_to do |format|
- if @recipe.update(recipe_params)
- format.html { redirect_to @recipe, notice: 'Recipe was successfully updated.' }
- format.json { render :show, status: :ok, location: @recipe }
- else
- format.html { render :edit }
- format.json { render json: @recipe.errors, status: :unprocessable_entity }
- end
+ if @recipe.update(recipe_params)
+ redirect_to @recipe, notice: 'Recipe was successfully updated.'
+ else
+ render :edit
end
end
# DELETE /recipes/1
- # DELETE /recipes/1.json
def destroy
@recipe.deleted = true
- respond_to do |format|
- if @recipe.save
- format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' }
- format.json { head :no_content }
- else
- format.html { redirect_to recipes_url, error: 'Recipe could not be destroyed.' }
- format.json { head :no_content }
- end
+ if @recipe.save
+ redirect_to recipes_url, notice: 'Recipe was successfully destroyed.'
+ else
+ redirect_to recipes_url, error: 'Recipe could not be destroyed.'
end
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
new file mode 100644
index 0000000..6b3e614
--- /dev/null
+++ b/app/controllers/users_controller.rb
@@ -0,0 +1,60 @@
+class UsersController < ApplicationController
+
+ before_filter :ensure_valid_user, except: [:login, :verify_login, :new, :create]
+
+ def login
+
+ end
+
+ def logout
+ set_current_user(nil)
+ session.destroy
+ flash[:notice] = "Logged out"
+ redirect_to root_path
+ end
+
+ def verify_login
+ if user = User.authenticate(params[:email], params[:password])
+ set_current_user(user)
+ flash[:notice] = "Welcome, #{user.full_name}"
+ redirect_to root_path
+ else
+ flash[:error] = "Invalid credentials"
+ render :login
+ end
+ end
+
+ def new
+ @user = User.new
+ end
+
+ def create
+ @user = User.new(user_params)
+
+ if @user.save
+ set_current_user(@user)
+ redirect_to root_path, notice: 'User was successfully created.'
+ else
+ render action: :new
+ end
+ end
+
+ def edit
+ @user = current_user
+ end
+
+ def update
+ @user = current_user
+ if @user.update(user_params)
+ redirect_to root_path, notice: 'User account updated'
+ else
+ render action: 'edit'
+ end
+ end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:username, :email, :full_name, :password, :password_confirmation)
+ end
+end
\ No newline at end of file
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e030367..24837eb 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -11,7 +11,27 @@ module ApplicationHelper
]
end
- def nav_item(name, url, controller)
+ def profile_nav_items
+ if current_user
+ [content_tag('li', class: 'dropdown') do
+ li_cnt = ''.html_safe
+
+ li_cnt << link_to('#', class: 'dropdown-toggle', data: {toggle: 'dropdown'}, role: 'button') do
+ ''.html_safe << "#{current_user.display_name}" << content_tag('span', '', class: 'caret')
+ end
+
+ li_cnt << content_tag('ul', class: 'dropdown-menu') do
+ ''.html_safe << nav_item('Profile', edit_user_path) << nav_item('Logout', logout_path)
+ end
+
+ li_cnt
+ end]
+ else
+ [nav_item('Login', login_path)]
+ end
+ end
+
+ def nav_item(name, url, controller = nil)
content_tag('li', link_to(name, url), class: active_for_controller(controller))
end
diff --git a/app/models/recipe.rb b/app/models/recipe.rb
index 0c6c5ac..8c27cd4 100644
--- a/app/models/recipe.rb
+++ b/app/models/recipe.rb
@@ -2,6 +2,7 @@ class Recipe < ActiveRecord::Base
has_many :recipe_ingredients, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy
has_many :recipe_steps, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy
+ belongs_to :user
scope :active, -> { where('deleted <> ? OR deleted IS NULL', true) }
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 0000000..2df7250
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,15 @@
+class User < ActiveRecord::Base
+
+ has_secure_password
+
+ validates :username, presence: true, uniqueness: { case_sensitive: false }
+
+ def self.authenticate(email, password)
+ find_by_email(email).try(:authenticate, password)
+ end
+
+ def display_name
+ self.full_name.present? ? self.full_name : self.username
+ end
+
+end
diff --git a/app/views/ingredients/_form.html.erb b/app/views/ingredients/_form.html.erb
index 0c3ef34..9235d50 100644
--- a/app/views/ingredients/_form.html.erb
+++ b/app/views/ingredients/_form.html.erb
@@ -5,17 +5,17 @@
<%= render partial: 'shared/error_list', locals: {model: @ingredient} %>
- <%= f.label :name %>
+ <%= f.label :name, class: 'control-label' %>
<%= f.text_field :name, class: 'form-control', autofocus: true %>
- <%= f.label :density %>
+ <%= f.label :density, class: 'control-label' %>
<%= f.text_field :density, class: 'form-control' %>
- <%= f.label :notes %>
+ <%= f.label :notes, class: 'control-label' %>
<%= f.text_area :notes, class: 'form-control' %>
diff --git a/app/views/ingredients/index.html.erb b/app/views/ingredients/index.html.erb
index ece143f..dad4916 100644
--- a/app/views/ingredients/index.html.erb
+++ b/app/views/ingredients/index.html.erb
@@ -4,30 +4,35 @@
Ingredients
-
-
-
- Name |
- Density |
- |
-
-
+ <% if @ingredients.empty? %>
+ No Ingredients
+ <% else %>
-
- <% @ingredients.each do |ingredient| %>
+
+
- <%= link_to ingredient.name, edit_ingredient_path(ingredient) %> |
- <%= ingredient.density %> |
- <%= link_to 'Destroy', ingredient, method: :delete, data: { confirm: 'Are you sure?' } %> |
+ Name |
+ Density |
+ |
- <% end %>
-
-
+
-
+
+ <% @ingredients.each do |ingredient| %>
+
+ <%= link_to ingredient.name, edit_ingredient_path(ingredient) %> |
+ <%= ingredient.density %> |
+ <%= link_to 'Destroy', ingredient, method: :delete, data: { confirm: 'Are you sure?' } %> |
+
+ <% end %>
+
+
+ <% end %>
- <%= link_to 'New Ingredient', new_ingredient_path, class: 'btn btn-primary' %>
+
+
+ <%= link_to 'New Ingredient', new_ingredient_path, class: 'btn btn-default' %>
-
\ No newline at end of file
+
diff --git a/app/views/ingredients/index.json.jbuilder b/app/views/ingredients/index.json.jbuilder
deleted file mode 100644
index 8a6a136..0000000
--- a/app/views/ingredients/index.json.jbuilder
+++ /dev/null
@@ -1,4 +0,0 @@
-json.array!(@ingredients) do |ingredient|
- json.extract! ingredient, :id
- json.url ingredient_url(ingredient, format: :json)
-end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index d29b0d9..d82e36e 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -29,6 +29,11 @@
<%= li %>
<% end %>
+
+ <% profile_nav_items.each do |li| %>
+ <%= li %>
+ <% end %>
+
diff --git a/app/views/recipes/index.json.jbuilder b/app/views/recipes/index.json.jbuilder
deleted file mode 100644
index 2298d85..0000000
--- a/app/views/recipes/index.json.jbuilder
+++ /dev/null
@@ -1,4 +0,0 @@
-json.array!(@recipes) do |recipe|
- json.extract! recipe, :id
- json.url recipe_url(recipe, format: :json)
-end
diff --git a/app/views/recipes/show.json.jbuilder b/app/views/recipes/show.json.jbuilder
deleted file mode 100644
index 4e12951..0000000
--- a/app/views/recipes/show.json.jbuilder
+++ /dev/null
@@ -1 +0,0 @@
-json.extract! @recipe, :id, :created_at, :updated_at
diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb
new file mode 100644
index 0000000..f90cd32
--- /dev/null
+++ b/app/views/users/_form.html.erb
@@ -0,0 +1,30 @@
+<%= form_for(@user, url: user_path) do |f| %>
+
+ <%= render partial: 'shared/error_list', locals: {model: @user} %>
+
+
+ <%= f.label :username, class: 'control-label' %>
+ <%= f.text_field :username, class: 'form-control' %>
+
+
+ <%= f.label :full_name, 'Name', class: 'control-label' %>
+ <%= f.text_field :full_name, class: 'form-control' %>
+
+
+ <%= f.label :email, class: 'control-label' %>
+ <%= f.text_field :email, class: 'form-control' %>
+
+
+ <%= f.label :password, class: 'control-label' %>
+ <%= f.password_field :password, class: 'form-control' %>
+
+
+ <%= f.label :password_confirmation, class: 'control-label' %>
+ <%= f.password_field :password_confirmation, class: 'form-control' %>
+
+
+
+ <%= f.submit class: 'btn btn-primary' %>
+ <%= link_to "Back", root_path, class: 'btn btn-default' %>
+
+<% end %>
diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb
new file mode 100644
index 0000000..d15fd3f
--- /dev/null
+++ b/app/views/users/edit.html.erb
@@ -0,0 +1,11 @@
+
+
+
+
+
+ <%= render partial: 'form' %>
+
+
+
diff --git a/app/views/users/login.html.erb b/app/views/users/login.html.erb
new file mode 100644
index 0000000..5ed4e1d
--- /dev/null
+++ b/app/views/users/login.html.erb
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+ <%= form_tag(login_path, :method => :post) do %>
+
+
+ <%= label_tag :email, "Email", class: 'control-label' %>
+ <%= text_field_tag :email, nil, class: 'form-control' %>
+
+
+
+ <%= label_tag :password, "Password", class: 'control-label' %>
+ <%= password_field_tag :password, nil, class: 'form-control' %>
+
+
+ <%= submit_tag("Login", class: 'btn btn-primary') %>
+ <% end %>
+
+
+
+ <%= link_to "Create an Account", new_user_path, class: 'btn btn-default' %>
+
+
+
\ No newline at end of file
diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb
new file mode 100644
index 0000000..39b2b52
--- /dev/null
+++ b/app/views/users/new.html.erb
@@ -0,0 +1,11 @@
+
+
+
+
+
+ <%= render partial: 'form' %>
+
+
+
diff --git a/config/routes.rb b/config/routes.rb
index f0a3718..f20c78a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,6 +16,12 @@ Rails.application.routes.draw do
end
end
+ resource :user, only: [:new, :create, :edit, :update]
+
+ get '/login' => 'users#login', as: :login
+ post '/login' => 'users#verify_login'
+ get '/logout' => 'users#logout', as: :logout
+
root 'recipes#index'
diff --git a/db/migrate/20160119212055_create_users.rb b/db/migrate/20160119212055_create_users.rb
new file mode 100644
index 0000000..4294cbf
--- /dev/null
+++ b/db/migrate/20160119212055_create_users.rb
@@ -0,0 +1,15 @@
+class CreateUsers < ActiveRecord::Migration
+ def change
+ create_table :users do |t|
+ t.string :username
+ t.string :email
+ t.string :full_name
+ t.string :password_digest
+ t.boolean :admin
+
+ t.timestamps null: false
+ end
+
+ add_column :recipes, :user_id, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0885b4a..6bc89ae 100644
--- a/db/schema.rb
+++ b/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: 20160119012704) do
+ActiveRecord::Schema.define(version: 20160119212055) do
create_table "ingredients", force: :cascade do |t|
t.string "name"
@@ -55,6 +55,17 @@ ActiveRecord::Schema.define(version: 20160119012704) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "deleted"
+ t.integer "user_id"
+ end
+
+ create_table "users", force: :cascade do |t|
+ t.string "username"
+ t.string "email"
+ t.string "full_name"
+ t.string "password_digest"
+ t.boolean "admin"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
new file mode 100644
index 0000000..1eeebb6
--- /dev/null
+++ b/spec/factories/users.rb
@@ -0,0 +1,10 @@
+FactoryGirl.define do
+ factory :user do
+ username "MyString"
+email "MyString"
+full_name "MyString"
+password_digest "MyString"
+admin false
+ end
+
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
new file mode 100644
index 0000000..47a31bb
--- /dev/null
+++ b/spec/models/user_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe User, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end