diff --git a/app/assets/javascripts/calculator.js b/app/assets/javascripts/calculator.js index 42ebd0e..08b4f0e 100644 --- a/app/assets/javascripts/calculator.js +++ b/app/assets/javascripts/calculator.js @@ -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); \ No newline at end of file diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb new file mode 100644 index 0000000..19558f1 --- /dev/null +++ b/app/controllers/admin/users_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d6fe376..08f0768 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 diff --git a/app/controllers/calculator_controller.rb b/app/controllers/calculator_controller.rb index 2105d4d..9c2cad8 100644 --- a/app/controllers/calculator_controller.rb +++ b/app/controllers/calculator_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/ingredients_controller.rb b/app/controllers/ingredients_controller.rb index 14f2283..b646bd0 100644 --- a/app/controllers/ingredients_controller.rb +++ b/app/controllers/ingredients_controller.rb @@ -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 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 59694b0..d2d12b6 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -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 diff --git a/app/models/ingredient.rb b/app/models/ingredient.rb index 3901ee1..ac0dcbc 100644 --- a/app/models/ingredient.rb +++ b/app/models/ingredient.rb @@ -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(' ') diff --git a/app/models/user.rb b/app/models/user.rb index 265a353..7544b5d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 } diff --git a/app/views/admin/users/edit.html.erb b/app/views/admin/users/edit.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb new file mode 100644 index 0000000..3dbeae3 --- /dev/null +++ b/app/views/admin/users/index.html.erb @@ -0,0 +1,42 @@ +
+
+ + + <% if @users.empty? %> +

No Users

+ <% else %> + + + + + + + + + + + + + + <% @users.each do |user| %> + + + + + + + + <% end %> + +
NameUsernameEmailAdmin
<%= link_to user.full_name, edit_admin_user_path(user) %><%= user.username %><%= user.email %><%= user.admin? %> + <%= link_to [:admin, user], method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-sm btn-danger' do %> + + <% end %> +
+ <% end %> + + +
+
diff --git a/app/views/admin/users/show.html.erb b/app/views/admin/users/show.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/app/views/calculator/index.html.erb b/app/views/calculator/index.html.erb index 2e3a351..f057314 100644 --- a/app/views/calculator/index.html.erb +++ b/app/views/calculator/index.html.erb @@ -1,7 +1,7 @@
- +
+ <%= label_tag :ingredient, "Ingredient", class: 'control-label' %> + <%= text_field_tag :ingredient, nil, class: 'form-control' %> +
+ +
+ <%= label_tag :density, "Density", class: 'control-label' %> + <%= text_field_tag :density, nil, class: 'form-control' %> +
+
<%= label_tag :output, "Output", class: 'control-label' %> <%= text_field_tag :output, nil, class: 'form-control', readonly: true %> diff --git a/app/views/calculator/ingredient_search.json.jbuilder b/app/views/calculator/ingredient_search.json.jbuilder new file mode 100644 index 0000000..d2b083f --- /dev/null +++ b/app/views/calculator/ingredient_search.json.jbuilder @@ -0,0 +1,6 @@ + +json.array! @ingredients do |i| + + json.extract! i, :id, :name, :density + +end \ No newline at end of file diff --git a/app/views/home/about.html.erb b/app/views/home/about.html.erb index 010aff2..6f8a616 100644 --- a/app/views/home/about.html.erb +++ b/app/views/home/about.html.erb @@ -6,7 +6,7 @@

- A Recipe manager. Source available on GitHub. + A Recipe manager. Source available from my Git repository at https://source.elbert.us.

diff --git a/config/routes.rb b/config/routes.rb index c8d9c29..9c7b3be 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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' diff --git a/db/migrate/20160303180854_add_user_id_to_ingredient.rb b/db/migrate/20160303180854_add_user_id_to_ingredient.rb new file mode 100644 index 0000000..34c775f --- /dev/null +++ b/db/migrate/20160303180854_add_user_id_to_ingredient.rb @@ -0,0 +1,5 @@ +class AddUserIdToIngredient < ActiveRecord::Migration + def change + add_column :ingredients, :user_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index d93b8e2..6eeffc3 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: 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|