Major unit conversion refactor + lots more specs
This commit is contained in:
parent
ffb2c92f74
commit
d5082f9c16
2
Gemfile
2
Gemfile
@ -31,6 +31,8 @@ gem 'bcrypt', '~> 3.1.7'
|
||||
|
||||
group :development, :test do
|
||||
|
||||
gem 'guard', '~> 2.13.0'
|
||||
gem 'guard-rspec', require: false
|
||||
gem 'rspec-rails', '~> 3.4.0'
|
||||
gem 'factory_girl_rails', '~> 4.5.0'
|
||||
gem 'database_cleaner', '~> 1.5.1'
|
||||
|
41
Gemfile.lock
41
Gemfile.lock
@ -50,6 +50,7 @@ GEM
|
||||
builder (3.2.2)
|
||||
byebug (8.2.1)
|
||||
cocoon (1.2.6)
|
||||
coderay (1.1.0)
|
||||
coffee-rails (4.1.1)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0, < 5.1.x)
|
||||
@ -68,8 +69,24 @@ GEM
|
||||
factory_girl_rails (4.5.0)
|
||||
factory_girl (~> 4.5.0)
|
||||
railties (>= 3.0.0)
|
||||
ffi (1.9.10)
|
||||
formatador (0.2.5)
|
||||
globalid (0.3.6)
|
||||
activesupport (>= 4.1.0)
|
||||
guard (2.13.0)
|
||||
formatador (>= 0.2.4)
|
||||
listen (>= 2.7, <= 4.0)
|
||||
lumberjack (~> 1.0)
|
||||
nenv (~> 0.1)
|
||||
notiffany (~> 0.0)
|
||||
pry (>= 0.9.12)
|
||||
shellany (~> 0.0)
|
||||
thor (>= 0.18.1)
|
||||
guard-compat (1.2.1)
|
||||
guard-rspec (4.6.4)
|
||||
guard (~> 2.1)
|
||||
guard-compat (~> 1.1)
|
||||
rspec (>= 2.99.0, < 4.0)
|
||||
i18n (0.7.0)
|
||||
jbuilder (2.4.0)
|
||||
activesupport (>= 3.0.0, < 5.1)
|
||||
@ -81,21 +98,34 @@ GEM
|
||||
json (1.8.3)
|
||||
libv8 (3.16.14.13)
|
||||
liner (0.2.4)
|
||||
listen (3.0.5)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
lumberjack (1.0.10)
|
||||
mail (2.6.3)
|
||||
mime-types (>= 1.16, < 3)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
method_source (0.8.2)
|
||||
mime-types (2.99)
|
||||
mini_portile2 (2.0.0)
|
||||
minitest (5.8.3)
|
||||
multi_json (1.11.2)
|
||||
mysql2 (0.3.20)
|
||||
nenv (0.2.0)
|
||||
nokogiri (1.6.7.1)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
notiffany (0.0.8)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
parslet (1.7.1)
|
||||
blankslate (>= 2.0, <= 4.0)
|
||||
pry (0.10.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
rack (1.6.4)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
@ -124,7 +154,14 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rake (10.5.0)
|
||||
rb-fsevent (0.9.7)
|
||||
rb-inotify (0.9.5)
|
||||
ffi (>= 0.5.0)
|
||||
ref (2.0.0)
|
||||
rspec (3.4.0)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-core (3.4.1)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
@ -149,7 +186,9 @@ GEM
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
shellany (0.0.1)
|
||||
signed_multiset (0.2.1)
|
||||
slop (3.6.0)
|
||||
sprockets (3.5.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
@ -192,6 +231,8 @@ DEPENDENCIES
|
||||
cocoon (~> 1.2.6)
|
||||
database_cleaner (~> 1.5.1)
|
||||
factory_girl_rails (~> 4.5.0)
|
||||
guard (~> 2.13.0)
|
||||
guard-rspec
|
||||
jbuilder (~> 2.0)
|
||||
jquery-rails
|
||||
mysql2 (~> 0.3.18)
|
||||
|
70
Guardfile
Normal file
70
Guardfile
Normal file
@ -0,0 +1,70 @@
|
||||
# A sample Guardfile
|
||||
# More info at https://github.com/guard/guard#readme
|
||||
|
||||
## Uncomment and set this to only include directories you want to watch
|
||||
# directories %w(app lib config test spec features) \
|
||||
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
||||
|
||||
## Note: if you are using the `directories` clause above and you are not
|
||||
## watching the project directory ('.'), then you will want to move
|
||||
## the Guardfile to a watched dir and symlink it back, e.g.
|
||||
#
|
||||
# $ mkdir config
|
||||
# $ mv Guardfile config/
|
||||
# $ ln -s config/Guardfile .
|
||||
#
|
||||
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
||||
|
||||
# Note: The cmd option is now required due to the increasing number of ways
|
||||
# rspec may be run, below are examples of the most common uses.
|
||||
# * bundler: 'bundle exec rspec'
|
||||
# * bundler binstubs: 'bin/rspec'
|
||||
# * spring: 'bin/rspec' (This will use spring if running and you have
|
||||
# installed the spring binstubs per the docs)
|
||||
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
||||
# * 'just' rspec: 'rspec'
|
||||
|
||||
guard :rspec, cmd: "bundle exec rspec" do
|
||||
require "guard/rspec/dsl"
|
||||
dsl = Guard::RSpec::Dsl.new(self)
|
||||
|
||||
# Feel free to open issues for suggestions and improvements
|
||||
|
||||
# RSpec files
|
||||
rspec = dsl.rspec
|
||||
watch(rspec.spec_helper) { rspec.spec_dir }
|
||||
watch(rspec.spec_support) { rspec.spec_dir }
|
||||
watch(rspec.spec_files)
|
||||
|
||||
# Ruby files
|
||||
ruby = dsl.ruby
|
||||
dsl.watch_spec_files_for(ruby.lib_files)
|
||||
|
||||
# Rails files
|
||||
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
||||
dsl.watch_spec_files_for(rails.app_files)
|
||||
dsl.watch_spec_files_for(rails.views)
|
||||
|
||||
watch(rails.controllers) do |m|
|
||||
[
|
||||
rspec.spec.("routing/#{m[1]}_routing"),
|
||||
rspec.spec.("controllers/#{m[1]}_controller"),
|
||||
rspec.spec.("acceptance/#{m[1]}")
|
||||
]
|
||||
end
|
||||
|
||||
# Rails config changes
|
||||
watch(rails.spec_helper) { rspec.spec_dir }
|
||||
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
||||
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
||||
|
||||
# Capybara features specs
|
||||
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
|
||||
watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
|
||||
|
||||
# Turnip features and steps
|
||||
watch(%r{^spec/acceptance/(.+)\.feature$})
|
||||
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
||||
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
||||
end
|
||||
end
|
@ -40,16 +40,11 @@
|
||||
"/calculator/calculate",
|
||||
{input: $input.val(), output_unit: $outputUnit.val()},
|
||||
function(data) {
|
||||
if (data.errors.input) {
|
||||
$input.closest(".form-group").addClass("has-error");
|
||||
if (data.errors.length) {
|
||||
$("#errors_panel").show();
|
||||
$("#errors_container").html(data.errors.join(" "));
|
||||
} else {
|
||||
$input.closest(".form-group").removeClass("has-error");
|
||||
}
|
||||
|
||||
if (data.errors.output_unit) {
|
||||
$outputUnit.closest(".form-group").addClass("has-error");
|
||||
} else {
|
||||
$outputUnit.closest(".form-group").removeClass("has-error");
|
||||
$("#errors_panel").hide();
|
||||
}
|
||||
|
||||
$output.val(data.output);
|
||||
|
@ -7,36 +7,20 @@ class CalculatorController < ApplicationController
|
||||
def calculate
|
||||
input = params[:input]
|
||||
output_unit = params[:output_unit]
|
||||
parsed_input = nil
|
||||
original_number = nil
|
||||
|
||||
data = {errors: {}, output: ''}
|
||||
data = {errors: [], output: ''}
|
||||
|
||||
if input.present?
|
||||
|
||||
begin
|
||||
parsed_input = UnitConversion.parse(input)
|
||||
original_number = parsed_input.value
|
||||
rescue UnitConversion::UnparseableUnitError => e
|
||||
data[:errors][:input] = [e.message]
|
||||
begin
|
||||
unit = UnitConversion.parse(input)
|
||||
if output_unit.present?
|
||||
unit = unit.convert(output_unit)
|
||||
data[:output] = unit.to_s
|
||||
else
|
||||
data[:output] = unit.auto_unit.to_s
|
||||
end
|
||||
|
||||
if parsed_input.present? && output_unit.present?
|
||||
begin
|
||||
parsed_input = parsed_input.convert_to output_unit
|
||||
rescue Unitwise::ExpressionError => e
|
||||
data[:errors][:output_unit] = [e.message]
|
||||
end
|
||||
end
|
||||
|
||||
if parsed_input
|
||||
puts parsed_input.value
|
||||
puts parsed_input.unit
|
||||
data[:output] = UnitConversion.auto_unit(UnitConversion.quantity_format(parsed_input.value, original_number), parsed_input.unit.to_s)
|
||||
end
|
||||
|
||||
else
|
||||
data[:errors][:input] = ['Invalid input']
|
||||
rescue UnitConversion::UnparseableUnitError => e
|
||||
data[:errors] << e.message
|
||||
end
|
||||
|
||||
render json: data
|
||||
|
@ -5,7 +5,7 @@ class DensityValidator < ActiveModel::EachValidator
|
||||
|
||||
begin
|
||||
unit = UnitConversion::parse(value)
|
||||
valid = UnitConversion::density? unit
|
||||
valid = unit.density?
|
||||
rescue UnitConversion::UnparseableUnitError => e
|
||||
valid = false
|
||||
msg = e.message
|
||||
|
@ -50,15 +50,13 @@ class Ingredient < ActiveRecord::Base
|
||||
def calculate_density(grams, description)
|
||||
return nil if grams.blank? || description.blank?
|
||||
|
||||
# replace 'fl oz' with 'floz'
|
||||
description = description.gsub(/fl oz/i, 'floz')
|
||||
|
||||
begin
|
||||
unit = UnitConversion.parse(description)
|
||||
if UnitConversion.volume?(unit)
|
||||
mass = Unitwise(grams, 'g')
|
||||
density = (mass / unit).convert_to(UnitConversion.normalize_unit_names('oz/cup'))
|
||||
return "#{density.value.round(4)} oz/cup"
|
||||
value_unit = UnitConversion.parse(description)
|
||||
if value_unit.volume?
|
||||
density_value = grams.to_d / value_unit.raw_value
|
||||
density_units = "g/#{value_unit.unit.unit}"
|
||||
density = UnitConversion.parse(density_value, density_units)
|
||||
return density.convert('oz/cup').to_s
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
@ -21,11 +21,16 @@ class RecipeIngredient < ActiveRecord::Base
|
||||
|
||||
def scale(factor, auto_unit = false)
|
||||
if factor.present? && self.quantity.present? && factor != '1'
|
||||
self.quantity = UnitConversion.convert(self.quantity, factor, nil, nil)
|
||||
|
||||
value_unit = UnitConversion.parse(self.quantity, self.units)
|
||||
value_unit = value_unit.scale(factor)
|
||||
|
||||
if auto_unit
|
||||
self.quantity, self.units = UnitConversion.auto_unit(self.quantity, self.units)
|
||||
value_unit = value_unit.auto_unit
|
||||
end
|
||||
|
||||
self.quantity = value_unit.pretty_value
|
||||
self.units = value_unit.unit.to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,264 +0,0 @@
|
||||
module UnitConversion
|
||||
|
||||
INTEGER_REGEX = /-?[0-9]+/
|
||||
DECIMAL_REGEX = /(?:-?[0-9]+)?\.[0-9]+/
|
||||
RATIONAL_REGEX = /-?(?:[0-9]+ )?[0-9]+\/[0-9]+/
|
||||
UNIT_REGEX = /[\[\]a-zA-Z][\[\]a-zA-Z\/.\-()0-9]*/
|
||||
UNIT_PARSING_REGEX = /^(?<value>#{INTEGER_REGEX}|#{DECIMAL_REGEX}|#{RATIONAL_REGEX})\s+(?<unit>#{UNIT_REGEX})$/
|
||||
|
||||
UNIT_ALIASES = {
|
||||
'[cup_us]': %w(cup cups c),
|
||||
'[tbs_us]': %w(tablespoon tablespoons tbsp tbs),
|
||||
'[tsp_us]': %w(teaspoon teaspoons tsp),
|
||||
'[oz_av]': %w(ounce ounces oz),
|
||||
'[lb_av]': %w(pound pounds lb lbs),
|
||||
'[pt_us]': %w(pint pints),
|
||||
'[qt_us]': %w(quart quarts qt),
|
||||
'[gal_us]': %w(gallon gallons ga),
|
||||
'[foz_us]': %w(foz floz),
|
||||
|
||||
g: %w(gram grams),
|
||||
kg: %w(kilograms kilogram),
|
||||
ml: %w(milliliter milliliters),
|
||||
l: %w(liter liters)
|
||||
}
|
||||
|
||||
UNIT_ALIAS_MAP = Hash[UNIT_ALIASES.map { |unit, values| values.map { |v| [v, unit] } }.flatten(1)]
|
||||
|
||||
# map that gives comfortable ranges for a given set of units
|
||||
UNIT_RANGES = {
|
||||
'[tsp_us]': (0.125...3.0),
|
||||
'[tbs_us]': (0.5...4.0),
|
||||
'[cup_us]': (0.25...4.0),
|
||||
'[qt_us]': (1.0...4.0),
|
||||
'[gal_us]': (0.5..9999),
|
||||
|
||||
'[oz_av]': (0..16),
|
||||
'[lb_av]': (1.0..9999),
|
||||
|
||||
g: (1.0..750),
|
||||
kg: (0.5..9999),
|
||||
|
||||
ml: (1.0..750),
|
||||
l: (0.5..9999)
|
||||
}
|
||||
|
||||
UNIT_ORDERS = {
|
||||
standard_volume: [
|
||||
:'[tsp_us]',
|
||||
:'[tbs_us]',
|
||||
:'[cup_us]',
|
||||
:'[qt_us]',
|
||||
:'[gal_us]'
|
||||
],
|
||||
|
||||
metric_volume: [
|
||||
:ml,
|
||||
:l
|
||||
],
|
||||
|
||||
standard_mass: [
|
||||
:'[oz_av]',
|
||||
:'[lb_av]'
|
||||
],
|
||||
|
||||
metric_mass: [
|
||||
:g,
|
||||
:kg
|
||||
]
|
||||
}
|
||||
|
||||
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
|
||||
value = get_value(match[:value])
|
||||
Unitwise(value, normalize_unit_names(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
|
||||
|
||||
def volume?(unit)
|
||||
unit.compatible_with? Unitwise(1, 'ml')
|
||||
end
|
||||
|
||||
def mass?(unit)
|
||||
unit.compatible_with? Unitwise(1, 'g')
|
||||
end
|
||||
|
||||
# Returns a Unitwise representation of the density. Raises an exception if the value is anything other than a
|
||||
# valid density
|
||||
def get_density(str)
|
||||
raise UnknownUnitError, 'No density provided' if str.blank?
|
||||
begin
|
||||
unit = parse(str)
|
||||
rescue UnparseableUnitError => err
|
||||
raise UnknownUnitError "Invalid density: #{err.message}"
|
||||
end
|
||||
|
||||
raise UnknownUnitError, "Invalid density: #{str} is not a density" unless density?(unit)
|
||||
unit
|
||||
end
|
||||
|
||||
def normalize_unit_names(unit_description)
|
||||
return unit_description.to_sym if UNIT_ALIASES.include?(unit_description.to_sym)
|
||||
unit_description.downcase.gsub(/[a-z]+/) do |match|
|
||||
UNIT_ALIAS_MAP[match] || match
|
||||
end
|
||||
end
|
||||
|
||||
def get_value(str)
|
||||
if str =~ /^#{INTEGER_REGEX}$/
|
||||
str.to_i
|
||||
elsif str =~ /^#{RATIONAL_REGEX}$/
|
||||
parts = str.split(' ')
|
||||
if parts.length == 2
|
||||
whole = parts.first.to_r
|
||||
fractional = parts.last.to_r
|
||||
(whole.abs + fractional) * (whole < 0 ? -1.to_r : 1.to_r)
|
||||
else
|
||||
str.to_r
|
||||
end
|
||||
elsif str =~ /^#{DECIMAL_REGEX}$/
|
||||
str.to_d
|
||||
else
|
||||
raise UnparseableUnitError, "str (#{str}) is not a valid number"
|
||||
end
|
||||
end
|
||||
|
||||
def convert(quantity, factor, input_unit, output_unit, density = nil)
|
||||
|
||||
value = get_value(quantity)
|
||||
factor = get_value(factor)
|
||||
|
||||
if value.is_a?(BigDecimal) && factor.is_a?(Rational)
|
||||
factor = factor.to_d(10)
|
||||
end
|
||||
|
||||
converted = value * factor
|
||||
|
||||
input_unit = normalize_unit_names(input_unit) unless input_unit.nil?
|
||||
output_unit = normalize_unit_names(output_unit) unless output_unit.nil?
|
||||
|
||||
if input_unit.present? && output_unit.present? && input_unit != output_unit
|
||||
in_unit = Unitwise(1, input_unit)
|
||||
out_unit = Unitwise(1, output_unit)
|
||||
unit = Unitwise(converted, input_unit)
|
||||
|
||||
if volume?(in_unit) && mass?(out_unit)
|
||||
unit_density = get_density(density)
|
||||
unit = unit * unit_density
|
||||
elsif mass?(in_unit) && volume?(out_unit)
|
||||
unit_density = get_density(density)
|
||||
unit = unit / unit_density
|
||||
end
|
||||
|
||||
converted = unit.convert_to(output_unit).value
|
||||
end
|
||||
|
||||
quantity_format(converted, value)
|
||||
end
|
||||
|
||||
def quantity_format(quantity, original_quantity)
|
||||
if original_quantity.is_a?(Rational) || original_quantity.is_a?(Integer)
|
||||
rational_val = rationalize(quantity)
|
||||
if rational_val.denominator == 1
|
||||
rational_val.to_i.to_s
|
||||
elsif rational_val.denominator < rational_val.numerator.abs
|
||||
whole = rational_val.floor
|
||||
fraction = rational_val - whole
|
||||
"#{whole} #{fraction}"
|
||||
else
|
||||
rational_val.to_s
|
||||
end
|
||||
else
|
||||
"%g" % ("%.3f" % quantity)
|
||||
end
|
||||
end
|
||||
|
||||
def rationalize(value)
|
||||
if value.is_a? Integer
|
||||
return value
|
||||
end
|
||||
|
||||
if value.floor == value
|
||||
return value
|
||||
end
|
||||
|
||||
useful_denominators = [2, 3, 4, 8, 16]
|
||||
|
||||
if value.is_a?(Rational) && useful_denominators.include?(value.denominator)
|
||||
return value
|
||||
end
|
||||
|
||||
whole = value.floor
|
||||
fraction = value - whole
|
||||
|
||||
approximations = useful_denominators.map do |d|
|
||||
best_n = (1...d).each.map { |n| {delta: (fraction - Rational(n, d)).abs, numerator: n} }.sort { |a, b| a[:delta] <=> b[:delta] }.first
|
||||
{denominator: d, numerator: best_n[:numerator], delta: best_n[:delta]}
|
||||
end
|
||||
|
||||
# Add 0 and 1
|
||||
approximations << { denominator: 1, numerator: 0, delta: (fraction - Rational(0,1)).abs }
|
||||
approximations << { denominator: 1, numerator: 1, delta: (fraction - Rational(1,1)).abs }
|
||||
|
||||
best = approximations.sort { |a, b| [a[:delta], a[:denominator]] <=> [b[:delta], b[:denominator]] }.first
|
||||
|
||||
Rational((whole * best[:denominator]) + best[:numerator], best[:denominator])
|
||||
end
|
||||
|
||||
# Given an awkward measurement such as '18 9/10 oz' or '5/24 cup' or '0.09434 cup',
|
||||
# it will round the rational and scale the unit to give a more reasonable, useful measurement, ie
|
||||
# 19 oz, 3 Tbsp, 1 1/2 Tbsp
|
||||
def auto_unit(quantity, units)
|
||||
normalized_unit = normalize_unit_names(units)
|
||||
value = initial_value = get_value(quantity)
|
||||
type_key, unit_orders = UNIT_ORDERS.detect { |key, orders| orders.include?(normalized_unit.to_sym) }
|
||||
new_unit = normalized_unit
|
||||
|
||||
if unit_orders && (unit_range = UNIT_RANGES[normalized_unit.to_sym])
|
||||
|
||||
if value < unit_range.first
|
||||
unit_orders = unit_orders.reverse
|
||||
end
|
||||
|
||||
idx = unit_orders.index(new_unit.to_sym)
|
||||
|
||||
while !unit_range.include?(value) && idx < (unit_orders.length - 1)
|
||||
idx += 1
|
||||
next_unit = unit_orders[idx]
|
||||
value = Unitwise(value, new_unit).convert_to(next_unit).value.round(4)
|
||||
new_unit = next_unit.to_s
|
||||
unit_range = UNIT_RANGES[new_unit.to_sym]
|
||||
end
|
||||
end
|
||||
|
||||
if normalized_unit == new_unit
|
||||
new_unit = units
|
||||
else
|
||||
new_unit = (UNIT_ALIASES[new_unit.to_sym] || []).first || new_unit
|
||||
end
|
||||
|
||||
return quantity_format(value, initial_value), new_unit
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -22,5 +22,18 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div id="errors_panel" class="panel panel-danger" style="display: none;">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Error</h3>
|
||||
</div>
|
||||
<div id="errors_container" class="panel-body">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
2
config/initializers/lib.rb
Normal file
2
config/initializers/lib.rb
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
require 'unit_conversion'
|
36
lib/unit_conversion.rb
Normal file
36
lib/unit_conversion.rb
Normal file
@ -0,0 +1,36 @@
|
||||
require 'unit_conversion/constants'
|
||||
require 'unit_conversion/errors'
|
||||
require 'unit_conversion/formatters'
|
||||
require 'unit_conversion/parsed_number'
|
||||
require 'unit_conversion/parsed_unit'
|
||||
require 'unit_conversion/conversions'
|
||||
require 'unit_conversion/value_unit'
|
||||
|
||||
module UnitConversion
|
||||
class << self
|
||||
|
||||
def parse(value_string, unit_string = nil)
|
||||
ValueUnit.for(value_string, unit_string)
|
||||
end
|
||||
|
||||
def convert(quantity, factor, input_unit, output_unit, density = nil)
|
||||
unit_value = parse(quantity, input_unit).scale(factor)
|
||||
|
||||
if output_unit.present?
|
||||
unit_value = unit_value.convert(output_unit, density)
|
||||
end
|
||||
|
||||
unit_value.pretty_value
|
||||
end
|
||||
|
||||
# Given an awkward measurement such as '18 9/10 oz' or '5/24 cup' or '0.09434 cup',
|
||||
# it will round the rational and scale the unit to give a more reasonable, useful measurement, ie
|
||||
# 19 oz, 3 Tbsp, 1 1/2 Tbsp
|
||||
def auto_unit(quantity, units)
|
||||
unit = parse(quantity, units).auto_unit
|
||||
|
||||
[unit.pretty_value, unit.unit.to_s]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
79
lib/unit_conversion/constants.rb
Normal file
79
lib/unit_conversion/constants.rb
Normal file
@ -0,0 +1,79 @@
|
||||
module UnitConversion
|
||||
INTEGER_REGEX = /-?[0-9]+/
|
||||
DECIMAL_REGEX = /(?:-?[0-9]+)?\.[0-9]+/
|
||||
RATIONAL_REGEX = /-?(?:[0-9]+ )?[0-9]+\/[0-9]+/
|
||||
UNIT_REGEX = /[\[\]a-zA-Z][\[\]a-zA-Z\/.\-()0-9 ]*/
|
||||
UNIT_PARSING_REGEX = /^(?<value>#{INTEGER_REGEX}|#{DECIMAL_REGEX}|#{RATIONAL_REGEX})(?:\s+(?<unit>#{UNIT_REGEX}))?$/
|
||||
|
||||
STANDARD_UNIT_ALIASES = {
|
||||
'[cup_us]': %w(cup cups c),
|
||||
'[tbs_us]': %w(tablespoon tablespoons tbsp tbs),
|
||||
'[tsp_us]': %w(teaspoon teaspoons tsp),
|
||||
'[oz_av]': %w(ounce ounces oz),
|
||||
'[lb_av]': %w(pound pounds lb lbs),
|
||||
'[pt_us]': %w(pint pints),
|
||||
'[qt_us]': %w(quart quarts qt),
|
||||
'[gal_us]': %w(gallon gallons ga),
|
||||
'[foz_us]': %w(foz floz),
|
||||
}
|
||||
|
||||
METRIC_UNIT_ALIASES = {
|
||||
g: %w(gram grams),
|
||||
kg: %w(kilograms kilogram),
|
||||
|
||||
ml: %w(milliliter milliliters),
|
||||
cl: %w(centiliter centiliters),
|
||||
dl: %w(deciliter deciliters),
|
||||
l: %w(liter liters),
|
||||
|
||||
m: %w(meter meters),
|
||||
cm: %w(centimeter centimeters)
|
||||
}
|
||||
|
||||
UNIT_ALIASES = STANDARD_UNIT_ALIASES.merge(METRIC_UNIT_ALIASES)
|
||||
|
||||
UNIT_ALIAS_MAP = Hash[UNIT_ALIASES.map { |unit, values| values.map { |v| [v, unit] } }.flatten(1)]
|
||||
|
||||
# map that gives comfortable ranges for a given set of units
|
||||
UNIT_RANGES = {
|
||||
'[tsp_us]': (0.125...3.0),
|
||||
'[tbs_us]': (0.5...4.0),
|
||||
'[cup_us]': (0.25...4.0),
|
||||
'[qt_us]': (1.0..3.9),
|
||||
'[gal_us]': (0.5..9999),
|
||||
|
||||
'[oz_av]': (0..16),
|
||||
'[lb_av]': (1.0..9999),
|
||||
|
||||
g: (1.0..750),
|
||||
kg: (0.5..9999),
|
||||
|
||||
ml: (1.0..750),
|
||||
l: (0.5..9999)
|
||||
}
|
||||
|
||||
UNIT_ORDERS = {
|
||||
standard_volume: [
|
||||
:'[tsp_us]',
|
||||
:'[tbs_us]',
|
||||
:'[cup_us]',
|
||||
:'[qt_us]',
|
||||
:'[gal_us]'
|
||||
],
|
||||
|
||||
metric_volume: [
|
||||
:ml,
|
||||
:l
|
||||
],
|
||||
|
||||
standard_mass: [
|
||||
:'[oz_av]',
|
||||
:'[lb_av]'
|
||||
],
|
||||
|
||||
metric_mass: [
|
||||
:g,
|
||||
:kg
|
||||
]
|
||||
}
|
||||
end
|
83
lib/unit_conversion/conversions.rb
Normal file
83
lib/unit_conversion/conversions.rb
Normal file
@ -0,0 +1,83 @@
|
||||
module UnitConversion
|
||||
|
||||
class Conversion
|
||||
end
|
||||
|
||||
class ScaleConversion < Conversion
|
||||
def initialize(parsed_factor)
|
||||
@factor = parsed_factor
|
||||
end
|
||||
|
||||
def convert(value_unit)
|
||||
value = @factor.value * value_unit.value.value
|
||||
ValueUnit.for(value, value_unit.unit, value_unit.formatter)
|
||||
end
|
||||
end
|
||||
|
||||
class ConvertConversion < Conversion
|
||||
def initialize(parsed_unit, density_unit_value = nil)
|
||||
@target_unit = parsed_unit
|
||||
|
||||
raise UnknownUnitError, "#{density_unit_value} is not a density" if density_unit_value && !density_unit_value.density?
|
||||
@density = density_unit_value
|
||||
end
|
||||
|
||||
def convert(value_unit)
|
||||
|
||||
input = value_unit.unitwise
|
||||
|
||||
if value_unit.volume? && @target_unit.mass?
|
||||
raise MissingDensityError, "Cannot convert #{value_unit.unit} to #{@target_unit} without density" unless @density
|
||||
input = input * @density.unitwise
|
||||
elsif value_unit.mass? && @target_unit.volume?
|
||||
raise MissingDensityError, "Cannot convert #{value_unit.unit} to #{@target_unit} without density" unless @density
|
||||
input = input / @density.unitwise
|
||||
end
|
||||
|
||||
input = input.convert_to @target_unit.unit
|
||||
|
||||
formatter = @target_unit.metric? ? DecimalFormatter.new : value_unit.formatter
|
||||
|
||||
ValueUnit.for(input.value, @target_unit, formatter)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
class AutoUnitConversion < Conversion
|
||||
|
||||
def convert(value_unit)
|
||||
|
||||
unless known_auto_unit?(value_unit.unit)
|
||||
return value_unit
|
||||
end
|
||||
|
||||
value = value_unit.raw_value
|
||||
unit = value_unit.unit.unit
|
||||
new_unit = unit
|
||||
|
||||
unit_orders = UNIT_ORDERS.values.detect { |orders| orders.include?(unit.to_sym) }
|
||||
unit_range = UNIT_RANGES[unit.to_sym]
|
||||
|
||||
if value < unit_range.first
|
||||
unit_orders = unit_orders.reverse
|
||||
end
|
||||
|
||||
idx = unit_orders.index(new_unit.to_sym)
|
||||
|
||||
while !unit_range.include?(value) && idx < (unit_orders.length - 1)
|
||||
idx += 1
|
||||
next_unit = unit_orders[idx]
|
||||
value = Unitwise(value, new_unit).convert_to(next_unit).value
|
||||
new_unit = next_unit.to_s
|
||||
unit_range = UNIT_RANGES[new_unit.to_sym]
|
||||
end
|
||||
|
||||
ValueUnit.for(value, new_unit, value_unit.formatter)
|
||||
end
|
||||
|
||||
def known_auto_unit?(unit)
|
||||
unit && UNIT_ORDERS.values.flatten.include?(unit.unit.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
10
lib/unit_conversion/errors.rb
Normal file
10
lib/unit_conversion/errors.rb
Normal file
@ -0,0 +1,10 @@
|
||||
module UnitConversion
|
||||
class UnparseableUnitError < StandardError
|
||||
end
|
||||
|
||||
class UnknownUnitError < UnparseableUnitError
|
||||
end
|
||||
|
||||
class MissingDensityError < UnparseableUnitError
|
||||
end
|
||||
end
|
75
lib/unit_conversion/formatters.rb
Normal file
75
lib/unit_conversion/formatters.rb
Normal file
@ -0,0 +1,75 @@
|
||||
module UnitConversion
|
||||
|
||||
class NumberFormatter
|
||||
|
||||
def self.for(parsed_value, parsed_unit)
|
||||
case
|
||||
when parsed_unit && parsed_unit.metric?
|
||||
DecimalFormatter
|
||||
when parsed_value.rational?
|
||||
RationalFormatter
|
||||
else
|
||||
DecimalFormatter
|
||||
end.new
|
||||
|
||||
end
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
# Converts a Numeric into a cooking-friendly Rational
|
||||
def rationalize(value)
|
||||
|
||||
# NOTE: this algorithm seems stupid.
|
||||
|
||||
if value.floor == value
|
||||
return value
|
||||
end
|
||||
|
||||
useful_denominators = [2, 3, 4, 8, 16]
|
||||
|
||||
if value.is_a?(Rational) && useful_denominators.include?(value.denominator)
|
||||
return value
|
||||
end
|
||||
|
||||
whole = value.floor
|
||||
fraction = value - whole
|
||||
|
||||
approximations = useful_denominators.map do |d|
|
||||
best_n = (1...d).each.map { |n| {delta: (fraction - Rational(n, d)).abs, numerator: n} }.sort { |a, b| a[:delta] <=> b[:delta] }.first
|
||||
{denominator: d, numerator: best_n[:numerator], delta: best_n[:delta]}
|
||||
end
|
||||
|
||||
# Add 0 and 1
|
||||
approximations << { denominator: 1, numerator: 0, delta: (fraction - Rational(0,1)).abs }
|
||||
approximations << { denominator: 1, numerator: 1, delta: (fraction - Rational(1,1)).abs }
|
||||
|
||||
best = approximations.sort { |a, b| [a[:delta], a[:denominator]] <=> [b[:delta], b[:denominator]] }.first
|
||||
|
||||
Rational((whole * best[:denominator]) + best[:numerator], best[:denominator])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class RationalFormatter < NumberFormatter
|
||||
def format(value)
|
||||
rational_val = rationalize(value)
|
||||
if rational_val.denominator == 1
|
||||
rational_val.to_i.to_s
|
||||
elsif rational_val.denominator < rational_val.numerator.abs
|
||||
whole = rational_val.floor
|
||||
fraction = rational_val - whole
|
||||
"#{whole} #{fraction}"
|
||||
else
|
||||
rational_val.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DecimalFormatter < NumberFormatter
|
||||
def format(value)
|
||||
"%g" % ("%.3f" % value)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
38
lib/unit_conversion/parsed_number.rb
Normal file
38
lib/unit_conversion/parsed_number.rb
Normal file
@ -0,0 +1,38 @@
|
||||
module UnitConversion
|
||||
class ParsedNumber
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def initialize(str)
|
||||
@original_value = str
|
||||
@value = str.is_a?(Numeric) ? str : parse_value(str)
|
||||
end
|
||||
|
||||
def to_s
|
||||
@value.to_s
|
||||
end
|
||||
|
||||
def rational?
|
||||
value.is_a?(Integer) || value.is_a?(Rational)
|
||||
end
|
||||
|
||||
def parse_value(str)
|
||||
if str =~ /^#{INTEGER_REGEX}$/
|
||||
str.to_i
|
||||
elsif str =~ /^#{RATIONAL_REGEX}$/
|
||||
parts = str.split(' ')
|
||||
if parts.length == 2
|
||||
whole = parts.first.to_r
|
||||
fractional = parts.last.to_r
|
||||
(whole.abs + fractional) * (whole < 0 ? -1.to_r : 1.to_r)
|
||||
else
|
||||
str.to_r
|
||||
end
|
||||
elsif str =~ /^#{DECIMAL_REGEX}$/
|
||||
str.to_d
|
||||
else
|
||||
raise UnparseableUnitError, "str [#{str}] is not a valid number"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
64
lib/unit_conversion/parsed_unit.rb
Normal file
64
lib/unit_conversion/parsed_unit.rb
Normal file
@ -0,0 +1,64 @@
|
||||
module UnitConversion
|
||||
class ParsedUnit
|
||||
|
||||
attr_reader :original_unit, :unit
|
||||
|
||||
def initialize(str)
|
||||
@original_unit = str
|
||||
@unit = normalize_unit_names(str)
|
||||
end
|
||||
|
||||
def to_s
|
||||
unit.gsub(/[a-z_\[\]]+/) do |match|
|
||||
if aliases = UNIT_ALIASES[match.to_sym]
|
||||
aliases.first
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def density?
|
||||
compatible? 'g/ml'
|
||||
end
|
||||
|
||||
def volume?
|
||||
compatible? 'ml'
|
||||
end
|
||||
|
||||
def mass?
|
||||
compatible? 'g'
|
||||
end
|
||||
|
||||
def metric?
|
||||
METRIC_UNIT_ALIASES.keys.include? unit.to_sym
|
||||
end
|
||||
|
||||
def unitwise(value = 1)
|
||||
begin
|
||||
Unitwise(value, unit)
|
||||
rescue Unitwise::ExpressionError => err
|
||||
raise UnknownUnitError, err.message
|
||||
end
|
||||
end
|
||||
|
||||
def compatible?(unit_str)
|
||||
begin
|
||||
unitwise.compatible_with? Unitwise(1, unit_str)
|
||||
rescue UnknownUnitError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_unit_names(unit_description)
|
||||
return unit_description.to_s if UNIT_ALIASES.include?(unit_description.to_sym)
|
||||
|
||||
# fluid ounce is the only known unit with a space; fix it up before the following replacement
|
||||
unit_description = unit_description.gsub(/fl oz/i, 'floz')
|
||||
|
||||
unit_description.downcase.gsub(/[a-z_\[\]]+/) do |match|
|
||||
UNIT_ALIAS_MAP[match] || match
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
151
lib/unit_conversion/value_unit.rb
Normal file
151
lib/unit_conversion/value_unit.rb
Normal file
@ -0,0 +1,151 @@
|
||||
module UnitConversion
|
||||
class ValueUnit
|
||||
|
||||
def self.for(value_string, unit_string = nil, formatter = nil)
|
||||
raise UnparseableUnitError, "value is empty" if value_string.blank?
|
||||
|
||||
if String === value_string && unit_string.nil?
|
||||
value_string, unit_string = parse_single_string(value_string)
|
||||
end
|
||||
|
||||
if value_string.is_a?(ParsedNumber)
|
||||
value = value_string
|
||||
else
|
||||
value = ParsedNumber.new(value_string)
|
||||
end
|
||||
|
||||
unit = case unit_string
|
||||
when nil
|
||||
nil
|
||||
when ->(u) { u.blank? }
|
||||
nil
|
||||
when ParsedUnit
|
||||
unit_string
|
||||
else
|
||||
ParsedUnit.new(unit_string)
|
||||
end
|
||||
|
||||
if unit.nil?
|
||||
ValueNoUnit.new(value, formatter)
|
||||
else
|
||||
ValueUnit.new(value, unit, formatter)
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_single_string(value_unit_string)
|
||||
match = UNIT_PARSING_REGEX.match(value_unit_string.to_s.strip)
|
||||
|
||||
if match && match[:value].present?
|
||||
return match[:value], match[:unit]
|
||||
else
|
||||
raise UnparseableUnitError, "'#{value_unit_string}' does not appear to be a valid measurement of the form <value> <units> (ie '5 cup' or '223 gram/cup')"
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :formatter
|
||||
|
||||
def initialize(value, unit, formatter = nil)
|
||||
@value = value
|
||||
@unit = unit
|
||||
@formatter = formatter || NumberFormatter.for(value, unit)
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{pretty_value} #{unit}"
|
||||
end
|
||||
|
||||
def unit
|
||||
@unit
|
||||
end
|
||||
|
||||
def value
|
||||
@value
|
||||
end
|
||||
|
||||
def raw_value
|
||||
@value.value
|
||||
end
|
||||
|
||||
def unitwise
|
||||
unit.unitwise(value.value)
|
||||
end
|
||||
|
||||
# Returns a new ValueUnit scaled by the given factor
|
||||
def scale(factor)
|
||||
if factor.present?
|
||||
parsed_factor = ParsedNumber.new(factor)
|
||||
ScaleConversion.new(parsed_factor).convert(self)
|
||||
else
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a new ValueUnit with the given new_parsed_unit. If converting between mass and volume,
|
||||
# also requires a density UnitValue
|
||||
def convert(new_unit, density = nil)
|
||||
new_parsed_unit = ParsedUnit.new(new_unit)
|
||||
parsed_density = density ? ValueUnit.for(density) : nil
|
||||
|
||||
if new_parsed_unit.unit != self.unit.unit
|
||||
ConvertConversion.new(new_parsed_unit, parsed_density).convert(self)
|
||||
else
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
def auto_unit
|
||||
AutoUnitConversion.new.convert(self)
|
||||
end
|
||||
|
||||
def density?
|
||||
unit.density?
|
||||
end
|
||||
|
||||
def volume?
|
||||
unit.volume?
|
||||
end
|
||||
|
||||
def mass?
|
||||
unit.mass?
|
||||
end
|
||||
|
||||
def pretty_value
|
||||
formatter.format(value.value)
|
||||
end
|
||||
end
|
||||
|
||||
class ValueNoUnit < ValueUnit
|
||||
def initialize(value, formatter = nil)
|
||||
@value = value
|
||||
@formatter = formatter || NumberFormatter.for(value, unit)
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{pretty_value}"
|
||||
end
|
||||
|
||||
def unit
|
||||
nil
|
||||
end
|
||||
|
||||
def unitwise
|
||||
raise UnknownUnitError, "No unit value provided"
|
||||
end
|
||||
|
||||
def convert(new_parsed_unit, density_unit_value = nil)
|
||||
raise UnknownUnitError, "No unit value provided"
|
||||
end
|
||||
|
||||
def density?
|
||||
false
|
||||
end
|
||||
|
||||
def volume?
|
||||
false
|
||||
end
|
||||
|
||||
def mass?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
@ -1,159 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# This spec was generated by rspec-rails when you ran the scaffold generator.
|
||||
# It demonstrates how one might use RSpec to specify the controller code that
|
||||
# was generated by Rails when you ran the scaffold generator.
|
||||
#
|
||||
# It assumes that the implementation code is generated by the rails scaffold
|
||||
# generator. If you are using any extension libraries to generate different
|
||||
# controller code, this generated spec may or may not pass.
|
||||
#
|
||||
# It only uses APIs available in rails and/or rspec-rails. There are a number
|
||||
# of tools you can use to make these specs even more expressive, but we're
|
||||
# sticking to rails and rspec-rails APIs to keep things simple and stable.
|
||||
#
|
||||
# Compared to earlier versions of this generator, there is very limited use of
|
||||
# stubs and message expectations in this spec. Stubs are only used when there
|
||||
# is no simpler way to get a handle on the object needed for the example.
|
||||
# Message expectations are only used when there is no simpler way to specify
|
||||
# that an instance is receiving a specific message.
|
||||
|
||||
RSpec.describe IngredientsController, type: :controller do
|
||||
|
||||
# This should return the minimal set of attributes required to create a valid
|
||||
# Ingredient. As you add validations to Ingredient, be sure to
|
||||
# adjust the attributes here as well.
|
||||
let(:valid_attributes) {
|
||||
skip("Add a hash of attributes valid for your model")
|
||||
}
|
||||
|
||||
let(:invalid_attributes) {
|
||||
skip("Add a hash of attributes invalid for your model")
|
||||
}
|
||||
|
||||
# This should return the minimal set of values that should be in the session
|
||||
# in order to pass any filters (e.g. authentication) defined in
|
||||
# IngredientsController. Be sure to keep this updated too.
|
||||
let(:valid_session) { {} }
|
||||
|
||||
describe "GET #index" do
|
||||
it "assigns all ingredients as @ingredients" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
get :index, {}, valid_session
|
||||
expect(assigns(:ingredients)).to eq([ingredient])
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show" do
|
||||
it "assigns the requested ingredient as @ingredient" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
get :show, {:id => ingredient.to_param}, valid_session
|
||||
expect(assigns(:ingredient)).to eq(ingredient)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #new" do
|
||||
it "assigns a new ingredient as @ingredient" do
|
||||
get :new, {}, valid_session
|
||||
expect(assigns(:ingredient)).to be_a_new(Ingredient)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #edit" do
|
||||
it "assigns the requested ingredient as @ingredient" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
get :edit, {:id => ingredient.to_param}, valid_session
|
||||
expect(assigns(:ingredient)).to eq(ingredient)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST #create" do
|
||||
context "with valid params" do
|
||||
it "creates a new Ingredient" do
|
||||
expect {
|
||||
post :create, {:ingredient => valid_attributes}, valid_session
|
||||
}.to change(Ingredient, :count).by(1)
|
||||
end
|
||||
|
||||
it "assigns a newly created ingredient as @ingredient" do
|
||||
post :create, {:ingredient => valid_attributes}, valid_session
|
||||
expect(assigns(:ingredient)).to be_a(Ingredient)
|
||||
expect(assigns(:ingredient)).to be_persisted
|
||||
end
|
||||
|
||||
it "redirects to the created ingredient" do
|
||||
post :create, {:ingredient => valid_attributes}, valid_session
|
||||
expect(response).to redirect_to(Ingredient.last)
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid params" do
|
||||
it "assigns a newly created but unsaved ingredient as @ingredient" do
|
||||
post :create, {:ingredient => invalid_attributes}, valid_session
|
||||
expect(assigns(:ingredient)).to be_a_new(Ingredient)
|
||||
end
|
||||
|
||||
it "re-renders the 'new' template" do
|
||||
post :create, {:ingredient => invalid_attributes}, valid_session
|
||||
expect(response).to render_template("new")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT #update" do
|
||||
context "with valid params" do
|
||||
let(:new_attributes) {
|
||||
skip("Add a hash of attributes valid for your model")
|
||||
}
|
||||
|
||||
it "updates the requested ingredient" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
put :update, {:id => ingredient.to_param, :ingredient => new_attributes}, valid_session
|
||||
ingredient.reload
|
||||
skip("Add assertions for updated state")
|
||||
end
|
||||
|
||||
it "assigns the requested ingredient as @ingredient" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
put :update, {:id => ingredient.to_param, :ingredient => valid_attributes}, valid_session
|
||||
expect(assigns(:ingredient)).to eq(ingredient)
|
||||
end
|
||||
|
||||
it "redirects to the ingredient" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
put :update, {:id => ingredient.to_param, :ingredient => valid_attributes}, valid_session
|
||||
expect(response).to redirect_to(ingredient)
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid params" do
|
||||
it "assigns the ingredient as @ingredient" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
put :update, {:id => ingredient.to_param, :ingredient => invalid_attributes}, valid_session
|
||||
expect(assigns(:ingredient)).to eq(ingredient)
|
||||
end
|
||||
|
||||
it "re-renders the 'edit' template" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
put :update, {:id => ingredient.to_param, :ingredient => invalid_attributes}, valid_session
|
||||
expect(response).to render_template("edit")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE #destroy" do
|
||||
it "destroys the requested ingredient" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
expect {
|
||||
delete :destroy, {:id => ingredient.to_param}, valid_session
|
||||
}.to change(Ingredient, :count).by(-1)
|
||||
end
|
||||
|
||||
it "redirects to the ingredients list" do
|
||||
ingredient = Ingredient.create! valid_attributes
|
||||
delete :destroy, {:id => ingredient.to_param}, valid_session
|
||||
expect(response).to redirect_to(ingredients_url)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -1,159 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# This spec was generated by rspec-rails when you ran the scaffold generator.
|
||||
# It demonstrates how one might use RSpec to specify the controller code that
|
||||
# was generated by Rails when you ran the scaffold generator.
|
||||
#
|
||||
# It assumes that the implementation code is generated by the rails scaffold
|
||||
# generator. If you are using any extension libraries to generate different
|
||||
# controller code, this generated spec may or may not pass.
|
||||
#
|
||||
# It only uses APIs available in rails and/or rspec-rails. There are a number
|
||||
# of tools you can use to make these specs even more expressive, but we're
|
||||
# sticking to rails and rspec-rails APIs to keep things simple and stable.
|
||||
#
|
||||
# Compared to earlier versions of this generator, there is very limited use of
|
||||
# stubs and message expectations in this spec. Stubs are only used when there
|
||||
# is no simpler way to get a handle on the object needed for the example.
|
||||
# Message expectations are only used when there is no simpler way to specify
|
||||
# that an instance is receiving a specific message.
|
||||
|
||||
RSpec.describe RecipesController, type: :controller do
|
||||
|
||||
# This should return the minimal set of attributes required to create a valid
|
||||
# Recipe. As you add validations to Recipe, be sure to
|
||||
# adjust the attributes here as well.
|
||||
let(:valid_attributes) {
|
||||
skip("Add a hash of attributes valid for your model")
|
||||
}
|
||||
|
||||
let(:invalid_attributes) {
|
||||
skip("Add a hash of attributes invalid for your model")
|
||||
}
|
||||
|
||||
# This should return the minimal set of values that should be in the session
|
||||
# in order to pass any filters (e.g. authentication) defined in
|
||||
# RecipesController. Be sure to keep this updated too.
|
||||
let(:valid_session) { {} }
|
||||
|
||||
describe "GET #index" do
|
||||
it "assigns all recipes as @recipes" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
get :index, {}, valid_session
|
||||
expect(assigns(:recipes)).to eq([recipe])
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show" do
|
||||
it "assigns the requested recipe as @recipe" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
get :show, {:id => recipe.to_param}, valid_session
|
||||
expect(assigns(:recipe)).to eq(recipe)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #new" do
|
||||
it "assigns a new recipe as @recipe" do
|
||||
get :new, {}, valid_session
|
||||
expect(assigns(:recipe)).to be_a_new(Recipe)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #edit" do
|
||||
it "assigns the requested recipe as @recipe" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
get :edit, {:id => recipe.to_param}, valid_session
|
||||
expect(assigns(:recipe)).to eq(recipe)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST #create" do
|
||||
context "with valid params" do
|
||||
it "creates a new Recipe" do
|
||||
expect {
|
||||
post :create, {:recipe => valid_attributes}, valid_session
|
||||
}.to change(Recipe, :count).by(1)
|
||||
end
|
||||
|
||||
it "assigns a newly created recipe as @recipe" do
|
||||
post :create, {:recipe => valid_attributes}, valid_session
|
||||
expect(assigns(:recipe)).to be_a(Recipe)
|
||||
expect(assigns(:recipe)).to be_persisted
|
||||
end
|
||||
|
||||
it "redirects to the created recipe" do
|
||||
post :create, {:recipe => valid_attributes}, valid_session
|
||||
expect(response).to redirect_to(Recipe.last)
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid params" do
|
||||
it "assigns a newly created but unsaved recipe as @recipe" do
|
||||
post :create, {:recipe => invalid_attributes}, valid_session
|
||||
expect(assigns(:recipe)).to be_a_new(Recipe)
|
||||
end
|
||||
|
||||
it "re-renders the 'new' template" do
|
||||
post :create, {:recipe => invalid_attributes}, valid_session
|
||||
expect(response).to render_template("new")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT #update" do
|
||||
context "with valid params" do
|
||||
let(:new_attributes) {
|
||||
skip("Add a hash of attributes valid for your model")
|
||||
}
|
||||
|
||||
it "updates the requested recipe" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
put :update, {:id => recipe.to_param, :recipe => new_attributes}, valid_session
|
||||
recipe.reload
|
||||
skip("Add assertions for updated state")
|
||||
end
|
||||
|
||||
it "assigns the requested recipe as @recipe" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
put :update, {:id => recipe.to_param, :recipe => valid_attributes}, valid_session
|
||||
expect(assigns(:recipe)).to eq(recipe)
|
||||
end
|
||||
|
||||
it "redirects to the recipe" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
put :update, {:id => recipe.to_param, :recipe => valid_attributes}, valid_session
|
||||
expect(response).to redirect_to(recipe)
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid params" do
|
||||
it "assigns the recipe as @recipe" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
put :update, {:id => recipe.to_param, :recipe => invalid_attributes}, valid_session
|
||||
expect(assigns(:recipe)).to eq(recipe)
|
||||
end
|
||||
|
||||
it "re-renders the 'edit' template" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
put :update, {:id => recipe.to_param, :recipe => invalid_attributes}, valid_session
|
||||
expect(response).to render_template("edit")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE #destroy" do
|
||||
it "destroys the requested recipe" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
expect {
|
||||
delete :destroy, {:id => recipe.to_param}, valid_session
|
||||
}.to change(Recipe, :count).by(-1)
|
||||
end
|
||||
|
||||
it "redirects to the recipes list" do
|
||||
recipe = Recipe.create! valid_attributes
|
||||
delete :destroy, {:id => recipe.to_param}, valid_session
|
||||
expect(response).to redirect_to(recipes_url)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -7,7 +7,7 @@ FactoryGirl.define do
|
||||
factory :usda_food do
|
||||
long_description 'Food'
|
||||
short_description 'Food'
|
||||
ndbn '01234'
|
||||
ndbn { generate :unique_ndbn }
|
||||
water 1.0
|
||||
kcal 101
|
||||
protein 1.2
|
||||
@ -23,4 +23,22 @@ FactoryGirl.define do
|
||||
refuse_percent 3
|
||||
end
|
||||
|
||||
factory :salted_butter, parent: :usda_food do
|
||||
long_description 'Butter, salted'
|
||||
short_description 'BUTTER,WITH SALT'
|
||||
water 15.87
|
||||
kcal 717
|
||||
protein 0.85
|
||||
lipid 81.11
|
||||
ash 2.11
|
||||
carbohydrates 0.06
|
||||
fiber 0
|
||||
sugar 0.06
|
||||
gram_weight_1 5.0
|
||||
gram_weight_2 14.2
|
||||
gram_weight_desc_1 '1 pat, (1\" sq, 1/3\" high)'
|
||||
gram_weight_desc_2 '1 tbsp'
|
||||
refuse_percent 0
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,15 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# Specs in this file have access to a helper object that includes
|
||||
# the IngredientsHelper. For example:
|
||||
#
|
||||
# describe IngredientsHelper do
|
||||
# describe "string concat" do
|
||||
# it "concats two strings with spaces" do
|
||||
# expect(helper.concat_strings("this","that")).to eq("this that")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
RSpec.describe IngredientsHelper, type: :helper do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,15 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# Specs in this file have access to a helper object that includes
|
||||
# the RecipesHelper. For example:
|
||||
#
|
||||
# describe RecipesHelper do
|
||||
# describe "string concat" do
|
||||
# it "concats two strings with spaces" do
|
||||
# expect(helper.concat_strings("this","that")).to eq("this that")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
RSpec.describe RecipesHelper, type: :helper do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
78
spec/lib/unit_conversion/conversions_spec.rb
Normal file
78
spec/lib/unit_conversion/conversions_spec.rb
Normal file
@ -0,0 +1,78 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UnitConversion::Conversion do
|
||||
|
||||
def get_value_unit(value, units)
|
||||
UnitConversion.parse(value, units)
|
||||
end
|
||||
|
||||
def get_number(number)
|
||||
UnitConversion::ParsedNumber.new(number)
|
||||
end
|
||||
|
||||
def get_unit(unit)
|
||||
UnitConversion::ParsedUnit.new(unit)
|
||||
end
|
||||
|
||||
describe UnitConversion::ScaleConversion do
|
||||
it 'scales' do
|
||||
expect(UnitConversion::ScaleConversion.new(get_number(2)).convert(get_value_unit(4, 'cups')).raw_value).to eq 8
|
||||
expect(UnitConversion::ScaleConversion.new(get_number('1/2')).convert(get_value_unit(4, 'cups')).raw_value).to eq 2
|
||||
end
|
||||
|
||||
it 'leaves the unit alone' do
|
||||
expect(UnitConversion::ScaleConversion.new(get_number(2)).convert(get_value_unit(4, 'cups')).unit.original_unit).to eq 'cups'
|
||||
expect(UnitConversion::ScaleConversion.new(get_number(2)).convert(get_value_unit(4, 'cats')).unit.original_unit).to eq 'cats'
|
||||
end
|
||||
end
|
||||
|
||||
describe UnitConversion::ConvertConversion do
|
||||
it 'converts standard units' do
|
||||
expect(UnitConversion::ConvertConversion.new(get_unit('tbsp')).convert(get_value_unit(1, 'cups')).raw_value).to eq 16
|
||||
expect(UnitConversion::ConvertConversion.new(get_unit('cups')).convert(get_value_unit(8, 'tbsp')).raw_value).to eq 0.5
|
||||
end
|
||||
|
||||
it 'takes on the new unit' do
|
||||
expect(UnitConversion::ConvertConversion.new(get_unit('tbsp')).convert(get_value_unit(1, 'cups')).unit.original_unit).to eq 'tbsp'
|
||||
expect(UnitConversion::ConvertConversion.new(get_unit('gallons')).convert(get_value_unit(1, 'cups')).unit.original_unit).to eq 'gallons'
|
||||
end
|
||||
|
||||
it 'converts from mass to volume' do
|
||||
expect(UnitConversion::ConvertConversion.new(get_unit('oz'), get_value_unit(5, 'oz/c')).convert(get_value_unit(2, 'cups')).raw_value).to be_within(0.01).of(10)
|
||||
end
|
||||
|
||||
it 'converts from volume to mass' do
|
||||
expect(UnitConversion::ConvertConversion.new(get_unit('c'), get_value_unit(5, 'oz/c')).convert(get_value_unit(10, 'oz')).raw_value).to be_within(0.01).of(2)
|
||||
end
|
||||
|
||||
it 'raises an error when attempting a mass/volume conversion without density' do
|
||||
expect { UnitConversion::ConvertConversion.new(get_unit('c')).convert(get_value_unit(10, 'oz')) }.to raise_error(UnitConversion::MissingDensityError)
|
||||
expect { UnitConversion::ConvertConversion.new(get_unit('oz')).convert(get_value_unit(10, 'c')) }.to raise_error(UnitConversion::MissingDensityError)
|
||||
end
|
||||
|
||||
it 'raises an error when density param is not a density' do
|
||||
expect { UnitConversion::ConvertConversion.new(get_unit('oz'), get_value_unit(2, 'g/oz')) }.to raise_error(UnitConversion::UnknownUnitError)
|
||||
expect { UnitConversion::ConvertConversion.new(get_unit('oz'), get_value_unit(2, 'cats')) }.to raise_error(UnitConversion::UnknownUnitError)
|
||||
expect { UnitConversion::ConvertConversion.new(get_unit('oz'), get_value_unit(2, 'g')) }.to raise_error(UnitConversion::UnknownUnitError)
|
||||
end
|
||||
|
||||
it 'preserves the formatter for standard units' do
|
||||
original = get_value_unit(10, 'tbsp')
|
||||
converted = UnitConversion::ConvertConversion.new(get_unit('c')).convert(original)
|
||||
expect(original.formatter).to be converted.formatter
|
||||
|
||||
original = get_value_unit("10.0", 'tbsp')
|
||||
converted = UnitConversion::ConvertConversion.new(get_unit('c')).convert(original)
|
||||
expect(original.formatter).to be converted.formatter
|
||||
end
|
||||
|
||||
it 'always sets the formatter to a DecimalFormatter for metric units' do
|
||||
original = get_value_unit(10, 'tbsp')
|
||||
expect(original.formatter).to be_a UnitConversion::RationalFormatter
|
||||
|
||||
converted = UnitConversion::ConvertConversion.new(get_unit('ml')).convert(original)
|
||||
expect(converted.formatter).to be_a UnitConversion::DecimalFormatter
|
||||
end
|
||||
end
|
||||
|
||||
end
|
82
spec/lib/unit_conversion/formatters_spec.rb
Normal file
82
spec/lib/unit_conversion/formatters_spec.rb
Normal file
@ -0,0 +1,82 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UnitConversion::NumberFormatter do
|
||||
|
||||
describe UnitConversion::RationalFormatter do
|
||||
|
||||
it 'formats integers' do
|
||||
expect(UnitConversion::RationalFormatter.new.format(1)).to eq '1'
|
||||
expect(UnitConversion::RationalFormatter.new.format(-1)).to eq '-1'
|
||||
expect(UnitConversion::RationalFormatter.new.format(1.0)).to eq '1'
|
||||
end
|
||||
|
||||
it 'formats rationals into rationals' do
|
||||
expect(UnitConversion::RationalFormatter.new.format(Rational(1,2))).to eq '1/2'
|
||||
expect(UnitConversion::RationalFormatter.new.format(Rational(5,16))).to eq '5/16'
|
||||
expect(UnitConversion::RationalFormatter.new.format(Rational(5, 4))).to eq '1 1/4'
|
||||
end
|
||||
|
||||
it 'rounds rationals into better rationals' do
|
||||
expect(UnitConversion::RationalFormatter.new.format(Rational(3,7))).to eq '7/16'
|
||||
end
|
||||
|
||||
it 'formats decimals into rationals' do
|
||||
expect(UnitConversion::RationalFormatter.new.format(1.5)).to eq '1 1/2'
|
||||
expect(UnitConversion::RationalFormatter.new.format(1.125)).to eq '1 1/8'
|
||||
expect(UnitConversion::RationalFormatter.new.format(24.38)).to eq '24 3/8'
|
||||
end
|
||||
end
|
||||
|
||||
describe UnitConversion::DecimalFormatter do
|
||||
it 'formats everything to a decimal' do
|
||||
expect(UnitConversion::DecimalFormatter.new.format(1)).to eq '1'
|
||||
expect(UnitConversion::DecimalFormatter.new.format(-1)).to eq '-1'
|
||||
expect(UnitConversion::DecimalFormatter.new.format(1.0)).to eq '1'
|
||||
expect(UnitConversion::DecimalFormatter.new.format(Rational(1,4))).to eq '0.25'
|
||||
expect(UnitConversion::DecimalFormatter.new.format("4.2899999999".to_d)).to eq '4.29'
|
||||
end
|
||||
end
|
||||
|
||||
describe '.rationalize' do
|
||||
it 'leaves integers alone' do
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(1)).to eq 1
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(15)).to eq 15
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(-1)).to eq -1
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(0)).to eq 0
|
||||
end
|
||||
|
||||
it 'leaves non-fractional numbers alone' do
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(1.0)).to eq 1.0
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(-1.0)).to eq -1.0
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(0.0)).to eq 0.0
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(35.0)).to eq 35.0
|
||||
end
|
||||
|
||||
it 'leaves already nice rationals alone' do
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(Rational(1,2))).to eq Rational(1,2)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(Rational(5,2))).to eq Rational(5,2)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(Rational(3,16))).to eq Rational(3,16)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(Rational(3,4))).to eq Rational(3,4)
|
||||
end
|
||||
|
||||
it 'converts neat decimals to rationals' do
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(1.5)).to eq Rational(3,2)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(0.125)).to eq Rational(1,8)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(5.0625)).to eq Rational(81, 16)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(0.75)).to eq Rational(3,4)
|
||||
end
|
||||
|
||||
it 'rounds weird rationals to nice rationals' do
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(Rational(3,7))).to eq Rational(7,16)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(Rational(2,5))).to eq Rational(3,8)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(Rational(2,5))).to eq Rational(3,8)
|
||||
end
|
||||
|
||||
it 'rounds weird decimals to nice rationals' do
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(0.24)).to eq Rational(1,4)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(1.24)).to eq Rational(5,4)
|
||||
expect(UnitConversion::NumberFormatter.new.rationalize(1.13)).to eq Rational(9,8)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
34
spec/lib/unit_conversion/parsed_number_spec.rb
Normal file
34
spec/lib/unit_conversion/parsed_number_spec.rb
Normal file
@ -0,0 +1,34 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UnitConversion::ParsedNumber do
|
||||
|
||||
it 'converts integers' do
|
||||
expect(UnitConversion::ParsedNumber.new('1').value).to eq 1
|
||||
expect(UnitConversion::ParsedNumber.new('-1').value).to eq -1
|
||||
expect(UnitConversion::ParsedNumber.new('0').value).to eq 0
|
||||
expect(UnitConversion::ParsedNumber.new('20').value).to eq 20
|
||||
expect(UnitConversion::ParsedNumber.new('103').value).to eq 103
|
||||
end
|
||||
|
||||
it 'converts decimal numbers' do
|
||||
expect(UnitConversion::ParsedNumber.new('1.0').value).to eq BigDecimal.new("1")
|
||||
expect(UnitConversion::ParsedNumber.new('-1.0').value).to eq BigDecimal.new("-1")
|
||||
expect(UnitConversion::ParsedNumber.new('54.33').value).to eq BigDecimal.new("54.33")
|
||||
expect(UnitConversion::ParsedNumber.new('-54.33').value).to eq BigDecimal.new("-54.33")
|
||||
expect(UnitConversion::ParsedNumber.new('.33').value).to eq BigDecimal.new("0.33")
|
||||
end
|
||||
|
||||
it 'converts simple fractions' do
|
||||
expect(UnitConversion::ParsedNumber.new('1/2').value).to eq Rational(1, 2)
|
||||
expect(UnitConversion::ParsedNumber.new('-1/2').value).to eq Rational(-1, 2)
|
||||
expect(UnitConversion::ParsedNumber.new('3/16').value).to eq Rational(3, 16)
|
||||
end
|
||||
|
||||
it 'converts fractions with whole numbers' do
|
||||
expect(UnitConversion::ParsedNumber.new('1 1/2').value).to eq Rational(3, 2)
|
||||
expect(UnitConversion::ParsedNumber.new('-1 1/2').value).to eq Rational(-3, 2)
|
||||
expect(UnitConversion::ParsedNumber.new('4 3/4').value).to eq Rational(19, 4)
|
||||
expect(UnitConversion::ParsedNumber.new('18 9/10').value).to eq Rational(189, 10)
|
||||
end
|
||||
|
||||
end
|
99
spec/lib/unit_conversion/parsed_unit_spec.rb
Normal file
99
spec/lib/unit_conversion/parsed_unit_spec.rb
Normal file
@ -0,0 +1,99 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UnitConversion::ParsedUnit do
|
||||
|
||||
it 'converts simple units' do
|
||||
data = {
|
||||
'c' => '[cup_us]',
|
||||
'cups' => '[cup_us]',
|
||||
'pints' => '[pt_us]',
|
||||
'gram' => 'g',
|
||||
'grams' => 'g',
|
||||
'Grams' => 'g',
|
||||
'Tbsp' => '[tbs_us]',
|
||||
'[tbs_us]' => '[tbs_us]',
|
||||
'[oz_av]' => '[oz_av]'
|
||||
}
|
||||
|
||||
data.each do |input, output|
|
||||
expect(UnitConversion::ParsedUnit.new(input).unit).to eq output
|
||||
end
|
||||
end
|
||||
|
||||
it 'converts mixed units' do
|
||||
data = {
|
||||
'oz/c' => '[oz_av]/[cup_us]',
|
||||
'kilograms/cups' => 'kg/[cup_us]',
|
||||
'pints/junk' => '[pt_us]/junk',
|
||||
'gram/[tbs_us]' => 'g/[tbs_us]'
|
||||
}
|
||||
|
||||
data.each do |input, output|
|
||||
expect(UnitConversion::ParsedUnit.new(input).unit).to eq output
|
||||
end
|
||||
end
|
||||
|
||||
describe '.to_s' do
|
||||
it 'renders friendly simple units' do
|
||||
expect(UnitConversion::ParsedUnit.new('m').to_s).to eq 'meter'
|
||||
expect(UnitConversion::ParsedUnit.new('tbsp').to_s).to eq 'tablespoon'
|
||||
expect(UnitConversion::ParsedUnit.new('gallons').to_s).to eq 'gallon'
|
||||
expect(UnitConversion::ParsedUnit.new('[cup_us]').to_s).to eq 'cup'
|
||||
expect(UnitConversion::ParsedUnit.new('junk').to_s).to eq 'junk'
|
||||
end
|
||||
|
||||
it 'renders friendly compound units' do
|
||||
expect(UnitConversion::ParsedUnit.new('m/c').to_s).to eq 'meter/cup'
|
||||
expect(UnitConversion::ParsedUnit.new('[oz_av]/[cup_us]').to_s).to eq 'ounce/cup'
|
||||
end
|
||||
end
|
||||
|
||||
describe '.metric?' do
|
||||
it 'returns true for metric units' do
|
||||
expect(UnitConversion::ParsedUnit.new('m').metric?).to be_truthy
|
||||
expect(UnitConversion::ParsedUnit.new('g').metric?).to be_truthy
|
||||
expect(UnitConversion::ParsedUnit.new('meter').metric?).to be_truthy
|
||||
expect(UnitConversion::ParsedUnit.new('centiliter').metric?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false for standard units' do
|
||||
expect(UnitConversion::ParsedUnit.new('c').metric?).to be_falsey
|
||||
expect(UnitConversion::ParsedUnit.new('tbsp').metric?).to be_falsey
|
||||
expect(UnitConversion::ParsedUnit.new('oz').metric?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns false for unknown units' do
|
||||
expect(UnitConversion::ParsedUnit.new('cats').metric?).to be_falsey
|
||||
expect(UnitConversion::ParsedUnit.new('dogs').metric?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe '.density?' do
|
||||
it 'returns true for any mass over volume unit' do
|
||||
data = [
|
||||
'gram/cup',
|
||||
'pound/gallon',
|
||||
'ounce/tablespoon',
|
||||
'ounce/centimeter3'
|
||||
]
|
||||
|
||||
data.each do |input|
|
||||
expect(UnitConversion::ParsedUnit.new(input).density?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false for any non density unit' do
|
||||
data = [
|
||||
'cup',
|
||||
'gram',
|
||||
'gram/hour',
|
||||
'centimeter3/ounce'
|
||||
]
|
||||
|
||||
data.each do |input|
|
||||
expect(UnitConversion::ParsedUnit.new(input).density?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
49
spec/lib/unit_conversion/value_unit_spec.rb
Normal file
49
spec/lib/unit_conversion/value_unit_spec.rb
Normal file
@ -0,0 +1,49 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UnitConversion::ValueUnit do
|
||||
|
||||
def check_vu(vu, value, unit)
|
||||
expect(vu.value).to be_a UnitConversion::ParsedNumber
|
||||
expect(vu.raw_value).to eq value
|
||||
|
||||
if unit.present?
|
||||
expect(vu.unit).to be_a UnitConversion::ParsedUnit
|
||||
expect(vu.unit.to_s).to eq unit
|
||||
else
|
||||
expect(vu.unit).to be_nil
|
||||
expect(vu).to be_a UnitConversion::ValueNoUnit
|
||||
end
|
||||
end
|
||||
|
||||
describe '.for' do
|
||||
|
||||
it 'Converts single strings' do
|
||||
check_vu(UnitConversion::ValueUnit.for('5 cups'), 5, 'cup')
|
||||
check_vu(UnitConversion::ValueUnit.for('5'), 5, nil)
|
||||
check_vu(UnitConversion::ValueUnit.for('1/3 fl oz'), Rational(1,3), 'foz')
|
||||
end
|
||||
|
||||
it 'Converts a pair of strings' do
|
||||
check_vu(UnitConversion::ValueUnit.for('5', 'cups'), 5, 'cup')
|
||||
check_vu(UnitConversion::ValueUnit.for('5', ''), 5, nil)
|
||||
check_vu(UnitConversion::ValueUnit.for('1/3', 'fl oz'), Rational(1,3), 'foz')
|
||||
end
|
||||
|
||||
it 'Converts a bare Numeric' do
|
||||
check_vu(UnitConversion::ValueUnit.for(5, 'cups'), 5, 'cup')
|
||||
check_vu(UnitConversion::ValueUnit.for(5, ''), 5, nil)
|
||||
check_vu(UnitConversion::ValueUnit.for(Rational(1,3), 'fl oz'), Rational(1,3), 'foz')
|
||||
check_vu(UnitConversion::ValueUnit.for(2.5, 'tsp'), 2.5, 'teaspoon')
|
||||
end
|
||||
|
||||
it 'Converts ParsedNumber and a string' do
|
||||
check_vu(UnitConversion::ValueUnit.for(UnitConversion::ParsedNumber.new('5'), 'cups'), 5, 'cup')
|
||||
check_vu(UnitConversion::ValueUnit.for(UnitConversion::ParsedNumber.new('5'), nil), 5, nil)
|
||||
end
|
||||
|
||||
it 'Converts parsed objects' do
|
||||
check_vu(UnitConversion::ValueUnit.for(UnitConversion::ParsedNumber.new('5'), UnitConversion::ParsedUnit.new('cups')), 5, 'cup')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -4,9 +4,9 @@ RSpec.describe UnitConversion do
|
||||
|
||||
describe '.auto_unit' do
|
||||
it 'leaves units alone if reasonable' do
|
||||
expect(UnitConversion.auto_unit('1/2', 'tbsp')).to eq ['1/2', 'tbsp']
|
||||
expect(UnitConversion.auto_unit('2', 'cups')).to eq ['2', 'cups']
|
||||
expect(UnitConversion.auto_unit('1', 'c')).to eq ['1', 'c']
|
||||
expect(UnitConversion.auto_unit('1/2', 'tbsp')).to eq ['1/2', 'tablespoon']
|
||||
expect(UnitConversion.auto_unit('2', 'cups')).to eq ['2', 'cup']
|
||||
expect(UnitConversion.auto_unit('1', 'c')).to eq ['1', 'cup']
|
||||
end
|
||||
|
||||
it 'leaves units alone if unknown' do
|
||||
@ -32,22 +32,6 @@ RSpec.describe UnitConversion do
|
||||
end
|
||||
end
|
||||
|
||||
describe '.get_value' do
|
||||
it 'returns rationals' do
|
||||
expect(UnitConversion.get_value('1/2')).to eq Rational(1, 2)
|
||||
expect(UnitConversion.get_value('1 1/2')).to eq Rational(3, 2)
|
||||
expect(UnitConversion.get_value('-1/2')).to eq Rational(-1, 2)
|
||||
expect(UnitConversion.get_value('-1 1/2')).to eq Rational(-3, 2)
|
||||
expect(UnitConversion.get_value('18 9/10')).to eq Rational(189, 10)
|
||||
end
|
||||
|
||||
it 'returns decimals' do
|
||||
expect(UnitConversion.get_value('-1')).to eq -1.to_d
|
||||
expect(UnitConversion.get_value('1.0')).to eq 1.to_d
|
||||
expect(UnitConversion.get_value('5.56')).to eq "5.56".to_d
|
||||
end
|
||||
end
|
||||
|
||||
describe '.convert' do
|
||||
it 'scales decimal numbers' do
|
||||
expect(UnitConversion.convert('1', '2', 'cup', 'cup')).to eq '2'
|
||||
@ -77,9 +61,7 @@ RSpec.describe UnitConversion do
|
||||
expect(UnitConversion.convert('1/2', '2', 'slices', 'slices')).to eq '1'
|
||||
expect(UnitConversion.convert('4', '1/8', nil, nil)).to eq '1/2'
|
||||
expect(UnitConversion.convert('4', '1/8', 'slices', nil)).to eq '1/2'
|
||||
expect(UnitConversion.convert('4', '1/8', nil, 'slices')).to eq '1/2'
|
||||
expect(UnitConversion.convert('4', '1/8', 'slices', '')).to eq '1/2'
|
||||
expect(UnitConversion.convert('4', '1/8', '', 'slices')).to eq '1/2'
|
||||
end
|
||||
|
||||
it 'converts and scales' do
|
||||
@ -111,7 +93,7 @@ RSpec.describe UnitConversion do
|
||||
}
|
||||
|
||||
data.each do |input, output|
|
||||
expect(UnitConversion.parse(input)).to eq output
|
||||
expect(UnitConversion.parse(input).unitwise).to eq output
|
||||
end
|
||||
end
|
||||
|
||||
@ -124,16 +106,12 @@ RSpec.describe UnitConversion do
|
||||
]
|
||||
|
||||
data.each do |input|
|
||||
expect { UnitConversion.parse(input) }.to raise_error(UnitConversion::UnknownUnitError), "'#{input}' didn't raise"
|
||||
expect { UnitConversion.parse(input).unitwise }.to raise_error(UnitConversion::UnknownUnitError), "'#{input}' didn't raise"
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises UnparseableUnitError on malformed string' do
|
||||
data = [
|
||||
'55',
|
||||
'55.5',
|
||||
'-55',
|
||||
'-55.55',
|
||||
'5.5/2 cups',
|
||||
'2/3.0 cups',
|
||||
'ounce',
|
||||
@ -148,106 +126,6 @@ RSpec.describe UnitConversion do
|
||||
end
|
||||
end
|
||||
|
||||
describe '.density?' do
|
||||
it 'returns true for any mass over volume unit' do
|
||||
data = [
|
||||
Unitwise(1, 'gram/cup'),
|
||||
Unitwise(1, 'pound/gallon'),
|
||||
Unitwise(1, 'ounce/tablespoon'),
|
||||
Unitwise(1, 'ounce/centimeter3')
|
||||
]
|
||||
|
||||
data.each do |input|
|
||||
expect(UnitConversion.density?(input)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false for any non density unit' do
|
||||
data = [
|
||||
Unitwise(1, 'cup'),
|
||||
Unitwise(1, 'gram'),
|
||||
Unitwise(1, 'gram/hour'),
|
||||
Unitwise(1, 'centimeter3/ounce')
|
||||
]
|
||||
|
||||
data.each do |input|
|
||||
expect(UnitConversion.density?(input)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.rationalize' do
|
||||
it 'leaves integers alone' do
|
||||
expect(UnitConversion.rationalize(1)).to eq 1
|
||||
expect(UnitConversion.rationalize(15)).to eq 15
|
||||
expect(UnitConversion.rationalize(-1)).to eq -1
|
||||
expect(UnitConversion.rationalize(0)).to eq 0
|
||||
end
|
||||
|
||||
it 'leaves non-fractional numbers alone' do
|
||||
expect(UnitConversion.rationalize(1.0)).to eq 1.0
|
||||
expect(UnitConversion.rationalize(-1.0)).to eq -1.0
|
||||
expect(UnitConversion.rationalize(0.0)).to eq 0.0
|
||||
expect(UnitConversion.rationalize(35.0)).to eq 35.0
|
||||
end
|
||||
|
||||
it 'leaves already nice rationals alone' do
|
||||
expect(UnitConversion.rationalize(Rational(1,2))).to eq Rational(1,2)
|
||||
expect(UnitConversion.rationalize(Rational(5,2))).to eq Rational(5,2)
|
||||
expect(UnitConversion.rationalize(Rational(3,16))).to eq Rational(3,16)
|
||||
expect(UnitConversion.rationalize(Rational(3,4))).to eq Rational(3,4)
|
||||
end
|
||||
|
||||
it 'converts neat decimals to rationals' do
|
||||
expect(UnitConversion.rationalize(1.5)).to eq Rational(3,2)
|
||||
expect(UnitConversion.rationalize(0.125)).to eq Rational(1,8)
|
||||
expect(UnitConversion.rationalize(5.0625)).to eq Rational(81, 16)
|
||||
expect(UnitConversion.rationalize(0.75)).to eq Rational(3,4)
|
||||
end
|
||||
|
||||
it 'rounds weird rationals to nice rationals' do
|
||||
expect(UnitConversion.rationalize(Rational(3,7))).to eq Rational(7,16)
|
||||
expect(UnitConversion.rationalize(Rational(2,5))).to eq Rational(3,8)
|
||||
expect(UnitConversion.rationalize(Rational(2,5))).to eq Rational(3,8)
|
||||
end
|
||||
|
||||
it 'rounds weird decimals to nice rationals' do
|
||||
expect(UnitConversion.rationalize(0.24)).to eq Rational(1,4)
|
||||
expect(UnitConversion.rationalize(1.24)).to eq Rational(5,4)
|
||||
expect(UnitConversion.rationalize(1.13)).to eq Rational(9,8)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.normalize_unit_name' do
|
||||
|
||||
it 'converts simple units' do
|
||||
data = {
|
||||
'c' => '[cup_us]',
|
||||
'cups' => '[cup_us]',
|
||||
'pints' => '[pt_us]',
|
||||
'gram' => 'g',
|
||||
'grams' => 'g',
|
||||
'Grams' => 'g',
|
||||
'Tbsp' => '[tbs_us]'
|
||||
}
|
||||
|
||||
data.each do |input, output|
|
||||
expect(UnitConversion.normalize_unit_names(input)).to eq output
|
||||
end
|
||||
end
|
||||
|
||||
it 'converts mixed units' do
|
||||
data = {
|
||||
'oz/c' => '[oz_av]/[cup_us]',
|
||||
'kilograms/cups' => 'kg/[cup_us]',
|
||||
'pints/junk' => '[pt_us]/junk'
|
||||
}
|
||||
|
||||
data.each do |input, output|
|
||||
expect(UnitConversion.normalize_unit_names(input)).to eq output
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -23,4 +23,15 @@ RSpec.describe Ingredient, type: :model do
|
||||
|
||||
end
|
||||
|
||||
describe 'set_usda_food' do
|
||||
it 'sets the density' do
|
||||
i = build(:ingredient)
|
||||
f = create(:salted_butter)
|
||||
|
||||
i.set_usda_food(f)
|
||||
|
||||
expect(i.density).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe RecipeIngredient, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
|
||||
end
|
||||
|
@ -33,12 +33,15 @@ RSpec.configure do |config|
|
||||
|
||||
config.before(:suite) do
|
||||
DatabaseCleaner.strategy = :transaction
|
||||
DatabaseCleaner.clean_with(:truncation)
|
||||
|
||||
DatabaseCleaner.cleaning do
|
||||
FactoryGirl.lint
|
||||
unless ENV['FAST'] == 'true'
|
||||
DatabaseCleaner.clean_with(:truncation)
|
||||
|
||||
DatabaseCleaner.cleaning do
|
||||
FactoryGirl.lint
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
config.around(:each) do |example|
|
||||
DatabaseCleaner.cleaning do
|
||||
|
@ -1,10 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "Ingredients", type: :request do
|
||||
describe "GET /ingredients" do
|
||||
it "works! (now write some real specs)" do
|
||||
get ingredients_path
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,10 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "Recipes", type: :request do
|
||||
describe "GET /recipes" do
|
||||
it "works! (now write some real specs)" do
|
||||
get recipes_path
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,39 +0,0 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe IngredientsController, type: :routing do
|
||||
describe "routing" do
|
||||
|
||||
it "routes to #index" do
|
||||
expect(:get => "/ingredients").to route_to("ingredients#index")
|
||||
end
|
||||
|
||||
it "routes to #new" do
|
||||
expect(:get => "/ingredients/new").to route_to("ingredients#new")
|
||||
end
|
||||
|
||||
it "routes to #show" do
|
||||
expect(:get => "/ingredients/1").to route_to("ingredients#show", :id => "1")
|
||||
end
|
||||
|
||||
it "routes to #edit" do
|
||||
expect(:get => "/ingredients/1/edit").to route_to("ingredients#edit", :id => "1")
|
||||
end
|
||||
|
||||
it "routes to #create" do
|
||||
expect(:post => "/ingredients").to route_to("ingredients#create")
|
||||
end
|
||||
|
||||
it "routes to #update via PUT" do
|
||||
expect(:put => "/ingredients/1").to route_to("ingredients#update", :id => "1")
|
||||
end
|
||||
|
||||
it "routes to #update via PATCH" do
|
||||
expect(:patch => "/ingredients/1").to route_to("ingredients#update", :id => "1")
|
||||
end
|
||||
|
||||
it "routes to #destroy" do
|
||||
expect(:delete => "/ingredients/1").to route_to("ingredients#destroy", :id => "1")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,39 +0,0 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe RecipesController, type: :routing do
|
||||
describe "routing" do
|
||||
|
||||
it "routes to #index" do
|
||||
expect(:get => "/recipes").to route_to("recipes#index")
|
||||
end
|
||||
|
||||
it "routes to #new" do
|
||||
expect(:get => "/recipes/new").to route_to("recipes#new")
|
||||
end
|
||||
|
||||
it "routes to #show" do
|
||||
expect(:get => "/recipes/1").to route_to("recipes#show", :id => "1")
|
||||
end
|
||||
|
||||
it "routes to #edit" do
|
||||
expect(:get => "/recipes/1/edit").to route_to("recipes#edit", :id => "1")
|
||||
end
|
||||
|
||||
it "routes to #create" do
|
||||
expect(:post => "/recipes").to route_to("recipes#create")
|
||||
end
|
||||
|
||||
it "routes to #update via PUT" do
|
||||
expect(:put => "/recipes/1").to route_to("recipes#update", :id => "1")
|
||||
end
|
||||
|
||||
it "routes to #update via PATCH" do
|
||||
expect(:patch => "/recipes/1").to route_to("recipes#update", :id => "1")
|
||||
end
|
||||
|
||||
it "routes to #destroy" do
|
||||
expect(:delete => "/recipes/1").to route_to("recipes#destroy", :id => "1")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "ingredients/edit", type: :view do
|
||||
before(:each) do
|
||||
@ingredient = assign(:ingredient, Ingredient.create!())
|
||||
end
|
||||
|
||||
it "renders the edit ingredient form" do
|
||||
render
|
||||
|
||||
assert_select "form[action=?][method=?]", ingredient_path(@ingredient), "post" do
|
||||
end
|
||||
end
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "ingredients/index", type: :view do
|
||||
before(:each) do
|
||||
assign(:ingredients, [
|
||||
Ingredient.create!(),
|
||||
Ingredient.create!()
|
||||
])
|
||||
end
|
||||
|
||||
it "renders a list of ingredients" do
|
||||
render
|
||||
end
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "ingredients/new", type: :view do
|
||||
before(:each) do
|
||||
assign(:ingredient, Ingredient.new())
|
||||
end
|
||||
|
||||
it "renders new ingredient form" do
|
||||
render
|
||||
|
||||
assert_select "form[action=?][method=?]", ingredients_path, "post" do
|
||||
end
|
||||
end
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "ingredients/show", type: :view do
|
||||
before(:each) do
|
||||
@ingredient = assign(:ingredient, Ingredient.create!())
|
||||
end
|
||||
|
||||
it "renders attributes in <p>" do
|
||||
render
|
||||
end
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "recipes/edit", type: :view do
|
||||
before(:each) do
|
||||
@recipe = assign(:recipe, Recipe.create!())
|
||||
end
|
||||
|
||||
it "renders the edit recipe form" do
|
||||
render
|
||||
|
||||
assert_select "form[action=?][method=?]", recipe_path(@recipe), "post" do
|
||||
end
|
||||
end
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "recipes/index", type: :view do
|
||||
before(:each) do
|
||||
assign(:recipes, [
|
||||
Recipe.create!(),
|
||||
Recipe.create!()
|
||||
])
|
||||
end
|
||||
|
||||
it "renders a list of recipes" do
|
||||
render
|
||||
end
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "recipes/new", type: :view do
|
||||
before(:each) do
|
||||
assign(:recipe, Recipe.new())
|
||||
end
|
||||
|
||||
it "renders new recipe form" do
|
||||
render
|
||||
|
||||
assert_select "form[action=?][method=?]", recipes_path, "post" do
|
||||
end
|
||||
end
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "recipes/show", type: :view do
|
||||
before(:each) do
|
||||
@recipe = assign(:recipe, Recipe.create!())
|
||||
end
|
||||
|
||||
it "renders attributes in <p>" do
|
||||
render
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user