Added admin; updated calculator; added owner to ingredients
This commit is contained in:
parent
b890665966
commit
78dfa9120e
@ -3,6 +3,7 @@
|
|||||||
function State() {
|
function State() {
|
||||||
this.input = null;
|
this.input = null;
|
||||||
this.outputUnit = null;
|
this.outputUnit = null;
|
||||||
|
this.density = null;
|
||||||
this.changed = false;
|
this.changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,6 +14,13 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
State.prototype.setDensity = function(value) {
|
||||||
|
if (value != this.density) {
|
||||||
|
this.changed = true;
|
||||||
|
this.density = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
State.prototype.setOutputUnit = function(value) {
|
State.prototype.setOutputUnit = function(value) {
|
||||||
if (value != this.outputUnit) {
|
if (value != this.outputUnit) {
|
||||||
this.changed = true;
|
this.changed = true;
|
||||||
@ -28,17 +36,40 @@
|
|||||||
this.changed = false;
|
this.changed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var ingredientSearchEngine = new Bloodhound({
|
||||||
|
initialize: false,
|
||||||
|
datumTokenizer: function(datum) {
|
||||||
|
return Bloodhound.tokenizers.whitespace(datum.name);
|
||||||
|
},
|
||||||
|
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||||
|
identify: function(datum) { return datum.id; },
|
||||||
|
sorter: function(a, b) {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1;
|
||||||
|
} else if (b.name < a.name) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
url: '/calculator/ingredient_search.json?query=%QUERY',
|
||||||
|
wildcard: '%QUERY'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(document).on("ready page:load", function() {
|
$(document).on("ready page:load", function() {
|
||||||
|
|
||||||
var state = new State();
|
var state = new State();
|
||||||
var $input = $("#input");
|
var $input = $("#input");
|
||||||
var $output = $("#output");
|
var $output = $("#output");
|
||||||
|
var $density = $("#density");
|
||||||
var $outputUnit = $("#output_unit");
|
var $outputUnit = $("#output_unit");
|
||||||
|
|
||||||
var performUpdate = _.debounce(function() {
|
var performUpdate = _.debounce(function() {
|
||||||
$.getJSON(
|
$.getJSON(
|
||||||
"/calculator/calculate",
|
"/calculator/calculate",
|
||||||
{input: $input.val(), output_unit: $outputUnit.val()},
|
{input: $input.val(), output_unit: $outputUnit.val(), density: $density.val()},
|
||||||
function(data) {
|
function(data) {
|
||||||
if (data.errors.length) {
|
if (data.errors.length) {
|
||||||
$("#errors_panel").show();
|
$("#errors_panel").show();
|
||||||
@ -53,9 +84,15 @@
|
|||||||
|
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
$input.add($outputUnit).on('change blur keyup', function(evt) {
|
var ingredientPicked = function(i) {
|
||||||
|
$density.val(i.density);
|
||||||
|
$density.trigger('change');
|
||||||
|
};
|
||||||
|
|
||||||
|
$input.add($outputUnit).add($density).on('change blur keyup', function(evt) {
|
||||||
state.setInput($input.val());
|
state.setInput($input.val());
|
||||||
state.setOutputUnit($outputUnit.val());
|
state.setOutputUnit($outputUnit.val());
|
||||||
|
state.setDensity($density.val());
|
||||||
|
|
||||||
if (state.is_changed()) {
|
if (state.is_changed()) {
|
||||||
performUpdate();
|
performUpdate();
|
||||||
@ -63,6 +100,25 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ($("#calculator").length) {
|
||||||
|
ingredientSearchEngine.initialize(false);
|
||||||
|
|
||||||
|
$("#ingredient").typeahead({},
|
||||||
|
{
|
||||||
|
name: 'ingredients',
|
||||||
|
source: ingredientSearchEngine,
|
||||||
|
display: function(datum) {
|
||||||
|
return datum.name;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("typeahead:select", function(evt, value) {
|
||||||
|
ingredientPicked(value);
|
||||||
|
})
|
||||||
|
.on("typeahead:autocomplete", function(evt, value) {
|
||||||
|
ingredientPicked(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
43
app/controllers/admin/users_controller.rb
Normal file
43
app/controllers/admin/users_controller.rb
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
module Admin
|
||||||
|
class UsersController < ApplicationController
|
||||||
|
before_action :set_user, only: [:show, :edit, :update, :destroy]
|
||||||
|
|
||||||
|
before_filter :ensure_admin_user
|
||||||
|
|
||||||
|
def index
|
||||||
|
@users = User.order(:full_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@user.assign_attributes(user_params)
|
||||||
|
|
||||||
|
if @user.save
|
||||||
|
redirect_to admin_users_path, notice: 'User was successfully updated.'
|
||||||
|
else
|
||||||
|
render :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@user.destroy
|
||||||
|
redirect_to admin_users_path, notice: 'User was destroyed'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
# Use callbacks to share common setup or constraints between actions.
|
||||||
|
def set_user
|
||||||
|
@user = User.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
# Never trust parameters from the scary internet, only allow the white list through.
|
||||||
|
def user_params
|
||||||
|
params.require(:user).permit(:username, :email, :full_name, :admin, :password, :password_confirmation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -18,11 +18,25 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def ensure_owner(item)
|
def ensure_owner(item)
|
||||||
unless current_user && item && (item.user_id == current_user.id || current_user.admin?)
|
owner = case
|
||||||
flash[:warning] = "Operation Not Permitted"
|
when current_user.nil?
|
||||||
return redirect_to root_path
|
false
|
||||||
|
when item.nil?
|
||||||
|
true
|
||||||
|
when current_user.admin?
|
||||||
|
true
|
||||||
|
when current_user.id == item.user_id
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if owner
|
||||||
yield if block_given?
|
yield if block_given?
|
||||||
|
else
|
||||||
|
flash[:warning] = "Operation Not Permitted"
|
||||||
|
redirect_to root_path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_user
|
def current_user
|
||||||
|
@ -7,13 +7,15 @@ class CalculatorController < ApplicationController
|
|||||||
def calculate
|
def calculate
|
||||||
input = params[:input]
|
input = params[:input]
|
||||||
output_unit = params[:output_unit]
|
output_unit = params[:output_unit]
|
||||||
|
density = params[:density]
|
||||||
|
density = nil unless density.present?
|
||||||
|
|
||||||
data = {errors: [], output: ''}
|
data = {errors: [], output: ''}
|
||||||
|
|
||||||
begin
|
begin
|
||||||
unit = UnitConversion.parse(input)
|
unit = UnitConversion.parse(input)
|
||||||
if output_unit.present?
|
if output_unit.present?
|
||||||
unit = unit.convert(output_unit)
|
unit = unit.convert(output_unit, density)
|
||||||
data[:output] = unit.to_s
|
data[:output] = unit.to_s
|
||||||
else
|
else
|
||||||
data[:output] = unit.auto_unit.to_s
|
data[:output] = unit.auto_unit.to_s
|
||||||
@ -26,4 +28,8 @@ class CalculatorController < ApplicationController
|
|||||||
render json: data
|
render json: data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ingredient_search
|
||||||
|
@ingredients = Ingredient.has_density.search(params[:query]).order(:name)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -17,12 +17,17 @@ class IngredientsController < ApplicationController
|
|||||||
|
|
||||||
# GET /ingredients/1/edit
|
# GET /ingredients/1/edit
|
||||||
def edit
|
def edit
|
||||||
|
ensure_owner @ingredient
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /ingredients
|
# POST /ingredients
|
||||||
# POST /ingredients.json
|
# POST /ingredients.json
|
||||||
def create
|
def create
|
||||||
@ingredient = Ingredient.new(ingredient_params)
|
@ingredient = Ingredient.new(ingredient_params)
|
||||||
|
@ingredient.user = current_user
|
||||||
|
if @ingredient.ndbn.present?
|
||||||
|
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
|
||||||
|
end
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @ingredient.save
|
if @ingredient.save
|
||||||
@ -37,6 +42,7 @@ class IngredientsController < ApplicationController
|
|||||||
|
|
||||||
# PATCH/PUT /ingredients/1
|
# PATCH/PUT /ingredients/1
|
||||||
def update
|
def update
|
||||||
|
ensure_owner @ingredient do
|
||||||
@ingredient.assign_attributes(ingredient_params)
|
@ingredient.assign_attributes(ingredient_params)
|
||||||
if @ingredient.ndbn.present?
|
if @ingredient.ndbn.present?
|
||||||
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
|
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
|
||||||
@ -52,16 +58,19 @@ class IngredientsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# DELETE /ingredients/1
|
# DELETE /ingredients/1
|
||||||
# DELETE /ingredients/1.json
|
# DELETE /ingredients/1.json
|
||||||
def destroy
|
def destroy
|
||||||
|
ensure_owner @ingredient do
|
||||||
@ingredient.destroy
|
@ingredient.destroy
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to ingredients_url, notice: 'Ingredient was successfully destroyed.' }
|
format.html { redirect_to ingredients_url, notice: 'Ingredient was successfully destroyed.' }
|
||||||
format.json { head :no_content }
|
format.json { head :no_content }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def select_ndbn
|
def select_ndbn
|
||||||
if params[:id].present?
|
if params[:id].present?
|
||||||
|
@ -5,12 +5,16 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def nav_items
|
def nav_items
|
||||||
[
|
nav = [
|
||||||
nav_item('Recipes', recipes_path, 'recipes'),
|
nav_item('Recipes', recipes_path, 'recipes'),
|
||||||
nav_item('Ingredients', ingredients_path, 'ingredients'),
|
nav_item('Ingredients', ingredients_path, 'ingredients'),
|
||||||
nav_item('Calculator', calculator_path, 'calculator'),
|
nav_item('Calculator', calculator_path, 'calculator'),
|
||||||
nav_item('About', about_path, 'home')
|
nav_item('About', about_path, 'home')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if current_user && current_user.admin?
|
||||||
|
nav << nav_item('Admin', admin_users_path, 'admin/users')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def profile_nav_items
|
def profile_nav_items
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
class Ingredient < ActiveRecord::Base
|
class Ingredient < ActiveRecord::Base
|
||||||
include TokenizedLike
|
include TokenizedLike
|
||||||
|
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :density, density: true, allow_blank: true
|
validates :density, density: true, allow_blank: true
|
||||||
|
|
||||||
|
scope :has_density, -> { where.not(density: nil) }
|
||||||
|
|
||||||
def self.search(query)
|
def self.search(query)
|
||||||
tokens = query.to_s.split(' ')
|
tokens = query.to_s.split(' ')
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
class User < ActiveRecord::Base
|
class User < ActiveRecord::Base
|
||||||
|
|
||||||
|
has_many :recipes, dependent: :nullify
|
||||||
|
has_many :ingredients, dependent: :nullify
|
||||||
|
|
||||||
has_secure_password
|
has_secure_password
|
||||||
|
|
||||||
validates :username, presence: true, uniqueness: { case_sensitive: false }
|
validates :username, presence: true, uniqueness: { case_sensitive: false }
|
||||||
|
0
app/views/admin/users/edit.html.erb
Normal file
0
app/views/admin/users/edit.html.erb
Normal file
42
app/views/admin/users/index.html.erb
Normal file
42
app/views/admin/users/index.html.erb
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Users</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if @users.empty? %>
|
||||||
|
<p>No Users</p>
|
||||||
|
<% else %>
|
||||||
|
|
||||||
|
<table class="table table-condensed table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Admin</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<% @users.each do |user| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= link_to user.full_name, edit_admin_user_path(user) %></td>
|
||||||
|
<td><%= user.username %></td>
|
||||||
|
<td><%= user.email %></td>
|
||||||
|
<td><%= user.admin? %></td>
|
||||||
|
<td>
|
||||||
|
<%= link_to [:admin, user], method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-sm btn-danger' do %>
|
||||||
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
0
app/views/admin/users/show.html.erb
Normal file
0
app/views/admin/users/show.html.erb
Normal file
@ -1,7 +1,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
|
|
||||||
<div class="page-header">
|
<div id="calculator" class="page-header">
|
||||||
<h1>Calculator</h1>
|
<h1>Calculator</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -15,6 +15,16 @@
|
|||||||
<%= text_field_tag :output_unit, nil, class: 'form-control' %>
|
<%= text_field_tag :output_unit, nil, class: 'form-control' %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group col-xs-6">
|
||||||
|
<%= label_tag :ingredient, "Ingredient", class: 'control-label' %>
|
||||||
|
<%= text_field_tag :ingredient, nil, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group col-xs-6">
|
||||||
|
<%= label_tag :density, "Density", class: 'control-label' %>
|
||||||
|
<%= text_field_tag :density, nil, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group col-xs-12">
|
<div class="form-group col-xs-12">
|
||||||
<%= label_tag :output, "Output", class: 'control-label' %>
|
<%= label_tag :output, "Output", class: 'control-label' %>
|
||||||
<%= text_field_tag :output, nil, class: 'form-control', readonly: true %>
|
<%= text_field_tag :output, nil, class: 'form-control', readonly: true %>
|
||||||
|
6
app/views/calculator/ingredient_search.json.jbuilder
Normal file
6
app/views/calculator/ingredient_search.json.jbuilder
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
json.array! @ingredients do |i|
|
||||||
|
|
||||||
|
json.extract! i, :id, :name, :density
|
||||||
|
|
||||||
|
end
|
@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
A Recipe manager. Source available on <a href="https://github.com/DanElbert/parsley">GitHub</a>.
|
A Recipe manager. Source available from my Git repository at <a href="https://source.elbert.us/dan/parsley">https://source.elbert.us</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -29,6 +29,11 @@ Rails.application.routes.draw do
|
|||||||
scope '/calculator', controller: :calculator, as: :calculator do
|
scope '/calculator', controller: :calculator, as: :calculator do
|
||||||
get '/' => :index
|
get '/' => :index
|
||||||
get '/calculate' => :calculate
|
get '/calculate' => :calculate
|
||||||
|
get '/ingredient_search' => :ingredient_search
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace 'admin' do
|
||||||
|
resources :users, except: [:new, :create]
|
||||||
end
|
end
|
||||||
|
|
||||||
root 'recipes#index'
|
root 'recipes#index'
|
||||||
|
5
db/migrate/20160303180854_add_user_id_to_ingredient.rb
Normal file
5
db/migrate/20160303180854_add_user_id_to_ingredient.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class AddUserIdToIngredient < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :ingredients, :user_id, :integer
|
||||||
|
end
|
||||||
|
end
|
@ -11,7 +11,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20160130231838) do
|
ActiveRecord::Schema.define(version: 20160303180854) do
|
||||||
|
|
||||||
create_table "ingredients", force: :cascade do |t|
|
create_table "ingredients", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
@ -28,6 +28,7 @@ ActiveRecord::Schema.define(version: 20160130231838) do
|
|||||||
t.integer "kcal"
|
t.integer "kcal"
|
||||||
t.decimal "fiber", precision: 10, scale: 1
|
t.decimal "fiber", precision: 10, scale: 1
|
||||||
t.decimal "sugar", precision: 10, scale: 2
|
t.decimal "sugar", precision: 10, scale: 2
|
||||||
|
t.integer "user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "recipe_ingredients", force: :cascade do |t|
|
create_table "recipe_ingredients", force: :cascade do |t|
|
||||||
|
Loading…
Reference in New Issue
Block a user