Works on ingredients

This commit is contained in:
2019-02-03 15:17:43 +01:00
parent 28afe8ece0
commit d3259c82b3
10 changed files with 151 additions and 61 deletions

Binary file not shown.

View File

@@ -2,7 +2,7 @@
CREATE TABLE recipes (
id INTEGER PRIMARY KEY NOT NULL,
title VARCHAR NOT NULL,
category INTEGER NOT NULL,
category SMALLINT NOT NULL,
ingredients TEXT NOT NULL,
preparation TEXT NOT NULL
)

View File

@@ -3,7 +3,22 @@ extern crate diesel;
use self::cookbook::*;
use self::models::*;
use self::diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use cookbook::models::fields::IngredientList;
struct IngredientListManager<'a>(&'a SqliteConnection, IngredientList);
impl<'a> IngredientListManager<'a> {
fn display_lines(&self) -> String {
let mut repr = String::new();
for id in self.1.iter() {
let ingdt = ingredients::get(self.0, id).expect("Database integrity error");
repr.push_str(&format!("{}\n", ingdt.alias));
}
repr
}
}
fn main() {
@@ -13,6 +28,6 @@ fn main() {
for rec in result {
println!("*************\n{}\n({:?})", rec.title, rec.category);
println!("-------------\n");
println!("{}", rec.ingredients);
println!("{}", rec.ingredients.as_string());
}
}

View File

@@ -1,16 +1,16 @@
extern crate cookbook;
extern crate diesel;
use std::io::{Read, stdin};
use std::io::{stdin};
use diesel::SqliteConnection;
use self::cookbook::*;
use self::models::{NewRecipe, fields::RecipeCategory};
use self::models::{NewRecipe, fields::RecipeCategory, fields::IngredientList};
struct CreateRecipe<'a> {
connection: &'a SqliteConnection,
title: &'a str,
category: Option<RecipeCategory>,
ingredients: String,
ingredients: IngredientList,
}
impl<'a> CreateRecipe<'a> {
@@ -19,7 +19,7 @@ impl<'a> CreateRecipe<'a> {
connection: conn,
title: "New recipe",
category: None,
ingredients: String::new(),
ingredients: IngredientList::new(),
}
}
@@ -35,23 +35,23 @@ impl<'a> CreateRecipe<'a> {
use crate::ingredients::*;
// Check it exists or create
if let Some(_ingdt) = find(self.connection, &name) {
println!("=");
} else {
create(self.connection, &name);
println!("+{}", &name);
match get_id_or_create(self.connection, &name) {
Ok(id) => {
println!("Got id {}", id);
self.ingredients.push(id);
},
Err(_) => println!("Error adding ingredient")
}
self.ingredients.push_str(&name);
}
/// Builds a NewRecipe instance from current data and insert it.
fn insert(self) {
let new_recipe = NewRecipe::new(
self.title,
self.category.unwrap_or(RecipeCategory::Breakfast),
&self.ingredients,
"");
let new_recipe = NewRecipe {
title: self.title,
category: self.category.unwrap_or(RecipeCategory::Breakfast),
ingredients: self.ingredients,
preparation: ""
};
match new_recipe.insert(self.connection) {
Ok(new) => println!("Added {}", new.title),
Err(e) => println!("Error: {}", e),

View File

@@ -40,34 +40,52 @@ pub mod recipes {
}
}
pub mod ingredients {
use crate::models::{Ingredient, NewIngredient};
use crate::models::{Ingredient, NewIngredient, fields::IngredientId};
use super::{SqliteConnection, schema};
use super::diesel::prelude::*;
pub fn find(conn: &SqliteConnection, name: &str) -> Option<Ingredient> {
use self::schema::ingredients::dsl::*;
let results = ingredients.filter(alias.like(name))
.limit(1)
.load::<Ingredient>(conn)
.expect("Error finding ingredient");
if !results.is_empty() {
Some(results.into_iter().nth(0).unwrap())
/// Returns the id of the ingredient identifiable by `name: &str`
/// If the ingredient does not yet exists, it is inserted in database.
pub fn get_id_or_create(conn: &SqliteConnection, name: &str) -> Result<i32,String> {
if let Some(ingdt) = find(conn, name) {
return Ok(ingdt.id);
} else {
None
return create(conn, name);
}
}
pub fn create(conn: &SqliteConnection, name: &str) -> usize {
use self::schema::ingredients;
pub fn get(conn: &SqliteConnection, ingdt_id: &IngredientId) -> Result<Ingredient,String> {
use self::schema::ingredients::dsl::*;
diesel::insert_into(ingredients::table)
ingredients.filter(id.eq(ingdt_id))
.first::<Ingredient>(conn)
.map_err(|e| format!("Could not retrieve : {}", e))
}
fn find(conn: &SqliteConnection, name: &str) -> Option<Ingredient> {
use self::schema::ingredients::dsl::*;
match ingredients.filter(alias.like(name))
.first(conn)
{
Ok(ingdt) => Some(ingdt),
Err(_) => None,
}
}
fn create(conn: &SqliteConnection, name: &str) -> Result<i32,String> {
use self::schema::ingredients::dsl::*;
let _ = diesel::insert_into(ingredients)
.values(&NewIngredient { alias: name })
.execute(conn)
.expect("Error inserting ingredient")
.map_err(|e| format!("Error inserting ingredient ! {:?}", e))?;
let inserted = ingredients
.order(id.desc())
.first::<Ingredient>(conn)
.map_err(|e| format!("No ingredient at all ! {:?}", e))?;
Ok(inserted.id)
}
}

View File

@@ -57,8 +57,7 @@ pub mod fields {
}
impl<DB: Backend> FromSql<SmallInt, DB> for RecipeCategory
where
i16: FromSql<SmallInt, DB>
where i16: FromSql<SmallInt, DB>
{
fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
let v = i16::from_sql(bytes)?;
@@ -68,13 +67,70 @@ pub mod fields {
}
impl<DB: Backend> ToSql<SmallInt, DB> for RecipeCategory
where
i16: ToSql<SmallInt, DB>{
where i16: ToSql<SmallInt, DB>
{
fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result {
i16::to_sql(&(*self as i16), out)
}
}
pub type IngredientId = i32;
#[derive(Debug, Clone, FromSqlRow, AsExpression)]
#[sql_type = "Text"]
pub struct IngredientList(Vec<IngredientId>);
/// Just a basic method for now
impl IngredientList {
pub fn new() -> Self {
IngredientList(Vec::new())
}
pub fn as_string(&self) -> String {
self.0.iter()
.map(|i| format!("{}", i))
.collect::<Vec<String>>()
.join(" ")
}
}
impl std::ops::Deref for IngredientList {
type Target = Vec<IngredientId>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for IngredientList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<DB: Backend> FromSql<Text, DB> for IngredientList
where String: FromSql<Text, DB>
{
fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
let data = String::from_sql(bytes)?;
Ok(
IngredientList(
data.split(" ")
.map(|i| i.parse::<IngredientId>().unwrap())
.collect()
))
}
}
impl<DB: Backend> ToSql<Text, DB> for IngredientList
where String: ToSql<Text, DB>
{
fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result {
String::to_sql(&self.as_string(), out)
}
}
}
@@ -84,7 +140,7 @@ pub struct Recipe {
pub id: i32,
pub title: String,
pub category: fields::RecipeCategory,
pub ingredients: String,
pub ingredients: fields::IngredientList,
pub preparation: String,
}
@@ -93,20 +149,11 @@ pub struct Recipe {
pub struct NewRecipe<'a> {
pub title: &'a str,
pub category: fields::RecipeCategory,
pub ingredients: &'a str,
pub ingredients: fields::IngredientList,
pub preparation: &'a str,
}
impl<'a> NewRecipe<'a> {
pub fn new(title: &'a str, category: fields::RecipeCategory, ingredients: &'a str, preparation: &'a str) -> Self {
NewRecipe{
title,
category,
ingredients,
preparation,
}
}
pub fn insert(self, conn: &SqliteConnection) -> Result<Self, String> {
diesel::insert_into(recipes::table)
.values(&self)

View File

@@ -14,6 +14,7 @@ use self::planner::solver::{Variables, Domain, Problem};
type Day = String;
const DAYS: &[&str] = &["Lundi", "Mardi", "Mercredi"];
#[allow(dead_code)]
enum Meals {
Breakfast(Day),
Lunch(Day),
@@ -41,8 +42,9 @@ fn generate_variables<V>(domain: &Domain<V>) -> Vec<(String, &Domain<V>, Option<
}
fn ingredients_contains<'a>(assign: &Variables<'a,Recipe>) -> bool {
assign.get("Lundi_Lunch").unwrap().unwrap().ingredients.contains("Patates")
&& !assign.get("Mardi_Lunch").unwrap().unwrap().ingredients.contains("Patates")
let id = 0;
assign.get("Lundi_Lunch").unwrap().unwrap().ingredients.contains(&id)
&& !assign.get("Mardi_Lunch").unwrap().unwrap().ingredients.contains(&id)
}

View File

@@ -13,6 +13,7 @@ enum Assignment<'a, V> {
Clear(String)
}
type Domains<'a, V> = HashMap<String, &'a Domain<V>>;
/// The domain of values that can be assigned to variables
#[derive(Clone)]
@@ -71,10 +72,11 @@ impl<'a,V> Problem<'a, V> {
/// Returns all possible Updates for next assignements, prepended with
/// a Clear to ensure the variable is unset before when leaving the branch.
fn _assign_next(&self) -> Option<Vec<Assignment<'a,V>>> {
fn _push_updates(&self) -> Option<Vec<Assignment<'a,V>>> {
// TODO: should be able to inject a choosing strategy
if let Some((key,_)) = self.variables.iter().find(|(_, val)| val.is_none()) {
let domain = self.domains.get(key).expect("No domain for variable !");
// Push a clear assignment first, just before going up the stack.
let mut updates = vec![Assignment::Clear(key.clone())];
if domain.values.is_empty() { panic!("No value in domain !"); }
@@ -96,13 +98,13 @@ impl<'a,V> Problem<'a, V> {
return true;
}
/// Visit all possible solutions, using a stack.
/// Visit all possible solutions, using a stack (DFS).
pub fn solve_all(&mut self) -> Vec<Variables<'a,V>>
where V: Clone + fmt::Debug
{
let mut solutions: Vec<Variables<V>> = vec![];
let mut stack: Vec<Assignment<'a, V>> = vec![];
stack.append(&mut self._assign_next().unwrap());
stack.append(&mut self._push_updates().unwrap());
loop {
let node = stack.pop();
if node.is_none() { break; };
@@ -111,7 +113,7 @@ impl<'a,V> Problem<'a, V> {
// Assign the variable and open new branches, if any.
*self.variables.get_mut(&key).unwrap() = Some(val);
// TODO: handle case of empty domain.values
if let Some(mut nodes) = self._assign_next() {
if let Some(mut nodes) = self._push_updates() {
stack.append(&mut nodes);
} else {
// Assignements are completed

View File

@@ -20,7 +20,7 @@
<h4 class="title">{{ items[active_view].title }}</h4>
<h6 class="subtitle">{{ categories[items[active_view].category].name }}</h6>
<p><strong>{{ items[active_view].ingredients }}</strong></p>
<button @click="deleteRecipe(active_view + 1)" class="button is-danger is-pulled-right">DELETE !</button>
<button @click="deleteRecipe(items[active_view].id)" class="button is-danger is-pulled-right">DELETE !</button>
</div>
</section>
<!-- Category List View -->
@@ -75,9 +75,15 @@
deleteRecipe: function(id) {
fetch("/api/delete/" + id)
.then((res) => res.json())
.then((data) => console.log("Deleted :" + data))
.then((data) => {
if (data === true) {
this.items.splice(this.active_view, 1);
this.closeActiveView();
console.log("Deleted :" + data);
} else {
console.log("Error deleting");
}})
.catch((err) => console.error(err));
this.closeActiveView();
},
fetchRecipesList: function() {
fetch("/api/list")

View File

@@ -40,7 +40,7 @@ mod api {
id: rec.id,
title: rec.title,
category: rec.category as i16,
ingredients: rec.ingredients,
ingredients: rec.ingredients.as_string(),
preparation: rec.preparation,
}
}