Works on ingredients
This commit is contained in:
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
CREATE TABLE recipes (
|
CREATE TABLE recipes (
|
||||||
id INTEGER PRIMARY KEY NOT NULL,
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
title VARCHAR NOT NULL,
|
title VARCHAR NOT NULL,
|
||||||
category INTEGER NOT NULL,
|
category SMALLINT NOT NULL,
|
||||||
ingredients TEXT NOT NULL,
|
ingredients TEXT NOT NULL,
|
||||||
preparation TEXT NOT NULL
|
preparation TEXT NOT NULL
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,22 @@ extern crate diesel;
|
|||||||
|
|
||||||
use self::cookbook::*;
|
use self::cookbook::*;
|
||||||
use self::models::*;
|
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() {
|
fn main() {
|
||||||
|
|
||||||
@@ -13,6 +28,6 @@ fn main() {
|
|||||||
for rec in result {
|
for rec in result {
|
||||||
println!("*************\n{}\n({:?})", rec.title, rec.category);
|
println!("*************\n{}\n({:?})", rec.title, rec.category);
|
||||||
println!("-------------\n");
|
println!("-------------\n");
|
||||||
println!("{}", rec.ingredients);
|
println!("{}", rec.ingredients.as_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
extern crate cookbook;
|
extern crate cookbook;
|
||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
|
|
||||||
use std::io::{Read, stdin};
|
use std::io::{stdin};
|
||||||
use diesel::SqliteConnection;
|
use diesel::SqliteConnection;
|
||||||
use self::cookbook::*;
|
use self::cookbook::*;
|
||||||
use self::models::{NewRecipe, fields::RecipeCategory};
|
use self::models::{NewRecipe, fields::RecipeCategory, fields::IngredientList};
|
||||||
|
|
||||||
struct CreateRecipe<'a> {
|
struct CreateRecipe<'a> {
|
||||||
connection: &'a SqliteConnection,
|
connection: &'a SqliteConnection,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
category: Option<RecipeCategory>,
|
category: Option<RecipeCategory>,
|
||||||
ingredients: String,
|
ingredients: IngredientList,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CreateRecipe<'a> {
|
impl<'a> CreateRecipe<'a> {
|
||||||
@@ -19,7 +19,7 @@ impl<'a> CreateRecipe<'a> {
|
|||||||
connection: conn,
|
connection: conn,
|
||||||
title: "New recipe",
|
title: "New recipe",
|
||||||
category: None,
|
category: None,
|
||||||
ingredients: String::new(),
|
ingredients: IngredientList::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,23 +35,23 @@ impl<'a> CreateRecipe<'a> {
|
|||||||
use crate::ingredients::*;
|
use crate::ingredients::*;
|
||||||
|
|
||||||
// Check it exists or create
|
// Check it exists or create
|
||||||
if let Some(_ingdt) = find(self.connection, &name) {
|
match get_id_or_create(self.connection, &name) {
|
||||||
println!("=");
|
Ok(id) => {
|
||||||
} else {
|
println!("Got id {}", id);
|
||||||
create(self.connection, &name);
|
self.ingredients.push(id);
|
||||||
println!("+{}", &name);
|
},
|
||||||
|
Err(_) => println!("Error adding ingredient")
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ingredients.push_str(&name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a NewRecipe instance from current data and insert it.
|
/// Builds a NewRecipe instance from current data and insert it.
|
||||||
fn insert(self) {
|
fn insert(self) {
|
||||||
let new_recipe = NewRecipe::new(
|
let new_recipe = NewRecipe {
|
||||||
self.title,
|
title: self.title,
|
||||||
self.category.unwrap_or(RecipeCategory::Breakfast),
|
category: self.category.unwrap_or(RecipeCategory::Breakfast),
|
||||||
&self.ingredients,
|
ingredients: self.ingredients,
|
||||||
"");
|
preparation: ""
|
||||||
|
};
|
||||||
match new_recipe.insert(self.connection) {
|
match new_recipe.insert(self.connection) {
|
||||||
Ok(new) => println!("Added {}", new.title),
|
Ok(new) => println!("Added {}", new.title),
|
||||||
Err(e) => println!("Error: {}", e),
|
Err(e) => println!("Error: {}", e),
|
||||||
|
|||||||
@@ -40,34 +40,52 @@ pub mod recipes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub mod ingredients {
|
pub mod ingredients {
|
||||||
use crate::models::{Ingredient, NewIngredient};
|
use crate::models::{Ingredient, NewIngredient, fields::IngredientId};
|
||||||
use super::{SqliteConnection, schema};
|
use super::{SqliteConnection, schema};
|
||||||
use super::diesel::prelude::*;
|
use super::diesel::prelude::*;
|
||||||
|
|
||||||
pub fn find(conn: &SqliteConnection, name: &str) -> Option<Ingredient> {
|
/// 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 {
|
||||||
|
return create(conn, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(conn: &SqliteConnection, ingdt_id: &IngredientId) -> Result<Ingredient,String> {
|
||||||
use self::schema::ingredients::dsl::*;
|
use self::schema::ingredients::dsl::*;
|
||||||
|
|
||||||
let results = ingredients.filter(alias.like(name))
|
ingredients.filter(id.eq(ingdt_id))
|
||||||
.limit(1)
|
.first::<Ingredient>(conn)
|
||||||
.load::<Ingredient>(conn)
|
.map_err(|e| format!("Could not retrieve : {}", e))
|
||||||
.expect("Error finding ingredient");
|
}
|
||||||
|
|
||||||
if !results.is_empty() {
|
fn find(conn: &SqliteConnection, name: &str) -> Option<Ingredient> {
|
||||||
Some(results.into_iter().nth(0).unwrap())
|
use self::schema::ingredients::dsl::*;
|
||||||
} else {
|
|
||||||
None
|
match ingredients.filter(alias.like(name))
|
||||||
|
.first(conn)
|
||||||
|
{
|
||||||
|
Ok(ingdt) => Some(ingdt),
|
||||||
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(conn: &SqliteConnection, name: &str) -> usize {
|
fn create(conn: &SqliteConnection, name: &str) -> Result<i32,String> {
|
||||||
use self::schema::ingredients;
|
use self::schema::ingredients::dsl::*;
|
||||||
|
|
||||||
diesel::insert_into(ingredients::table)
|
let _ = diesel::insert_into(ingredients)
|
||||||
.values(&NewIngredient { alias: name })
|
.values(&NewIngredient { alias: name })
|
||||||
.execute(conn)
|
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ pub mod fields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<DB: Backend> FromSql<SmallInt, DB> for RecipeCategory
|
impl<DB: Backend> FromSql<SmallInt, DB> for RecipeCategory
|
||||||
where
|
where i16: FromSql<SmallInt, DB>
|
||||||
i16: FromSql<SmallInt, DB>
|
|
||||||
{
|
{
|
||||||
fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
|
fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
|
||||||
let v = i16::from_sql(bytes)?;
|
let v = i16::from_sql(bytes)?;
|
||||||
@@ -68,13 +67,70 @@ pub mod fields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<DB: Backend> ToSql<SmallInt, DB> for RecipeCategory
|
impl<DB: Backend> ToSql<SmallInt, DB> for RecipeCategory
|
||||||
where
|
where i16: ToSql<SmallInt, DB>
|
||||||
i16: ToSql<SmallInt, DB>{
|
{
|
||||||
fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result {
|
fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result {
|
||||||
i16::to_sql(&(*self as i16), out)
|
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 id: i32,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub category: fields::RecipeCategory,
|
pub category: fields::RecipeCategory,
|
||||||
pub ingredients: String,
|
pub ingredients: fields::IngredientList,
|
||||||
pub preparation: String,
|
pub preparation: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,20 +149,11 @@ pub struct Recipe {
|
|||||||
pub struct NewRecipe<'a> {
|
pub struct NewRecipe<'a> {
|
||||||
pub title: &'a str,
|
pub title: &'a str,
|
||||||
pub category: fields::RecipeCategory,
|
pub category: fields::RecipeCategory,
|
||||||
pub ingredients: &'a str,
|
pub ingredients: fields::IngredientList,
|
||||||
pub preparation: &'a str,
|
pub preparation: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NewRecipe<'a> {
|
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> {
|
pub fn insert(self, conn: &SqliteConnection) -> Result<Self, String> {
|
||||||
diesel::insert_into(recipes::table)
|
diesel::insert_into(recipes::table)
|
||||||
.values(&self)
|
.values(&self)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use self::planner::solver::{Variables, Domain, Problem};
|
|||||||
type Day = String;
|
type Day = String;
|
||||||
const DAYS: &[&str] = &["Lundi", "Mardi", "Mercredi"];
|
const DAYS: &[&str] = &["Lundi", "Mardi", "Mercredi"];
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
enum Meals {
|
enum Meals {
|
||||||
Breakfast(Day),
|
Breakfast(Day),
|
||||||
Lunch(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 {
|
fn ingredients_contains<'a>(assign: &Variables<'a,Recipe>) -> bool {
|
||||||
assign.get("Lundi_Lunch").unwrap().unwrap().ingredients.contains("Patates")
|
let id = 0;
|
||||||
&& !assign.get("Mardi_Lunch").unwrap().unwrap().ingredients.contains("Patates")
|
assign.get("Lundi_Lunch").unwrap().unwrap().ingredients.contains(&id)
|
||||||
|
&& !assign.get("Mardi_Lunch").unwrap().unwrap().ingredients.contains(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ enum Assignment<'a, V> {
|
|||||||
Clear(String)
|
Clear(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Domains<'a, V> = HashMap<String, &'a Domain<V>>;
|
type Domains<'a, V> = HashMap<String, &'a Domain<V>>;
|
||||||
/// The domain of values that can be assigned to variables
|
/// The domain of values that can be assigned to variables
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -71,10 +72,11 @@ impl<'a,V> Problem<'a, V> {
|
|||||||
|
|
||||||
/// Returns all possible Updates for next assignements, prepended with
|
/// Returns all possible Updates for next assignements, prepended with
|
||||||
/// a Clear to ensure the variable is unset before when leaving the branch.
|
/// 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
|
// TODO: should be able to inject a choosing strategy
|
||||||
if let Some((key,_)) = self.variables.iter().find(|(_, val)| val.is_none()) {
|
if let Some((key,_)) = self.variables.iter().find(|(_, val)| val.is_none()) {
|
||||||
let domain = self.domains.get(key).expect("No domain for variable !");
|
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())];
|
let mut updates = vec![Assignment::Clear(key.clone())];
|
||||||
|
|
||||||
if domain.values.is_empty() { panic!("No value in domain !"); }
|
if domain.values.is_empty() { panic!("No value in domain !"); }
|
||||||
@@ -96,13 +98,13 @@ impl<'a,V> Problem<'a, V> {
|
|||||||
return true;
|
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>>
|
pub fn solve_all(&mut self) -> Vec<Variables<'a,V>>
|
||||||
where V: Clone + fmt::Debug
|
where V: Clone + fmt::Debug
|
||||||
{
|
{
|
||||||
let mut solutions: Vec<Variables<V>> = vec![];
|
let mut solutions: Vec<Variables<V>> = vec![];
|
||||||
let mut stack: Vec<Assignment<'a, 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 {
|
loop {
|
||||||
let node = stack.pop();
|
let node = stack.pop();
|
||||||
if node.is_none() { break; };
|
if node.is_none() { break; };
|
||||||
@@ -111,7 +113,7 @@ impl<'a,V> Problem<'a, V> {
|
|||||||
// Assign the variable and open new branches, if any.
|
// Assign the variable and open new branches, if any.
|
||||||
*self.variables.get_mut(&key).unwrap() = Some(val);
|
*self.variables.get_mut(&key).unwrap() = Some(val);
|
||||||
// TODO: handle case of empty domain.values
|
// 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);
|
stack.append(&mut nodes);
|
||||||
} else {
|
} else {
|
||||||
// Assignements are completed
|
// Assignements are completed
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<h4 class="title">{{ items[active_view].title }}</h4>
|
<h4 class="title">{{ items[active_view].title }}</h4>
|
||||||
<h6 class="subtitle">{{ categories[items[active_view].category].name }}</h6>
|
<h6 class="subtitle">{{ categories[items[active_view].category].name }}</h6>
|
||||||
<p><strong>{{ items[active_view].ingredients }}</strong></p>
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- Category List View -->
|
<!-- Category List View -->
|
||||||
@@ -75,9 +75,15 @@
|
|||||||
deleteRecipe: function(id) {
|
deleteRecipe: function(id) {
|
||||||
fetch("/api/delete/" + id)
|
fetch("/api/delete/" + id)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => console.log("Deleted :" + data))
|
.then((data) => {
|
||||||
.catch((err) => console.error(err));
|
if (data === true) {
|
||||||
|
this.items.splice(this.active_view, 1);
|
||||||
this.closeActiveView();
|
this.closeActiveView();
|
||||||
|
console.log("Deleted :" + data);
|
||||||
|
} else {
|
||||||
|
console.log("Error deleting");
|
||||||
|
}})
|
||||||
|
.catch((err) => console.error(err));
|
||||||
},
|
},
|
||||||
fetchRecipesList: function() {
|
fetchRecipesList: function() {
|
||||||
fetch("/api/list")
|
fetch("/api/list")
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ mod api {
|
|||||||
id: rec.id,
|
id: rec.id,
|
||||||
title: rec.title,
|
title: rec.title,
|
||||||
category: rec.category as i16,
|
category: rec.category as i16,
|
||||||
ingredients: rec.ingredients,
|
ingredients: rec.ingredients.as_string(),
|
||||||
preparation: rec.preparation,
|
preparation: rec.preparation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user