units
This commit is contained in:
parent
8743b32a49
commit
3b4134f684
1
Gemfile
1
Gemfile
@ -17,6 +17,7 @@ gem 'bootstrap-sass', '~> 3.3.6'
|
|||||||
gem 'turbolinks'
|
gem 'turbolinks'
|
||||||
gem 'jbuilder', '~> 2.0'
|
gem 'jbuilder', '~> 2.0'
|
||||||
gem 'cocoon', '~> 1.2.6'
|
gem 'cocoon', '~> 1.2.6'
|
||||||
|
gem 'unitwise', '~> 2.0.0'
|
||||||
|
|
||||||
# Use ActiveModel has_secure_password
|
# Use ActiveModel has_secure_password
|
||||||
# gem 'bcrypt', '~> 3.1.7'
|
# gem 'bcrypt', '~> 3.1.7'
|
||||||
|
|||||||
16
Gemfile.lock
16
Gemfile.lock
@ -42,6 +42,7 @@ GEM
|
|||||||
json
|
json
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.2)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
|
blankslate (3.1.3)
|
||||||
bootstrap-sass (3.3.6)
|
bootstrap-sass (3.3.6)
|
||||||
autoprefixer-rails (>= 5.2.1)
|
autoprefixer-rails (>= 5.2.1)
|
||||||
sass (>= 3.3.4)
|
sass (>= 3.3.4)
|
||||||
@ -78,16 +79,21 @@ GEM
|
|||||||
thor (>= 0.14, < 2.0)
|
thor (>= 0.14, < 2.0)
|
||||||
json (1.8.3)
|
json (1.8.3)
|
||||||
libv8 (3.16.14.13)
|
libv8 (3.16.14.13)
|
||||||
|
liner (0.2.4)
|
||||||
loofah (2.0.3)
|
loofah (2.0.3)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.6.3)
|
mail (2.6.3)
|
||||||
mime-types (>= 1.16, < 3)
|
mime-types (>= 1.16, < 3)
|
||||||
|
memoizable (0.4.2)
|
||||||
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
mime-types (2.99)
|
mime-types (2.99)
|
||||||
mini_portile2 (2.0.0)
|
mini_portile2 (2.0.0)
|
||||||
minitest (5.8.3)
|
minitest (5.8.3)
|
||||||
multi_json (1.11.2)
|
multi_json (1.11.2)
|
||||||
nokogiri (1.6.7.1)
|
nokogiri (1.6.7.1)
|
||||||
mini_portile2 (~> 2.0.0.rc2)
|
mini_portile2 (~> 2.0.0.rc2)
|
||||||
|
parslet (1.7.1)
|
||||||
|
blankslate (>= 2.0, <= 4.0)
|
||||||
rack (1.6.4)
|
rack (1.6.4)
|
||||||
rack-test (0.6.3)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
@ -141,6 +147,7 @@ GEM
|
|||||||
sprockets (>= 2.8, < 4.0)
|
sprockets (>= 2.8, < 4.0)
|
||||||
sprockets-rails (>= 2.0, < 4.0)
|
sprockets-rails (>= 2.0, < 4.0)
|
||||||
tilt (>= 1.1, < 3)
|
tilt (>= 1.1, < 3)
|
||||||
|
signed_multiset (0.2.1)
|
||||||
sprockets (3.5.2)
|
sprockets (3.5.2)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
@ -162,6 +169,11 @@ GEM
|
|||||||
uglifier (2.7.2)
|
uglifier (2.7.2)
|
||||||
execjs (>= 0.3.0)
|
execjs (>= 0.3.0)
|
||||||
json (>= 1.8.0)
|
json (>= 1.8.0)
|
||||||
|
unitwise (2.0.0)
|
||||||
|
liner (~> 0.2)
|
||||||
|
memoizable (~> 0.4)
|
||||||
|
parslet (~> 1.5)
|
||||||
|
signed_multiset (~> 0.2)
|
||||||
web-console (2.2.1)
|
web-console (2.2.1)
|
||||||
activemodel (>= 4.0)
|
activemodel (>= 4.0)
|
||||||
binding_of_caller (>= 0.7.2)
|
binding_of_caller (>= 0.7.2)
|
||||||
@ -186,4 +198,8 @@ DEPENDENCIES
|
|||||||
therubyracer
|
therubyracer
|
||||||
turbolinks
|
turbolinks
|
||||||
uglifier (>= 1.3.0)
|
uglifier (>= 1.3.0)
|
||||||
|
unitwise (~> 2.0.0)
|
||||||
web-console (~> 2.0)
|
web-console (~> 2.0)
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.10.6
|
||||||
|
|||||||
@ -15,4 +15,5 @@
|
|||||||
//= require turbolinks
|
//= require turbolinks
|
||||||
//= require bootstrap-sprockets
|
//= require bootstrap-sprockets
|
||||||
//= require cocoon
|
//= require cocoon
|
||||||
|
//= require typeahead
|
||||||
//= require_tree .
|
//= require_tree .
|
||||||
|
|||||||
@ -7,30 +7,71 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initializeIngredientEditor($container, ingredientSearchEngine) {
|
||||||
|
$container.find(".ingredient-typeahead").typeahead({
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ingredients',
|
||||||
|
source: ingredientSearchEngine,
|
||||||
|
display: function(datum) {
|
||||||
|
return datum.name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(document).on("ready page:load", function() {
|
$(document).on("ready page:load", function() {
|
||||||
|
|
||||||
|
var ingredientSearchEngine = new Bloodhound({
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prefetch: {
|
||||||
|
url: '/ingredients/prefetch.json',
|
||||||
|
cache: false
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
url: '/ingredients/search.json?query=%QUERY',
|
||||||
|
wildcard: '%QUERY'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$("#step-list")
|
$("#step-list")
|
||||||
.on("cocoon:after-insert", function(e, item) {
|
.on("cocoon:after-insert", function(e, item) {
|
||||||
reorder($(this));
|
reorder($(this));
|
||||||
})
|
})
|
||||||
.on("cocoon:after-remove", function(e, item) {
|
.on("cocoon:after-remove", function(e, item) {
|
||||||
reorder($(this));
|
reorder($(this));
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$("#ingredient-list")
|
|
||||||
.on("cocoon:after-insert", function(e, item) {
|
|
||||||
reorder($(this));
|
|
||||||
})
|
})
|
||||||
.on("cocoon:after-remove", function(e, item) {
|
.on('changed', 'input.sort-order', function() {
|
||||||
reorder($(this));
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#step-list").on('changed', 'input.sort-order', function() {
|
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var $span = $this.closest(".nested-fields").find(".sort-order-display");
|
var $span = $this.closest(".nested-fields").find(".sort-order-display");
|
||||||
$span.html($this.val());
|
$span.html($this.val());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var $ingredientList = $("#ingredient-list");
|
||||||
|
|
||||||
|
initializeIngredientEditor($ingredientList, ingredientSearchEngine);
|
||||||
|
|
||||||
|
$ingredientList
|
||||||
|
.on("cocoon:after-insert", function(e, item) {
|
||||||
|
reorder($(this));
|
||||||
|
initializeIngredientEditor(item, ingredientSearchEngine);
|
||||||
|
})
|
||||||
|
.on("cocoon:after-remove", function(e, item) {
|
||||||
|
reorder($(this));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
@import "bootstrap";
|
@import "bootstrap";
|
||||||
@import "spacelab/_bootswatch";
|
@import "spacelab/_bootswatch";
|
||||||
|
|
||||||
|
@import "typeahead-bootstrap";
|
||||||
@import "recipes";
|
@import "recipes";
|
||||||
|
|
||||||
$footer_height: 40px;
|
$footer_height: 40px;
|
||||||
|
|||||||
64
app/assets/stylesheets/typeahead-bootstrap.scss
Normal file
64
app/assets/stylesheets/typeahead-bootstrap.scss
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
span.twitter-typeahead .tt-menu,
|
||||||
|
span.twitter-typeahead .tt-dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
float: left;
|
||||||
|
min-width: 160px;
|
||||||
|
padding: 5px 0;
|
||||||
|
margin: 2px 0 0;
|
||||||
|
list-style: none;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.twitter-typeahead .tt-hint {
|
||||||
|
color: #A5A5A5;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.twitter-typeahead .tt-suggestion {
|
||||||
|
display: block;
|
||||||
|
padding: 3px 20px;
|
||||||
|
clear: both;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
color: #333333;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.twitter-typeahead .tt-suggestion.tt-cursor,
|
||||||
|
span.twitter-typeahead .tt-suggestion:hover,
|
||||||
|
span.twitter-typeahead .tt-suggestion:focus {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: 0;
|
||||||
|
background-color: #337ab7;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.twitter-typeahead {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.input-group span.twitter-typeahead {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.input-group span.twitter-typeahead .tt-menu,
|
||||||
|
.input-group span.twitter-typeahead .tt-dropdown-menu {
|
||||||
|
top: 32px !important;
|
||||||
|
}
|
||||||
|
.input-group.input-group-sm span.twitter-typeahead .tt-menu,
|
||||||
|
.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu {
|
||||||
|
top: 28px !important;
|
||||||
|
}
|
||||||
|
.input-group.input-group-lg span.twitter-typeahead .tt-menu,
|
||||||
|
.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu {
|
||||||
|
top: 44px !important;
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ class IngredientsController < ApplicationController
|
|||||||
# GET /ingredients
|
# GET /ingredients
|
||||||
# GET /ingredients.json
|
# GET /ingredients.json
|
||||||
def index
|
def index
|
||||||
@ingredients = Ingredient.all
|
@ingredients = Ingredient.all.order(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /ingredients/1
|
# GET /ingredients/1
|
||||||
@ -28,7 +28,7 @@ class IngredientsController < ApplicationController
|
|||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @ingredient.save
|
if @ingredient.save
|
||||||
format.html { redirect_to @ingredient, notice: 'Ingredient was successfully created.' }
|
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully created.' }
|
||||||
format.json { render :show, status: :created, location: @ingredient }
|
format.json { render :show, status: :created, location: @ingredient }
|
||||||
else
|
else
|
||||||
format.html { render :new }
|
format.html { render :new }
|
||||||
@ -61,6 +61,16 @@ class IngredientsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prefetch
|
||||||
|
@ingredients = Ingredient.all.order(:name)
|
||||||
|
render :search
|
||||||
|
end
|
||||||
|
|
||||||
|
def search
|
||||||
|
query = params[:query] + '%'
|
||||||
|
@ingredients = Ingredient.where("name LIKE ?", query).order(:name)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Use callbacks to share common setup or constraints between actions.
|
# Use callbacks to share common setup or constraints between actions.
|
||||||
def set_ingredient
|
def set_ingredient
|
||||||
|
|||||||
18
app/models/concerns/density_validator.rb
Normal file
18
app/models/concerns/density_validator.rb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class DensityValidator < ActiveModel::EachValidator
|
||||||
|
def validate_each(record, attribute, value)
|
||||||
|
valid = false
|
||||||
|
msg = 'is not a unit of density'
|
||||||
|
|
||||||
|
begin
|
||||||
|
unit = UnitConversion::parse(value)
|
||||||
|
valid = UnitConversion::density? unit
|
||||||
|
rescue UnitConversion::UnparseableUnitError => e
|
||||||
|
valid = false
|
||||||
|
msg = e.message
|
||||||
|
end
|
||||||
|
|
||||||
|
unless valid
|
||||||
|
record.errors[attribute] << (options[:message] || msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,5 +1,6 @@
|
|||||||
class Ingredient < ActiveRecord::Base
|
class Ingredient < ActiveRecord::Base
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
|
validates :density, density: true, allow_blank: true
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,5 +4,6 @@ class RecipeIngredient < ActiveRecord::Base
|
|||||||
belongs_to :recipe, inverse_of: :recipe_ingredients
|
belongs_to :recipe, inverse_of: :recipe_ingredients
|
||||||
|
|
||||||
validates :sort_order, presence: true
|
validates :sort_order, presence: true
|
||||||
|
validates :custom_density, density: true, allow_blank: true
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
32
app/models/unit_conversion.rb
Normal file
32
app/models/unit_conversion.rb
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
module UnitConversion
|
||||||
|
|
||||||
|
UNIT_PARSING_REGEX = /^(?<value>(?:-?[0-9]+)?(?:\.?[0-9]*)?)\s*(?<unit>[a-z\/.\-()0-9]+)$/
|
||||||
|
|
||||||
|
class UnparseableUnitError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
|
class UnknownUnitError < UnparseableUnitError
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
|
||||||
|
def parse(unit_string)
|
||||||
|
match = UNIT_PARSING_REGEX.match(unit_string.to_s.strip)
|
||||||
|
|
||||||
|
if match && match[:value].present? && match[:unit].present?
|
||||||
|
begin
|
||||||
|
Unitwise(match[:value].to_f, match[:unit])
|
||||||
|
rescue Unitwise::ExpressionError => err
|
||||||
|
raise UnknownUnitError, err.message
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise UnparseableUnitError, "'#{unit_string}' does not appear to be a valid measurement of the form <value> <units> (ie '5 cup' or '223 gram/cup')"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def density?(unit)
|
||||||
|
unit.compatible_with? Unitwise(1, 'g/ml')
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<%= f.label :name %>
|
<%= f.label :name %>
|
||||||
<%= f.text_field :name, class: 'form-control' %>
|
<%= f.text_field :name, class: 'form-control', autofocus: true %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
|
<div class="page-header">
|
||||||
<h1>Listing Ingredients</h1>
|
<h1>Listing Ingredients</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
6
app/views/ingredients/search.json.jbuilder
Normal file
6
app/views/ingredients/search.json.jbuilder
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
json.array! @ingredients do |i|
|
||||||
|
|
||||||
|
json.extract! i, :id, :name, :density, :notes
|
||||||
|
|
||||||
|
end
|
||||||
@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
<div class="form-group form-group-sm">
|
<div class="form-group form-group-sm">
|
||||||
<%= f.label :custom_name, "Name" %>
|
<%= f.label :custom_name, "Name" %>
|
||||||
<%= f.text_field :custom_name, class: 'form-control' %>
|
<%= f.text_field :custom_name, class: 'form-control ingredient-typeahead' %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-8">
|
<div class="col-xs-4">
|
||||||
<div class="form-group form-group-sm">
|
<div class="form-group form-group-sm">
|
||||||
<%= f.label :quantity %>
|
<%= f.label :quantity %>
|
||||||
<%= f.text_field :quantity, class: 'form-control' %>
|
<%= f.text_field :quantity, class: 'form-control' %>
|
||||||
@ -23,6 +23,13 @@
|
|||||||
<%= f.text_field :units, class: 'form-control' %>
|
<%= f.text_field :units, class: 'form-control' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div class="form-group form-group-sm">
|
||||||
|
<%= f.label :custom_density, "Density" %>
|
||||||
|
<%= f.text_field :custom_density, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
<div class="col-xs-8">
|
<div class="col-xs-8">
|
||||||
<div class="form-group form-group-sm">
|
<div class="form-group form-group-sm">
|
||||||
<%= f.label :step %>
|
|
||||||
<%= f.text_area :step, class: 'form-control' %>
|
<%= f.text_area :step, class: 'form-control' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
<h1>Listing Recipes</h1>
|
<h1>Listing Recipes</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<% if @recipes.empty? %>
|
<% if @recipes.empty? %>
|
||||||
<p>No Recipes</p>
|
<p>No Recipes</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
||||||
<table>
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
|
|
||||||
resources :recipes
|
resources :recipes
|
||||||
resources :ingredients
|
resources :ingredients do
|
||||||
|
collection do
|
||||||
|
constraints format: 'json' do
|
||||||
|
get :search
|
||||||
|
get :prefetch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
root 'recipes#index'
|
root 'recipes#index'
|
||||||
|
|
||||||
|
|||||||
22
db/seeds.rb
22
db/seeds.rb
@ -5,3 +5,25 @@
|
|||||||
#
|
#
|
||||||
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
|
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
|
||||||
# Mayor.create(name: 'Emanuel', city: cities.first)
|
# Mayor.create(name: 'Emanuel', city: cities.first)
|
||||||
|
|
||||||
|
|
||||||
|
puts "Seeding..."
|
||||||
|
|
||||||
|
Ingredient.create!([
|
||||||
|
{name: 'Butter, Salted', density: '226 gram/cup'},
|
||||||
|
{name: 'Butter, Unsalted', density: '226 gram/cup'},
|
||||||
|
{name: 'Flour, Bleached All Purpose', density: '130 gram/cup'},
|
||||||
|
{name: 'Flour, Cake', density: '120 gram/cup'},
|
||||||
|
{name: 'Flour, Whole Wheat', density: '130 gram/cup'},
|
||||||
|
{name: 'Cornstarch', density: '10 gram/tablespoon'},
|
||||||
|
{name: 'Cornmeal', density: '120 gram/cup'},
|
||||||
|
{name: 'Sugar, Granulated', density: '200 gram/cup'},
|
||||||
|
{name: 'Sugar, Brown, Lightly Packed', density: '210 gram/cup'},
|
||||||
|
{name: 'Sugar, Powdered', density: '120 gram/cup'},
|
||||||
|
{name: 'Chocolate Chips', density: '170 gram/cup'},
|
||||||
|
{name: 'Cocoa Powder', density: '100 gram/cup'}
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
puts "Seeds planted."
|
||||||
@ -1,6 +1,8 @@
|
|||||||
FactoryGirl.define do
|
FactoryGirl.define do
|
||||||
factory :ingredient do
|
factory :ingredient do
|
||||||
|
name 'Ingredient'
|
||||||
|
density nil
|
||||||
|
notes 'note note note'
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
FactoryGirl.define do
|
FactoryGirl.define do
|
||||||
factory :recipe_ingredient do
|
factory :recipe_ingredient do
|
||||||
|
sort_order 1
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
FactoryGirl.define do
|
FactoryGirl.define do
|
||||||
factory :recipe_step do
|
factory :recipe_step do
|
||||||
number 1
|
sort_order 1
|
||||||
step "MyText"
|
step "MyText"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
FactoryGirl.define do
|
FactoryGirl.define do
|
||||||
factory :recipe do
|
factory :recipe do
|
||||||
|
name "Recipe"
|
||||||
|
description "desc"
|
||||||
|
source "source"
|
||||||
|
yields 4
|
||||||
|
total_time 20
|
||||||
|
active_time 10
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,26 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Ingredient, type: :model do
|
RSpec.describe Ingredient, type: :model do
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
|
describe 'validation' do
|
||||||
|
|
||||||
|
it 'validates density' do
|
||||||
|
i = build(:ingredient)
|
||||||
|
expect(i).to be_valid
|
||||||
|
|
||||||
|
i.density = '5'
|
||||||
|
expect(i).not_to be_valid
|
||||||
|
|
||||||
|
i.density = '5 cup'
|
||||||
|
expect(i).not_to be_valid
|
||||||
|
|
||||||
|
i.density = '5 gram/cup'
|
||||||
|
expect(i).to be_valid
|
||||||
|
|
||||||
|
i.density = '5 mile/hour'
|
||||||
|
expect(i).not_to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -27,13 +27,24 @@ require 'rspec/rails'
|
|||||||
ActiveRecord::Migration.maintain_test_schema!
|
ActiveRecord::Migration.maintain_test_schema!
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
|
||||||
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
|
||||||
|
|
||||||
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
|
||||||
# examples within a transaction, remove the following line or assign false
|
config.include FactoryGirl::Syntax::Methods
|
||||||
# instead of true.
|
|
||||||
config.use_transactional_fixtures = true
|
config.before(:suite) do
|
||||||
|
DatabaseCleaner.strategy = :transaction
|
||||||
|
DatabaseCleaner.clean_with(:truncation)
|
||||||
|
|
||||||
|
DatabaseCleaner.cleaning do
|
||||||
|
FactoryGirl.lint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
config.around(:each) do |example|
|
||||||
|
DatabaseCleaner.cleaning do
|
||||||
|
example.run
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# RSpec Rails can automatically mix in different behaviours to your tests
|
# RSpec Rails can automatically mix in different behaviours to your tests
|
||||||
# based on their file location, for example enabling you to call `get` and
|
# based on their file location, for example enabling you to call `get` and
|
||||||
|
|||||||
2451
vendor/assets/javascripts/typeahead.js
vendored
Normal file
2451
vendor/assets/javascripts/typeahead.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user