Added admin; updated calculator; added owner to ingredients

This commit is contained in:
Dan Elbert 2016-03-03 13:12:42 -06:00
parent b890665966
commit 78dfa9120e
17 changed files with 233 additions and 25 deletions

View File

@ -3,6 +3,7 @@
function State() {
this.input = null;
this.outputUnit = null;
this.density = null;
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) {
if (value != this.outputUnit) {
this.changed = true;
@ -28,17 +36,40 @@
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() {
var state = new State();
var $input = $("#input");
var $output = $("#output");
var $density = $("#density");
var $outputUnit = $("#output_unit");
var performUpdate = _.debounce(function() {
$.getJSON(
"/calculator/calculate",
{input: $input.val(), output_unit: $outputUnit.val()},
{input: $input.val(), output_unit: $outputUnit.val(), density: $density.val()},
function(data) {
if (data.errors.length) {
$("#errors_panel").show();
@ -53,9 +84,15 @@
}, 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.setOutputUnit($outputUnit.val());
state.setDensity($density.val());
if (state.is_changed()) {
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);

View 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

View File

@ -18,11 +18,25 @@ class ApplicationController < ActionController::Base
end
def ensure_owner(item)
unless current_user && item && (item.user_id == current_user.id || current_user.admin?)
owner = case
when current_user.nil?
false
when item.nil?
true
when current_user.admin?
true
when current_user.id == item.user_id
true
else
false
end
if owner
yield if block_given?
else
flash[:warning] = "Operation Not Permitted"
return redirect_to root_path
redirect_to root_path
end
yield if block_given?
end
def current_user

View File

@ -7,13 +7,15 @@ class CalculatorController < ApplicationController
def calculate
input = params[:input]
output_unit = params[:output_unit]
density = params[:density]
density = nil unless density.present?
data = {errors: [], output: ''}
begin
unit = UnitConversion.parse(input)
if output_unit.present?
unit = unit.convert(output_unit)
unit = unit.convert(output_unit, density)
data[:output] = unit.to_s
else
data[:output] = unit.auto_unit.to_s
@ -26,4 +28,8 @@ class CalculatorController < ApplicationController
render json: data
end
def ingredient_search
@ingredients = Ingredient.has_density.search(params[:query]).order(:name)
end
end

View File

@ -17,12 +17,17 @@ class IngredientsController < ApplicationController
# GET /ingredients/1/edit
def edit
ensure_owner @ingredient
end
# POST /ingredients
# POST /ingredients.json
def create
@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|
if @ingredient.save
@ -37,18 +42,20 @@ class IngredientsController < ApplicationController
# PATCH/PUT /ingredients/1
def update
@ingredient.assign_attributes(ingredient_params)
if @ingredient.ndbn.present?
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
end
ensure_owner @ingredient do
@ingredient.assign_attributes(ingredient_params)
if @ingredient.ndbn.present?
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
end
respond_to do |format|
if @ingredient.save
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully updated.' }
format.json { render :show, status: :ok, location: @ingredient }
else
format.html { render :edit }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
respond_to do |format|
if @ingredient.save
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully updated.' }
format.json { render :show, status: :ok, location: @ingredient }
else
format.html { render :edit }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
end
end
end
end
@ -56,10 +63,12 @@ class IngredientsController < ApplicationController
# DELETE /ingredients/1
# DELETE /ingredients/1.json
def destroy
@ingredient.destroy
respond_to do |format|
format.html { redirect_to ingredients_url, notice: 'Ingredient was successfully destroyed.' }
format.json { head :no_content }
ensure_owner @ingredient do
@ingredient.destroy
respond_to do |format|
format.html { redirect_to ingredients_url, notice: 'Ingredient was successfully destroyed.' }
format.json { head :no_content }
end
end
end

View File

@ -5,12 +5,16 @@ module ApplicationHelper
end
def nav_items
[
nav = [
nav_item('Recipes', recipes_path, 'recipes'),
nav_item('Ingredients', ingredients_path, 'ingredients'),
nav_item('Calculator', calculator_path, 'calculator'),
nav_item('About', about_path, 'home')
]
if current_user && current_user.admin?
nav << nav_item('Admin', admin_users_path, 'admin/users')
end
end
def profile_nav_items

View File

@ -1,9 +1,13 @@
class Ingredient < ActiveRecord::Base
include TokenizedLike
belongs_to :user
validates :name, presence: true
validates :density, density: true, allow_blank: true
scope :has_density, -> { where.not(density: nil) }
def self.search(query)
tokens = query.to_s.split(' ')

View File

@ -1,5 +1,8 @@
class User < ActiveRecord::Base
has_many :recipes, dependent: :nullify
has_many :ingredients, dependent: :nullify
has_secure_password
validates :username, presence: true, uniqueness: { case_sensitive: false }

View File

View 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>

View File

View File

@ -1,7 +1,7 @@
<div class="row">
<div class="col-xs-12">
<div class="page-header">
<div id="calculator" class="page-header">
<h1>Calculator</h1>
</div>
@ -15,6 +15,16 @@
<%= text_field_tag :output_unit, nil, class: 'form-control' %>
</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">
<%= label_tag :output, "Output", class: 'control-label' %>
<%= text_field_tag :output, nil, class: 'form-control', readonly: true %>

View File

@ -0,0 +1,6 @@
json.array! @ingredients do |i|
json.extract! i, :id, :name, :density
end

View File

@ -6,7 +6,7 @@
</div>
<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>

View File

@ -29,6 +29,11 @@ Rails.application.routes.draw do
scope '/calculator', controller: :calculator, as: :calculator do
get '/' => :index
get '/calculate' => :calculate
get '/ingredient_search' => :ingredient_search
end
namespace 'admin' do
resources :users, except: [:new, :create]
end
root 'recipes#index'

View File

@ -0,0 +1,5 @@
class AddUserIdToIngredient < ActiveRecord::Migration
def change
add_column :ingredients, :user_id, :integer
end
end

View File

@ -11,7 +11,7 @@
#
# 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|
t.string "name"
@ -28,6 +28,7 @@ ActiveRecord::Schema.define(version: 20160130231838) do
t.integer "kcal"
t.decimal "fiber", precision: 10, scale: 1
t.decimal "sugar", precision: 10, scale: 2
t.integer "user_id"
end
create_table "recipe_ingredients", force: :cascade do |t|