const { sequelize, Cat } = require("./models");
async function main() {
const cat = Cat.build({
firstName: "Markov The Magnificent",
specialSkill: "sleeping",
age: 5,
});
try {
// Try to save cat to the database.
await cat.save();
console.log("Save success!");
console.log(JSON.stringify(cat, null, 2));
} catch (err) {
console.log("Save failed!");
// Print list of errors.
for (const validationError of err.errors) {
console.log("*", validationError.message);
}
}
await sequelize.close();
}
main();
```
We will be told:
```
Save failed!
* firstName must not be more than eight letters long
```
The `len` validation gets a `msg` attribute as usual. We also configure
`args: [0, 8]`. These are the "arguments" to the `len` validation. We
are telling Sequelize to trigger a validation error if the `firstName`
property has a length less than zero (impossible) or greater than eight.
Note that even though the `len` validation is not triggered for a length
of zero, the `notEmpty` validation still will be.
If desired, we could use the `len` validation to set a true minimum
length for a string. If we wanted a minimum length of two letters, we
would just change `args: [2, 8]`. (We ought also update the `msg`
appropriately.)
## Validating That A Numeric Value Is Within A Specified Range
A `Cat` should never have a negative age. Perhaps, also, a `Cat` should
have a theoretical maximum age of 99 years. We can add validations to
enforce these requirements:
```javascript
// ./models/cat.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Cat = sequelize.define('Cat', {
// ...
age: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: "age must not be null",
},
min: {
args: [0],
msg: "age must not be less than zero",
},
max: {
args: [99],
msg: "age must not be greater than 99",
},
},
},
}, {});
Cat.associate = function(models) {
// associations can be defined here
};
return Cat;
};
```
You can see that the `min` and `max` validations are configured in the
same sort of way that the `len` validation is.
If we try to save a `Cat` with an `age` of `-1`, we are printed:
```
Save failed!
* age must not be less than zero
```
Likewise, if we try to save a `Cat` with an `age` of `123` we are
printed:
```
Save failed!
* age must not be greater than 99
```
*(Note: I've stopped repeating our `index.js` file, since there are only
trivial modifications to a `Cat`'s attributes each time.)*
## Validating That An Attribute Is Among A Finite Set Of Values
Let's say that a `Cat`'s `specialSkill` should be restricted to a
pre-defined list of `["jumping", "sleeping", "purring"]`. That is: a
`Cat` should not be allowed to have just any `specialSkill`. The
`specialSkill` must be on the list.
We can enforce this requirement like so:
```javascript
// ./models/cat.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Cat = sequelize.define('Cat', {
// ...
specialSkill: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: "specialSkill must not be null",
},
notEmpty: {
msg: "specialSkill must not be empty",
},
isIn: {
args: [["jumping", "sleeping", "purring"]],
msg: "specialSkill must be either jumping, sleeping, or purring",
},
},
},
// ...
}, {});
Cat.associate = function(models) {
// associations can be defined here
};
return Cat;
};
```
Notice how we **doubly-nest** the list of special skills (`["jumping,
"sleeping", "purring"]`) when specifying the `args` for the `isIn`
validation. This is because we want to pass **one** argument: an array
of three possible special skills.
Now when we try to save a `Cat` with `specialSkill` set to `"pearl
diving"`, our code will print:
```
Save failed!
* specialSkill must be either jumping, sleeping, or purring
```
## Conclusion
There is a very large variety of validations that are provided by
Sequelize. You can find many more in the Sequelize [documentation for
validations][validation-docs].
[validation-docs]: https://sequelize.org/v5/manual/models-definition.html#validations
Having completed this reading, you now know how to:
1. Validate that an attribute is not set to `NULL`.
2. Validate that a string attribute is not set to the empty string `""`.
3. Validate that a string attribute is not too long (has too many
characters).
3. Validate that a numeric attribute meets minimum or maximum
thresholds.
4. Validate that an attribute is within a limited set of options.
________________________________________________________________________________
# Transactions With Sequelize
In this reading, we will learn about database _transactions_ and how we
use them via Sequelize. We will learn how to group multiple update
operations into a single atomic, indivisible unit.
At the end of the reading, you should know:
1. How to write code that is resilient to SQL operation failures,
1. How to group multiple operations into a database transaction using
Sequelize (the `sequelize.transaction` method),
1. How to prevent "race conditions" using transactions.
## The Problem: Database Updates Can Fail
Imagine a scenario with a banking database. Markov wants to transfer
$7,500 to Curie. To perform the transfer, we will perform two database
update operations:
1. Reduce Markov's account balance by $7,500,
2. Increase Curie's account balance by $7,500.
When transferring money, it's very important that *both* operations be
performed. If we reduce Markov's balance but fail to increase Curie's
balance, the bank effectively steals money from Markov. If we increase
Curie's balance without reducing Markov's balance, the bank effectively
gives away free money to Curie. Neither is acceptable.
We must keep in mind that any attempt to perform a database update can
sometimes _fail_. It can fail for a number of reasons:
1. The command is sent, but the database has previously been shut down
by the database administrator. Because the database is not running,
the database is not listening for our update, won't receive it, and
thus won't process it.
2. A bug in the database or operating system software has caused the
database or operating system to crash. Again, the database is not
running, so it can neither receive nor process our update.
3. Power has been lost to the machine running the database. The database
is not running.
4. The internet connection that connects us to the database machine is
disrupted. The database may be running and listening for SQL requests
to process. However, our update request cannot get through to the
database machine. Because the database cannot receive our request,
the database cannot process it.
5. The update asks the database to violate a pre-defined constraint. For
example: the database may have a constraint that an account balance
must never be less than zero. Any update that asks the database to
reduce an account balance to less than zero will be rejected and
therefore fail.
Only this last scenario is "our fault." The fact is that database
updates can fail **through no fault of our own**. With regard to our
example: our first SQL request to reduce Markov's account balance may
succeed, but the database may then crash before we have sent the request
to increase Curie's balance. Through no fault of our own, the bank has
stolen money from Markov without giving it to Curie.
How can we write code that avoids this fundamental problem?
## The Solution: Database Transactions
One way to solve the problem is to "group" or "pair" the two update
operations somehow. We want to tell the database "Reduce Markov's
balance AND increment Curie's balance." We want to tell the database:
"If for any reason you cannot perform **both** operations, make sure not
to perform **either**." We want to tell the database: "If you crash
after reducing Markov's balance, make sure to either (a) increase
Curie's balance when you restart, or (b) undo the increase to Markov's
balance when you restart."
We want to ask the database to treat the pair of updates as one _atomic_
(meaning **indivisible**) unit. SQL lets you do this using a feature
called _transactions_.
You've previously seen how to use SQL transactions:
```sql
START TRANSACTION;
-- Reduce Markov's balance by $7500
UPDATE "BankAccounts" SET balance = balance - 7500 WHERE id = 1;
-- Increment Curie's balance by $7500
UPDATE "BankAccounts" SET balance = balance + 7500 WHERE id = 2;
COMMIT TRANSACTION;
```
SQL guarantees to you that everything between `START TRANSACTION` and
`COMMIT TRANSACTION` will be processed _atomically_. If any update
operation fails, none of the updates will be performed. The transaction
is "all-or-nothing."
In this reading you will learn how to use SQL transactions with the
Sequelize ORM.
## The `BankAccounts` Schema
For our example in this reading, I will use a single table with two
accounts.
```
catsdb=> SELECT * FROM "BankAccounts";
id | clientName | balance | ...
----+------------+---------+-----
1 | Markov | 5000 | ...
2 | Curie | 10000 | ...
(2 rows)
```
I have generated a Sequelize model corresponding to the `BankAccounts`
table:
```javascript
// ./models/bank_account.js
'use strict';
module.exports = (sequelize, DataTypes) => {
// Define BankAccount model.
const BankAccount = sequelize.define('BankAccount', {
// Define clientName attribute.
clientName: {
type: DataTypes.STRING,
allowNull: false,
// Define validations on clientName.
validate: {
// clientName must not be null.
notNull: {
msg: "clientName must not be NULL",
},
// clientName must not be empty.
notEmpty: {
msg: "clientName must not be empty",
},
},
},
// Define balance attribute.
balance: {
type: DataTypes.INTEGER,
allowNull: false,
// Define validations on balance.
validate: {
// balance must not be less than zero.
min: {
args: [0],
msg: "balance must not be less than zero",
},
},
},
}, {});
return BankAccount;
};
```
Notice that the `min` validation on `balance` will not allow us to save
an account balance that is below zero.
## Example: An Update Fails Because Of Validation Failure
Let's imagine that Markov wants to transfer $7,500 to Curie.
Unfortunately, Markov has only $5,000 in his account! Decrementing
Markov's account balance by $7,500 would put it in the negative, which
our validation will not allow. Thus the transfer must fail.
Imagine that Markov is unaware that his account balance cannot cover the
transfer. He tries to perform the transfer anyway:
```javascript
// ./index.js
const { sequelize , BankAccount } = require("./models");
// This code will try to transfer $7,500 from Markov to Curie.
async function main() {
// Fetch Markov and Curie's accounts.
const markovAccount = await BankAccount.findByPk(1);
const curieAccount = await BankAccount.findByPk(2);
try {
// Increment Curie's balance by $7,500.
curieAccount.balance += 7500;
await curieAccount.save();
// Decrement Markov's balance by $7,500.
markovAccount.balance -= 7500;
await markovAccount.save();
} catch (err) {
// Report if anything goes wrong.
console.log("Error!");
for (const e of err.errors) {
console.log(
`${e.instance.clientName}: ${e.message}`
);
}
}
await sequelize.close();
}
main();
```
Running this code prints the following:
```
Executing (default): SELECT "id", "clientName", "balance", "createdAt", "updatedAt" FROM "BankAccounts" AS "BankAccount" WHERE "BankAccount"."id" = 1;
Executing (default): SELECT "id", "clientName", "balance", "createdAt", "updatedAt" FROM "BankAccounts" AS "BankAccount" WHERE "BankAccount"."id" = 2;
Executing (default): UPDATE "BankAccounts" SET "balance"=$1,"updatedAt"=$2 WHERE "id" = $3
Error!
Markov: balance must not be less than zero
```
Everything starts out fine. We fetch Markov and Curie's accounts. We
increase Curie's balance. But then we hit a snag: when we call
`markovAccount.save()`, Sequelize detects that we are trying to set
Markov's balance below zero. Sequelize therefore **does not** send a SQL
request to update Markov's account balance. Instead,
`markovAccount.save()` throws an exception. We print the error: Markov's
balance must not be less than zero.
We thus avoid saving a negative balance for Markov. But other damage has
already been done. If we now check account balances, we will see:
```
catsdb=> SELECT * FROM "BankAccounts";
id | clientName | balance | ...
----+------------+---------+-----
1 | Markov | 5000 | ...
2 | Curie | 17500 | ...
(2 rows)
```
The bank has given free money to Curie! We should have "rolledback" the
increase of Curie's balance. We will learn how to do that!
## Incorrect Solutions
One may suggest a fix: make sure to decrement Markov's account balance
before incrementing Curie's balance! If Markov's balance is
insufficient, we can stop the transfer before giving Curie any money.
We *could* swap the order of the updates, and it would indeed fix this
specific problem. But imagine if Markov tries to transfer $2,500 (an
amount he can afford). We first decrement Markov's account balance and
then -- the operating system crashes before the second update can be
submitted. Curie's balance is not incremented. The bank has stolen
Markov's money!
The problem is fundamental: no matter what order we perform the two
updates in, the database can always fail *after* processing the first,
but *before* processing the second. For our code to be resilient to
unavoidable failures, there is no other choice but to use a database
transaction.
## Using A Database Transaction With Sequelize
Let's return to our previous example of trying to transfer $7,500 from
Markov to Curie. Specifically, we will rewrite this key part:
```javascript
// Increment Curie's balance by $7,500.
curieAccount.balance += 7500;
await curieAccount.save();
// Decrement Markov's balance by $7,500.
markovAccount.balance -= 7500;
await markovAccount.save();
```
To ask Sequelize to perform the two updates in a SQL database
transaction, we use the `sequelize.transaction` method. We will write
this like so, instead:
```javascript
await sequelize.transaction(async (tx) => {
// Increment Curie's balance by $7,500.
curieAccount.balance += 7500;
await curieAccount.save({ transaction: tx });
// Decrement Markov's balance by $7,500.
markovAccount.balance -= 7500;
await markovAccount.save({ transaction: tx });
});
```
Let's go through the transaction code and explain each part:
```javascript
// Start a transaction. Queries run inside the callback can be part of
// the transaction.
await sequelize.transaction(async (tx) => {
// Increment Curie's balance by $7,500.
curieAccount.balance += 7500;
// Pass the `tx` transaction object so that Sequelize knows to
// update Curie's account as part of this transaction (rather than
// "on its own" per usual).
await curieAccount.save({ transaction: tx });
// Decrement Markov's balance by $7,500.
markovAccount.balance -= 7500;
// Again, pass the `tx` transaction object. Thus both updates are part
// of the same transaction.
await markovAccount.save({ transaction: tx });
// If no exceptions have been thrown, `sequelize.transaction` will
// `COMMIT` the transaction after the end of the callback.
//
// If any error gets thrown, `sequelize.transaction` will abort
// the transaction by issuing a `ROLLBACK`. This will cancel all
// updates.
});
```
Let's put the transaction code back into our original program:
```javascript
// ./index.js
const { sequelize, BankAccount } = require("./models");
async function main() {
// Fetch Markov and Curie's accounts.
const markovAccount = await BankAccount.findByPk(1);
const curieAccount = await BankAccount.findByPk(2);
try {
await sequelize.transaction(async (tx) => {
// Increment Curie's balance by $7,500.
curieAccount.balance += 7500;
await curieAccount.save({ transaction: tx });
// Decrement Markov's balance by $7,500.
markovAccount.balance -= 7500;
await markovAccount.save({ transaction: tx });
});
} catch (err) {
// Report if anything goes wrong.
console.log("Error!");
for (const e of err.errors) {
console.log(
`${e.instance.clientName}: ${e.message}`
);
}
}
await sequelize.close();
}
main();
```
Running this code prints:
```
Executing (default): SELECT "id", "clientName", "balance", "createdAt", "updatedAt" FROM "BankAccounts" AS "BankAccount" WHERE "BankAccount"."id" = 1;
Executing (default): SELECT "id", "clientName", "balance", "createdAt", "updatedAt" FROM "BankAccounts" AS "BankAccount" WHERE "BankAccount"."id" = 2;
Executing (208b3951-9ab9-489b-97f0-afb49aaff807): START TRANSACTION;
Executing (208b3951-9ab9-489b-97f0-afb49aaff807): UPDATE "BankAccounts" SET "balance"=$1,"updatedAt"=$2 WHERE "id" = $3
Executing (208b3951-9ab9-489b-97f0-afb49aaff807): ROLLBACK;
Error!
Markov: balance must not be less than zero
```
Let's review what happened. We again start by fetching both
`BankAccount`s. We next `START TRANSACTION`. We issue the update to
Curie's account.
Then Sequelize detects the validation failure when trying to run
`markovAccount.save({ transaction: tx })`. Markov doesn't have enough
money in his account! Sequelize throws an exception. The
`sequelize.transaction` method *catches the exception* and issues a
`ROLLBACK` for the transaction. This tells the database to undo the
prior increment of Curie's account balance.
Having rolled back the transaction, the `sequelize.transaction`
method *rethrows* the error, so that our logging code gets a chance to
learn about the error and print its details.
## Aside: What Is The `Transaction` Object?
_This is bonus information in case you are troubled by what the `tx`
parameter to `sequelize.transaction` is for. You can use transactions
correctly without knowing this bonus information._
What is the mysterious `tx` that is passed by `sequelize.transaction`
to our callback? It is basically just a unique ID. In this case, the ID
is: `208b3951-9ab9-489b-97f0-afb49aaff807`. You can see this ID in the
logs above.
When we say `curieAccount.save({ transaction: tx })`, we are telling
Sequelize: "update Curie's account as part of transaction number
`208b3951-9ab9-489b-97f0-afb49aaff807`."
Sequelize needs transaction IDs because it can be running many SQL
transactions _concurrently_ (loosely speaking: "in parallel"). One part
of the application could be transferring money from Markov to Curie at
the same time another part of the application is transferring money from
Kate to Ned.
If Sequelize did not keep track of transaction IDs, it would not know
that `curieAccount.save()` should be a part of the Markov/Curie
transaction rather than the Kate/Ned transaction.
## Transactions Prevent _Race Conditions_
There is still a subtle mistake in my bank transfer code. There is a
potential problem if someone modifies Markov's or Curie's account in
between (1) the initial fetch of their accounts, and (2) the transaction
to update the accounts.
```javascript
// ./index.js
async function main() {
// I will transfer only $5,000 so that Markov's balance can cover the
// amount. Markov starts out with $5,000.
// Fetch Markov and Curie's accounts.
const markovAccount = await BankAccount.findByPk(1);
const curieAccount = await BankAccount.findByPk(2);
// ***
// Imagine that right now some other program transfers the $5,000 out
// of Markov's account. Markov's true account **in the database** now
// has a balance of $0. But `markovAccount.balance` is still $5,000,
// because we fetched Markov's `BankAccount` **before** the transfer
// was made!
// ***
try {
await sequelize.transaction(async (tx) => {
// Increment Curie's balance by $5,000 (to $15,000).
curieAccount.balance += 5000;
await curieAccount.save({ transaction: tx });
// Decrement `markovAccount.balance` by $5,000.
// `markovAccount.balance` is set to zero.
markovAccount.balance -= 5000;
// Save and set Markov's balance to zero.
await markovAccount.save({ transaction: tx });
// Problem: Markov's balance in the database was *already* zero.
// Markov had no money to transfer. He should not have been able
// to transfer the $5,000.
});
} catch (err) {
// ...
}
await sequelize.close();
}
main();
```
Because another program can "race in between" (1) the reading of the
account balances and (2) the updating of the balances, we call this
potential problem a _race condition_. The easiest way to fix the race
condition is to prohibit anyone else from modifying Markov's account
balance in between (1) and (2).
Luckily, the solution is simple. Any data used in a transaction will be
_locked_ until the transaction completes. Data that is locked can be
neither read nor written by other transactions. If our transaction reads
(or writes) data, no one else can read or write that data until our
transaction completes. When we `COMMIT` (or `ROLLBACK`) all the locked
data is freed (the locks are _released_).
We don't have to lock the data ourselves. Simply by doing all our
queries inside the same transaction, the database will lock the data for
us. Therefore, to fix the problem, we should move the initial account
fetching by `findByPk` into the transaction (i.e., pass it `{
transaction: tx }`):
```javascript
async function main() {
try {
// Do all database access within the transaction.
await sequelize.transaction(async (tx) => {
// Fetch Markov and Curie's accounts.
const markovAccount = await BankAccount.findByPk(
1, { transaction: tx },
);
const curieAccount = await BankAccount.findByPk(
2, { transaction: tx }
);
// No one can mess with Markov or Curie's accounts until the
// transaction completes! The account data has been locked!
// Increment Curie's balance by $5,000.
curieAccount.balance += 5000;
await curieAccount.save({ transaction: tx });
// Decrement Markov's balance by $5,000.
markovAccount.balance -= 5000;
await markovAccount.save({ transaction: tx });
});
} catch (err) {
// ...
}
await sequelize.close();
}
main();
```
This prints:
```
Executing (76321a03-93c5-47c0-861a-cf24c3e6f3bf): START TRANSACTION;
Executing (76321a03-93c5-47c0-861a-cf24c3e6f3bf): SELECT "id", "clientName", "balance", "createdAt", "updatedAt" FROM "BankAccounts" AS "BankAccount" WHERE "BankAccount"."id" = 1;
Executing (76321a03-93c5-47c0-861a-cf24c3e6f3bf): SELECT "id", "clientName", "balance", "createdAt", "updatedAt" FROM "BankAccounts" AS "BankAccount" WHERE "BankAccount"."id" = 2;
Executing (76321a03-93c5-47c0-861a-cf24c3e6f3bf): UPDATE "BankAccounts" SET "balance"=$1,"updatedAt"=$2 WHERE "id" = $3
Executing (76321a03-93c5-47c0-861a-cf24c3e6f3bf): UPDATE "BankAccounts" SET "balance"=$1,"updatedAt"=$2 WHERE "id" = $3
Executing (76321a03-93c5-47c0-861a-cf24c3e6f3bf): COMMIT;
```
Notice that now *everything* is done in the transaction
`76321a03-93c5-47c0-861a-cf24c3e6f3bf`. This includes the initial
fetching of the accounts. Because the fetching is done within the
transaction, other users are not allowed to modify the accounts until
the transaction is finished.
Moving our read operations into the transaction has solved our race
condition problem. Every Sequelize operation - whether reading or
writing - can take a `transaction: tx` option. This includes:
1. `findByPk`
1. `findAll`
1. `save`
1. `create`
1. `destroy`
## Conclusion
Having completed this reading, here are the important things to take
away:
1. You should know that any SQL update operation may fail, often through
no fault of your own.
1. You should know that you must write code resilient to SQL failures.
1. You should know that when performing multiple update operations as
part of a group, you must use a transaction.
1. You should know how to use `sequelize.transaction` to run commands
within a SQL transaction.
1. You should know how to pass a transaction object as the
`{ transaction: tx }` parameter to a Sequelize command (such as
`save`).
1. You should know what a _race condition_ is: the possibility that
someone else modifies previously fetched data before you are finished
using/updating it.
1. You should know how to use transactions to guard against race
conditions. That is: you should know that both reading and writing
operations should be put in the same transaction.
________________________________________________________________________________
# Recipe Box With Sequelize Project
In this project, you will build the Data Access Layer to power a Web
application. Unlike previously, you will use the Sequelize library and tools to
do this to build a more maintainable application.
It has more steps than the SQL version, but it's more maintainable in the long
run. Also, the SQL version hid a lot of complexity from you with respect to the
running of the SQL. Go look at the SQL version of the files in the
**controllers** directory to see what we had to do to load the SQL and execute
it.
Now, compare the _simplicity_ of those with the simplicity of the files in the
**controllers** directory for _this_ version of the application. It's easier to
understand _this_ version. You want to know where to add a column to a table?
Go to the migrations. You want to know where to fix a query? Go to the proper
repository file.
It's just _so_ much better organized.
Quite often, you will see that you will have more files and, overall, more lines
of code in well-organized, highly-maintainable software project. Remembering
where code is _is hard_. That's why having clearly-named files and directories
is so very important.
## The data model analysis
This looks no different because it's the same application.
What goes into a recipe box? Why, recipes, of course! Here's an example recipe
card.
![Recipe card]
You can see that a recipe is made up of three basic parts:
* A title,
* A list of ingredients, and
* A list of instructions.
You're going to add a little more to that, too. It will also have
* The date/time that it was entered into the recipe box, and
* The date/time it was last updated in the recipe box.
These are good pieces of data to have so that you can show them "most recent"
for example.
Ingredients themselves are complex data types and need their own structure. They
"belong" to a recipe. That means they'll need to reference that recipe. That
means an ingredient is made up of:
* An amount (optional),
* A unit of measure (optional),
* The actual food stuff, and
* The id of the recipe that it belongs to.
That unit of measure is a good candidate for normalization, don't you think?
It's a predefined list of options that should not change and that you don't want
people just typing whatever they want in there, not if you want to maintain
data integrity. Otherwise, you'll end up with "C", "c", "cup", "CUP", "Cup", and
all the other permutations, each of which is a distinct value but means the same
thing.
Instructions are also complex objects, but not by looking at them. Initially,
one might only see text that comprises an instruction. But, very importantly,
instructions have _order_. They also _belong_ to the recipe. With that in mind,
an instruction is made up of:
* The text of the instruction,
* The order that it appears in the recipe, and
* The id of the recipe that it belongs to.
That is enough to make a good model for the recipe box.
![recipe box data model]
## The application
The application is a standard [express.js] application using the [pug] library
to generate the HTML and the [node-postgres] library to connect to the database.
It already has [sequelize] and [sequelize-cli] installed.
## Getting started
* Clone the starter project from
https://github.com/appacademy-starters/sql-orm-recipe-box
* Run `npm install` to install the packages
* Run `npm run dev` to start the server on port 3000
You'll do all of your work in the **data-access-layer** directory. In there, you
will find a series of JS files. Each of these will hold your JavaScript code
rather than SQL code.
## Your code
You're going to be using JavaScript and the tools of Sequelize. Keep the
[Sequelize documentation] open and handy. Even developers that use ORMs every
day will keep the documentation open because there's so much to know about them.
## Phase 1: Initialize the Sequelize project
Because this project already has [sequelize-cli] installed, you can initialize
the project by typing `npx sequelize-cli init`. The `npx` command runs
locally-installed tools. That will create the project structure that Sequelize
expects for us to continue to use its tools.
## Phase 2: Create a database user for the project
Using a PostgreSQL client like `psql` or Postbird, create a new user for this
application named "sequelize_recipe_box_app" with the password "HfKfK79k" _and_
the ability to create a database. Here's the [link to the CREATE USER
documentation] so that you can determine which options to give.
## Phase 2: Change the connection configuration
The project contains a directory named **config**. Inside there, you will find a
file named **config.json**. You need to make some configuration changes.
* Change all the "user" and "password" values to the information for the user
that you created in Phase 2.
* Change the "database" values to be "recipe_box_development",
"recipe_box_test", and "recipe_box_production".
* Change all of the "dialect" values from "mysql" to "postgres".
* Delete all of the "operatorAliases" entries. It's to support earlier versions
of the Sequelize library. Make sure to remove the comma from the preceding
line so that it's valid JSON.
* Because you'll be using seed data in this project, add `"seederStorage":
"sequelize"` to each of the different blocks so that Sequelize CLI won't run
a seeder more than once causing duplicate entries in the database.
That will configure the application and the Sequelize tools to properly connect
to your development database.
## Phase 3: Create your database
Rather than writing SQL to do this, you will use the tools. Run
```
npx sequelize-cli db:create
```
That runs the Sequelize CLI with the command `db:create`.
When you run this, it will default to the "development" setting and read the
information from the configuration file to create your database for you! It
should print out something like
```
Sequelize CLI [Node: 10.19.0, CLI: 5.5.1, ORM: 5.21.5]
Loaded configuration file "config/config.json".
Using environment "development".
Database recipe_box_development created.
```
You can also drop the database by typing ... you guessed it! The Sequelize CLI
with the command `db:drop`!
```
npx sequelize-cli db:drop
```
If you run that, run the "create" command, again, so the database exists.
## Phase 4: The units of measurement data
Just as a review, here is the specification for the table that holds units of
measurement.
| Column Name | Column Type | Constraints |
|-------------|-------------|-------------|
| id | SERIAL | PK |
| name | VARCHAR(20) | NOT NULL |
Luckily, the Sequelize models and migrations take care of the "id" property for
you without you having to do anything. So, you can just focus on that "name"
property.
### Create a migration
It's time to create the first migration, the one that defines the table that
will hold units of measure. You can use the Sequelize CLI to generate the
migration for you. You can _also_ tell it to create a model for you, and it will
create a migration along _with_ the model. You should do that to get the biggest
return on investment for the characters that you will type.
The command is `model:generate` and it takes a couple of arguments, "--name"
which contains the name of the model (as a singular noun) to generate,
and "--attributes" which has a comma-separated list of "property-name:data-type"
pairs.
**Learning Tip**: It is _so very important_ that you don't copy and paste this.
Type these things out so it has a better chance of creating durable knowledge.
```
npx sequelize-cli model:generate \
--name MeasurementUnit \
--attributes name:string
```
That will create two files, if everything works well. (The name of your
migration file will be different because it's time-based.)
```
New model was created at models/measurementunit.js
New migration was created at migrations/20200101012349-MeasurementUnit.js
```
The **model** file will be used by the application to query the database. It
will be used by the express.js application. It is part of the running software.
The **migration** file is used to construct the database. It is only used by the
Sequelize CLI tool to build the database. Unlike those schema and seed files
that you had in the SQL version of this project which destroyed _everything_
when run, migrations are designed to change your database as your application
grows. This is a much better strategy so that existing data in the databases
that other people use aren't damaged.
Because the data model requires the "name" column to be both non-null _and_
unique, you have to add some information to the migration file. Open it and, for
the "name" property, make non-nullable by looking at how the other properties
are configured. Then, add the "unique" property set to `true` to the "name"
configuration, as well. That should be enough for Sequelize to create the table
for you.
The last thing to do is to change the length of the "name" property. By default,
Sequelize will make it 255 characters long. The specification for the table
says it should really only be 20 characters. To tell the migration that, change
the type for "name" from `Sequelize.STRING` to `Sequelize.STRING(20)`.
## Run your migration
If you now run your migration with the Sequelize CLI, it will create the table
for you.
```
npx sequelize-cli db:migrate
```
That should give you some output that looks similar to this.
```
Loaded configuration file "config/config.json".
Using environment "development".
==<PHONE_NUMBER>2349-create-measurement-unit: migrating =======
==<PHONE_NUMBER>2349-create-measurement-unit: migrated (0.021s)
```
You can confirm that the table "MeasurementUnits" is created by using your
PostgreSQL client. You'll also see that another table is created,
"SequelizeMeta", which contains information about which migration has most
recently been run. It contains a single column, "name". Each row contains an
entry of which migration file has run. Now that you've run your migration file,
the table contains one entry, the name of your migration file. When you run
more migrations, you will see more rows, each containing the name of the file
that you've run.
**psql Note**: If you are using `psql` as you PostgreSQL command, be aware that
it will lowercase any entity and column names you type in there. If you type
`SELECT * FROM MeasurementUnits`, it converts that to `SELECT * FROM
measurementunits` before running it. To prevent that from happening, use
quotation marks around the table name. `SELECT * FROM "MeasurementUnits"` will
do the trick.
It's important that you _never_ change the name of a migration file after it's
been run.
In the real world, you should _never_ change the content of a migration file
after it's been committed and shared in your Git repository. Asking others to
rollback their migrations just because you changed one of yours is bad manners.
Instead, you should add a new migration that makes the change that you want.
## Create the seed data
You can create the seed data for the unit of measurements by creating a
**seeder** as the Sequelize CLI calls them. You can create one using the
Sequelize CLI tool. Run the following and make sure you don't get any errors.
```
npx sequelize-cli seed:generate --name default-measurement-units
```
Now, you want to insert the seed data. You will do this by using the
`bulkInsert` method of the object passed in through the `queryInterface`
parameter of the `up` method. Feel free to delete the comment in the `up` method
and replace it with this.
```js
return queryInterface.bulkInsert('MeasurementUnits', [
{ name: 'cups', createdAt: new Date(), updatedAt: new Date() },
]);
```
The `bulkInsert` method takes two parameters:
* The name of the table to insert into, and
* An array of objects that have property names that match the column names in
the table.
You can see that the first object has been provided by the example. Now, create
objects for all of these values, as well. (The empty item in the list is an
empty string and is intentional) Make sure you do them **in this order,** or
when we get to the seed data for the other tables it won't work. (We've supplied
you with files for the seed data for the other tables because there is a lot of
it)
* "fluid ounces"
* "gallons"
* "grams"
* "liters"
* "milliliters"
* "ounces"
* "pinch"
* "pints"
* "pounds"
* "quarts"
* "tablespoons"
* "teaspoons"
* ""
* "cans"
* "slices"
* "splash"
Now, run the Sequelize CLI with the command `db:seed:all`.
After you get that done, you can confirm that all of the records (rows) were
created in the "MeasurementUnits" table.
## Phase 5: The recipe table model
This will go much like the last one, except there's no seed data. Just to
refresh your memory, here's the specification for the "recipes" table.
| Column Name | Column Type | Constraints |
|-------------|--------------|-------------------------------------|
| id | SERIAL | PK |
| title | VARCHAR(200) | NOT NULL |
| created | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP |
| updated | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP |
As you've discovered, Sequelize takes care of the "id" for you _and_ the columns
to track when the recipe has been created and updated! Your job is to
* Generate a model for the "recipe"
* Customize the migration so the "title" column is not nullable
Run your migration and confirm that you defined it correctly by checking the
attributes in the description of the table. The important parts to check are
that the "title" column is a VARCHAR(200) and is non-nullable. (The "Collation"
column has been removed for brevity.)
```
Table "public.Recipes"
Column | Type | Nullable | Default
-----------+--------------------------+----------+------------
id | integer | not null | nextval(...
title | character varying(200) | not null |
createdAt | timestamp with time zone | not null |
updatedAt | timestamp with time zone | not null |
Indexes:
"Recipes_pkey" PRIMARY KEY, btree (id)
```
## Phase 6: The instruction table model
Now, things get a little trickier because this model will reference the recipe
model. Here's the specification for the "instructions" table.
| Column Name | Column Type | Constraints |
|---------------|-------------|--------------|
| id | SERIAL | PK |
| specification | TEXT | NOT NULL |
| listOrder | INTEGER | NOT NULL |
| recipeId | INTEGER | FK, NOT NULL |
When you type out your migration generation command, the "--attributes"
parameter will look like this:
```
--attributes column1:type1,column2:type2,column3:type3
```
Instead of using "string" for the "specification" column of the table, use
"text" to generate a TEXT column.
After it generates the migration file, modify each of the column descriptors in
the migration so that the columns are not nullable. Then, add a new property
to the one for "recipeId" called "references" that is an object that contains
a "model" property set to "Recipes". It should look like this.
```js
recipeId: {
allowNull: false,
references: { model: "Recipes" },
type: Sequelize.INTEGER,
},
```
With that in place, run the migration. Then, check the table definition in your
PostgreSQL client.
```
Table "public.Instructions"
Column | Type | Nullable | Default
---------------+--------------------------+----------+-----------------
id | integer | not null | nextval('"Ins...
specification | text | not null |
listOrder | integer | not null |
recipeId | integer | not null |
createdAt | timestamp with time zone | not null |
updatedAt | timestamp with time zone | not null |
Indexes:
"Instructions_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"Instructions_recipeId_fkey" FOREIGN KEY ("recipeId")
REFERENCES "Recipes"(id)
```
You should see all non-null columns and a foreign key between the "Instructions"
table and the "Recipes" table.
## Phase 7: The ingredients model
The model for ingredients has _two_ foreign keys. Create the model and migration
for it. Here's the table specification.
| Column Name | Column Type | Constraints |
|-------------------|---------------|--------------|
| id | SERIAL | PK |
| amount | NUMERIC(5, 2) | NOT NULL |
| measurementUnitId | INTEGER | FK, NOT NULL |
| foodStuff | VARCHAR(500) | NOT NULL |
| recipeId | INTEGER | FK, NOT NULL |
After you modify and run your migration, you should have a table in your
database that looks like this, with two foreign keys, one to the "Recipes"
table and the other to the "MeasurementUnits" table.
```
Table "public.Ingredients"
Column | Type | Nullable | Default
-------------------+--------------------------+----------+-----------------
id | integer | not null | nextval('"Ing...
amount | numeric(5,2) | not null |
measurementUnitId | integer | not null |
foodStuff | character varying(500) | not null |
recipeId | integer | not null |
createdAt | timestamp with time zone | not null |
updatedAt | timestamp with time zone | not null |
Indexes:
"Ingredients_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"Ingredients_measurementUnitId_fkey"
FOREIGN KEY ("measurementUnitId")
REFERENCES "MeasurementUnits"(id)
"Ingredients_recipeId_fkey"
FOREIGN KEY ("recipeId")
REFERENCES "Recipes"(id)
```
## Phase 8: Seed data for all of the tables
Now that you have tables in the database, it's time to create some seed data for
all of them. In the **data-access-layer** directory, you will find three text
files each containing JavaScript objects on each row that match the tables
in the previous three sections.
If you didn't seed the MeasurementUnits data in the correct order listed in the
section above, you may have to redo that seed file, because the data from the
text files depends on the ids of the data in the `MeasurementUnits` table being
correct.
There are three tables to seed: Ingredients, Instructions, and Recipes. It is
important to note that you will need to seed them in the correct order due to
foreign key dependencies.
Look at the data model for the application, again.
![recipe box data model]
You can see that the Instructions depends on Recipes because it has the foreign
key "recipeId" to the Recipes table. You can also see that the Ingredients table
has dependencies on the Recipes and MeasurementUnits tables because of its
foreign keys "measurementUnitId" and "recipeId". (You've already seeded the
MeasurementUnits table in Phase 4, so that data exists for use by the
Ingredients table.) Recipes does not have any foreign keys. You need to seed
Recipes, first, because it does not have any foreign keys and, therefore, does
not have any data dependencies. Then, you can seed the Instructions and
Ingredients tables in either order because their data dependencies will have
been met.
Create seeder files for them in that order: Recipes, first, then Ingredients and
Instructions. Use the contents of each of the text files in
**data-access-layer** to do bulk inserts.
After you create each seed file, run
```
npx sequelize-cli db:seed:all
```
to make sure you don't have any errors. If you do, fix them before moving onto
the next seed file.
If you end up seeding the data in the wrong order and getting a foreign key
constraint error, just use the CLI to drop the database, create the database,
migrate the database, and then you can try running your seeders, again. You may
need to rename your migration filenames to get your seeds running in the
correct order.
## Phase 9: Updating models with references
Now that you have all of the migrations set up correctly and a database defined,
it is time for you to turn your attention to the model files that were
generated in the previous phases.
Consider the relationship between an Instruction and a Recipe. A Recipe _has
many_ Instructions. In the other direction, you would say that an Instruction
_has one_ Recipe, or that Instruction _belongs to_ the Recipe. To set that up in
your model, open the file **models/recipe.js**. In there, you will see the
following.
```js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Recipe = sequelize.define('Recipe', {
title: DataTypes.STRING
}, {});
Recipe.associate = function(models) {
// associations can be defined here
};
return Recipe;
};
```
In the `associate` function is where you can define the association between the
Recipe and the Instruction. Replace the comment with the following statement.
```js
Recipe.hasMany(models.Instruction, { foreignKey: 'recipeId' });
```
This instructs Sequelize that Recipe should have a collection of Instruction
objects associated with it. To insure that Sequelize uses the foreign key column
that you created on the "Instructions" table in your migration, you must specify
it as part of the collection definition.
In the file **models/instruction.js**, replace the comment with the following to
define the other side of the relationship.
```js
Instruction.belongsTo(models.Recipe, { foreignKey: 'recipeId' });
```
This instructs Sequelize that Instruction has a single Recipe object associated
with it. Again, because of inconsistent naming conventions used by Sequelize,
you must specify the foreign key column name in the "Instructions" table.
Think about the many-to-one and one-to-many relationships between Ingredient,
MeasurementUnit, and Recipe. Then, modify those model files accordingly with
the `hasMany` and `belongsTo` associations, always specifying the name of the
foreign key column that binds the two tables together.
## Phase 10: Updating models with validations
Now that you have seed data created, it will be important to prevent users from
entering data that does not meet the expectations of the data model.
Consider the content of **models/instruction.js**
```js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Instruction = sequelize.define('Instruction', {
specification: DataTypes.TEXT,
listOrder: DataTypes.INTEGER,
recipeId: DataTypes.INTEGER
}, {});
Instruction.associate = function(models) {
Instruction.belongsTo(models.Recipe, { foreignKey: 'recipeId' });
};
return Instruction;
};
```
It would be nice if the model could validate each of those properties to make
sure that no one sets them to null and that `listOrder` is greater than 0, for
example. You can do that with [per-attribute validations].
For example, you can change the above code to the following to make sure that
the "specification" property won't get set to an empty string when someone tries
to save the object.
```js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Instruction = sequelize.define('Instruction', {
specification: {
type: DataTypes.TEXT,
validate: {
notEmpty: true,
},
},
listOrder: DataTypes.INTEGER,
recipeId: DataTypes.INTEGER
}, {});
Instruction.associate = function(models) {
Instruction.belongsTo(models.Recipe, { foreignKey: 'recipeId' });
};
return Instruction;
};
```
Make sure all of the other string properties in the models won't allow the empty
string to be set on them.
## Phase 11: Cascade delete for recipes
The Recipe model has dependencies: the Instruction and the Ingredient both have
_belongs to_ relationships. This means that the row in the "Recipes" table must
exist to have records in the "Ingredients" and "Instructions" table. If you try
to delete a Recipe row from the database that has either Instructions or
Ingredients, it won't work due to referential integrity. You would have to
delete all of the Ingredients and Instructions _before_ being able to delete the
Recipe.
Sequelize provides a handy shortcut for that and will manage deleting the
associated records for you when you delete a row from the Recipes table. It's
called a _cascading delete_. Open the **models/recipe.js** file. In there,
modify the second argument of each of the `hasMany` calls to include two new
property/value pairs:
* `onDelete: 'CASCADE'`
* `hooks: true`
Refer to the documentation on [Associations] to see an example. But, don't
delete the `foreignKey` property that you put there in Phase 9.
## Phase 12: Building the repositories
Now that you have the seeds, models, and migrations out of the way, you can
build the data access layer with a lot of speed. Sequelize will now handle all
of the SQL generation for you. You can just use the models that you've
painstakingly crafted.
Because you are writing JavaScript files, you want the server to restart because
it won't automatically reload the changed JavaScript that you're writing. To
that end, you will use a different command while developing.
```
npm run dev
```
This runs a special script that will reload the JavaScript in the data access
layer every time you make a change. You can see what's run in the
**package.json** file in this project in the "scripts" section for the "dev"
property.
You will work in the three files named
* **recipes-repository.js**: The collection of functions needed to interact with
recipes for the application
* **instructions-repository.js**: The collection of functions needed to interact
with the instructions for the application
* **ingredients-repository.js**: The collection of functions needed to interact
with the ingredients for the application
Each of the files imports your models and makes them available to you. Then,
you can use them in your querying. Follow the hints in each of the repository
functions.
[Recipe card]: images/sql-recipe-card.jpeg
[express.js]: https://www.expressjs.com
[pug]: https://pugjs.org
[node-postgres]: https://node-postgres.com
[Parameterized query]: https://node-postgres.com/features/queries#Parameterized%20query
[Sequelize documentation]: https://sequelize.org/v5/
[sequelize]: https://www.npmjs.com/package/sequelize
[sequelize-cli]: https://www.npmjs.com/package/sequelize-cli
[link to the CREATE USER documentation]: https://www.postgresql.org/docs/current/sql-createuser.html
[per-attribute validations]: https://sequelize.org/master/manual/validations-and-constraints.html#per-attribute-validations
[Associations]: https://sequelize.org/master/manual/hooks.html#associations
[recipe box data model]: images/sql-recipe-box-data-model.png


Anytime is Train Time
Shoo Shoo,Chee Chee!!.
Current Train Schedule
Train Name
Destination
Frequency(min)
Next Arrival
Minutes Away
Add Train
Train Name
Destination
First Train Time(HHmm-military time)
Frequency(min)
Submit


 The security was never that great, but once there were merchants and warehouses, security guards and adventurers... But that was a long time ago....
Now they're all gone! All that's left is this old, worn-out railroad station and the Bandits who rob anyone foolish enough to come here!
How would you like a job?
Tell me more about the Bandits.


(HTML (HEAD )HEAD (BODY (P "outer paragraph\n )P (P "inner paragraph (BR )BR "second line in inner paragraph\n )P "second line in outer paragrapth\n (P )P "outside paragraph tags )BODY )HTML


My name is Mario Jiménez U/I, I'm a Front end developer mainly focused on HTML, CSS and Javascript. I contribute regurlary to open source projects on Github like FreeCodeCamp or CodeforAmerica. I'm also a big fan of Science fiction.


{% load staticfiles %}
El Político
Map Influence Sources:
News Media
Twitter User Feed
Instagram Posts
About
Statistics Recorded
Percentage Total
Avg. Certainty
Positivity
Green Party
[NaN]
[NaN]
[NaN]
Liberal Party
[NaN]
[NaN]
[NaN]
Conservativism
[NaN]
[NaN]
[NaN]
Libertarianism
[NaN]
[NaN]
[NaN]


--- layout: post index: 2 tags: Karos title: The Queue Line of K'aros excerpt: "For heavily themed coasters like K'aros, standing in line for the ride is an integral part of the experience. An interesting queue with good theming is important to built up excitement for the ride. In this blog post I will give you a complete tour of the heavily themed queue line of K'aros." reddit: "https://www.reddit.com/r/PlanetCoaster/comments/bak1o3/the_queue_line_of_karos_grindlheim_blog_update_2/" planco-forum: "https://forums.frontier.co.uk/threads/grindlheim-an-immersive-theme-world-inspired-by-klugheim-at-phantasialand.506455/post-7720360" number-of-images: 14 image-descriptions: - You may know these buildings from the last blog post. This is the main plaza where the queue line of K'aros begins. - The center of the first room that guests enter is themed with a huge machine that powers the launches of the coaster. - Moving parts like cogs and pistons and timed steam effects are part of this structure. - After the machine room the queue takes a 180° turn. This is located inside the tower-like building on the left of picture \#1. - Guests can take a last look onto the plaza before the queue leads further down inside the building. - The open windows of the following passage reveal the coaster and its large station building for the first time. K'aros' first launch is directly located above this section. - The passage is also parallel to the second launch of K'aros so that guests have an incredible view of the coaster rushing by. Next, the queue crosses over the launch via the little roofed bridge that can be seen on the left. - Following the bridge, crossing over K'aros and Ri'kh, guests then enter the basement of the station building... - ...which is home to the only small 'cattle pen' like line on K'aros' queue. The boarding station is located directly above this room. - Up next, the queue leaves the basement and hugs the outside wall of the station. The path leads up the stairs on the left. - The stairs curve to right following the station's wall up to the highest point of the queue while crossing K'aros again below and above. - Missing out on K'aros means missing out on incredible views of Grindlheim that can only be enjoyed from the queue line. We are close the end now. - One last turn right and a few steps done and we finally enter the station. - Marked in orange on this bird's eye view is the complete queue line. Most of it is hidden inside buildings and deeply integrated into the overall theming. ---
Welcome back to the second post here on the Grindlheim blog.
Today I want to show you the complete queue line of K'aros.
For heavily themed coasters like K'aros, standing in line for the ride is an integral part of the experience. Usually more time is spent in the queue than on the ride itself. Therefore an interesting queue layout and good theming are important to built up excitement for the ride.
Like at Klugheim at Phantasialand, I decided to not integrate pre-shows into my queues but try to make them interesting by layout, architecture, theming and music. While I'm currently making good progress on the queue for Ri'kh, K'aros' queue line is already finished and ready to be shown to you. As you might understand after viewing the 14 images I brought along for today, building K'aros' queue and theming it was not an easy task because most of it is hidden inside multi-story buildings. Furthermore, elevated sections cross the coasters K'aros and Ri'kh several times. I was struggling especially with theming curved stairs which I'm trying to avoid from now on.
Without further ado, please view the gallery below for a complete tour of the queue line of K'aros.


Insert substitution variables: I like {{flavor}} ice cream.
Make some content conditional: Your ice cream is {{if flavor == "strawberry"}} pink {{else}} cream{{end}}-colored.
Iterate through items in a list:
Inserting HTML using variables: {{{setMenuHTML}}}
Inserting HTML containing variables: {{render_dynamic_content(dynamic_html.setMenuHTMLWithSpecial)}}
Controlling links:


Was an American singer, actor and producer who was one of the most popular and influential musical artists of the 20th century. He is one of the best-selling music artists of all time, having sold more than 150 million records worldwide.


|
Welcome to JS Bin!
Thanks for signing up. To get familiar with JS Bin, check out our help and follow @js_bin on Twitter.
{{#if code}}
As a special gift, I'm offering you 60 days free Pro – total saving of over $100(!) when you subscribe to a yearly pro account with the special code: {{code}} (valid for a month from today).
{{else}}
If you want to power-up your JS Bin usage, you can upgrade your account to Pro today, and you'll have access to unlimited private bins, asset uploading, experimental features and more.
{{/if}}
Thanks again,
– Remy (creator of JS Bin)
|


{% extends 'base.html' %} {% block content %}
Ok, we believe you, {{ user }}! Your summoner account is linked, now set your password.


--- layout: archive ---
Gabrielle investigates the role technology plays in shaping perception and the possibilities of knowledge production. With a focus on language technologies and digital fabrication, she designs and builds systems that question practices of sense-making with and through machines. She is affiliated to the TAT Lab and Machine Agency in HCDE, to the DXARTS SoftLab and Studio Tilt in Design.
Prior to attending graduate school, Gabrielle worked as a writer and conversation designer for Alexa at Amazon. She also worked as a video producer and bilingual writer for various tech organizations in Montréal, QC. She loves cinema, philosophy, literature and conducting alchemical experiences in her kitchen, which some call cooking.


public interface XSImplementation
XSLoader
.
This interface should be implemented on the same object that implements
DOMImplementation.| Modifier and Type | Method and Description |
|---|---|
LSInputList |
createLSInputList(LSInput[] values)
Creates an immutable
LSInputList from the given array of LSInput s. |
StringList |
createStringList(String[] values)
Creates an immutable
StringList from the given array of String s. |
XSLoader |
createXSLoader(StringList versions)
Creates a new XSLoader.
|
StringList |
getRecognizedVersions()
A list containing the versions of XML Schema documents recognized by
this
XSImplemenation . |
StringList getRecognizedVersions()
XSImplemenation
.XSLoader createXSLoader(StringList versions) throws XSException
versions
- A list containing the versions of XML Schema
documents which can be loaded by the XSLoader
or
null
to permit XML Schema documents of any recognized
version to be loaded by the XSLoader.XSException
- NOT_SUPPORTED_ERR: Raised if the implementation does not support one
of the specified versions.StringList createStringList(String[] values)
StringList
from the given array of String
s.values
- the array containing the String
values that will be placed in the list.StringList
from the given array of String
s.LSInputList createLSInputList(LSInput[] values)
LSInputList
from the given array of LSInput
s.values
- the array containing the LSInput
values that will be placed in the list.LSInputList
from the given array of LSInput
s.Copyright © 2023. All rights reserved.


4. Arduino based Wireless Sensor Network for precision Agriculture Monitoring (KrushiSense/SenseTube++)¶
Application Domain¶
- Agriculture / Horticulture
Reasearch Domain¶
- E&TC,
- Networking
Data / tools required¶
- Microcontrollers
- SOC computers, etc.
Synopsys¶
Low cost open HW & SW based standardized, interoperable system and service.
<add SenseTube images, list of publications, etc.>
Expected Deliverables¶
- Sensing and analysis platfrom
- Real time location based monitoring and agriculture management components for
- Crop water management
- Crop pest/disease management
- Market analysis
Status¶
- Framework and initial field tests completed during PhD project
To do¶
- Add UI/UX components
- Finished product
Intellectual property details¶
- Author/s: Suryakant Sawant, Surya Durbha, J. Adinarayana
- Date: 16 June 2016
- Details: Indian patent pending


My early years as a Research Scientist creating new products and process improvements developed my technical and analytical problem-solving skills.
Subsequent years gave me opportunities to cultivate my communication, leadership and team skills, leading to increased responsibilities and the reputation as a company expert in several technologies.
My lifelong passion for programming prompted me to initiate software projects whenever possible.
- Ruby
- Rails
- React
- Redux
- Github
- C
- C#
- VB
- Javascript
- SQL
- CSS
- HTML
- MVC
- .NET
- Entity Framework
- JQuery
- Bootstrap
- JSON
- AJAX
- Angular 2
- Typescript


Going Nowhere, Going Home
Review of Central do Brasil (1998, a/k/a Central Station; Portuguese with English subtitles)
Seen on Christmas Eve 1998 with Linda at the Lincoln Plaza Cinemas for $8.75
Big cities are renowned for grinding down the little people, and that goes triple for the third world. Much like the world of Bunuel's Mexico City in Los Olvidados, Rio de Janeiro is merciless. People are out for themselves and if you take a wrong turn, you are much more likely to be the victim of vigilante justice than police brutality.
Dora is a retired schoolteacher who supplements her pension by writing letters for illiterate peasants reporting home on their urban fortunes--sometimes lying, so no one will worry. They trust her to send the, but she often doesn't, playing God and judging which ones will reach their destination.
One of her patrons is killed by a bus, leaving her son Josue (Vinicius de Oliveira) to fend for himself on the streets. Dora takes pity on him and takes him in. Thinking she can get him adopted by rich foreigners, Dora sells him to a local thug, and she buys a new television.
Horrified to learn that he is more likely going to become an unwilling--and deceased--organ donor, she kidnaps him, and the two go on the run. Dora hopes to reunite him with his father in a remote rural outpost.
Fernanda Montenegro's Dora is both hard-bitten and vulnerable. It's easy to see the decent person underneath the her current cynical shell. De Oliveira, a nonactor, is wonderful as not just a helpless child, but as Dora's moral barometer. His outrage at her low ethics helps prompt her into better behavior.
What ensues is a journey "from nowhere to nowhere," as another critic put it; Josue's father has moved several times, and Dora pushes further into the frontier to find him. What happens is not predictable. Dora's metaphoric journey and inevitable decisions are selfless. Given the hopeless nature of Rio, her decisions are almost miraculous, as she gives back hope and faith despite crushing realities. It takes looking after a young boy for Dora to come to a major realization: "I long for my father; I long for everything." The letters dictated to her are more than messages; they are prayers.
Director Walter Salles Jr. takes us through the unfamiliar landscapes of urban and rural Brazil. Surrounded by natural splendors, we see the emptiness of the cramped tenement projects in Rio, and the fullness of the open valleys of the north. The further they go from Rio, the more blind faith and religious imagery fills the vacant rural landscape. In a way, the excesses of decadent greed and religious fervor both are seen as crazy, spinning out of control.
The Portuguese title (Central do Brasil) suggests that what is happening here is essentially what is happening in Brazil. Salles shows what's possible when cynicism and greed are dropped in favor of belief
More movie reviews by Seth Bookey, with graphics, can be found at http://www.geocities.com/Athens/2679/kino.html
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


How to Build a Twitter-Bot
Table of Contents
There is a command-line twitter client that can be used to send tweets to a certain account from terminal. Combining it with cron and other linux tools I have built a twitter-bot that sends proverbs periodically to twitter (https://twitter.com/l10n_sq). I will describe here how I did it.
1 Accessing twitter from the terminal
There is a single-letter tool called "t", which is a command-line Twitter client written in Ruby. I installed and configured it as described in this tutorial: http://xmodulo.com/access-twitter-command-line-linux.html.
The installation is simple:
apt-get install ruby-dev gem install t
Then create a new app on twitter: https://apps.twitter.com/app/new Follow the detailed instructions and the screenshots on the tutorial above.
The next step is to authorize your app to access the Twitter account:
useradd --system --create-home twitter su - twitter t authorize
The account access info will be stored in ~/.trc as a plain text.
Now we can use it like this
$ t account l10n_sq $ t whois @dashohoxha ID 50053789 Since Jun 23 2009 (8 years ago) Last update 20+ Free eBooks To Learn Linux For Free https://t.co/PoOMspWYJr (a month ago) Screen name @dashohoxha Name Dashamir Hoxha Tweets 1,403 Favorites 21 Listed 5 Following 50 Followers 141 $ t stream timeline $ t update "Hello there" $ t follow @dashohoxha @linuxfoundation $ t mentions -n 10 $ t search timeline "linux"
For more complex usage examples, refer to the official documentation.
2 Getting random proverbs with 'fortune'
There is a nice little command, called fortune
, which can get and
print a random citation from a file of citations.
apt-get install fortune fortune
The data files from which it gets the random citations are on
/usr/share/games/fortunes/
. The ones with the extension .dat
are
binary files (which are used by the command), and those without this
extension are text files. The format of these text files is very
simple: Quotations are separated from each-other by a line that
contains a %
sign. It is very easy to create a file that contains
your own list of preferred proverbs or citations. Then convert it to
the binary format with the command:
strfile -c % yourlist yourlist.dat
Now, to get a random item from your list you can run:
fortune yourlist
3 Twitting random items from your lists
I use a small script in order to tweet random sayings from my preferred lists:
#!/bin/bash fortune='/usr/games/fortune' t='/usr/local/bin/t' tweet=$($fortune -s -n 140 25% english 75% shqip) $t update "$tweet"
The main command here is fortune -s -n 140 25% english 75% shqip
.
The options -s -n 140
makes sure that only short proverbs of up to
140 chars are returned, which are suitable for being twitted. The
arguments 25% english 75% shqip
ask the command to return something
from the files in the directory /usr/share/games/fortunes/english/
with a probability of 25%, and something from the files in the
directory /usr/share/games/fortunes/shqip/
with a probability of 75%
(in the first directory I have collected some English proverbs, and on
the second one some Albanian proverbs). The last step of the script is
to send this random proverb to twitter, with the command =t update
"tweet"=.
4 Making it automatic
The last thing is to run the script above periodically and
automatically. This is a job for the good old cron
. I have created
the file /etc/cron.d/fortune
which has these lines:
### first create a user with `adduser twitter` 0 */5 * * * twitter /home/twitter/fortune.sh > /dev/null 2>&1 ### uncomment this line only for debugging #*/5 * * * * twitter /home/twitter/fortune.sh
It runs the script each 5 hours, sending posts to twitter automatically.
5 Twitting translations from l10n.org.al
Another script very similar to the previous one is this:
#!/bin/bash t='/usr/local/bin/t' base_url=https://l10n.org.al tweet=$(curl -k $base_url/btr/tweet) mention=$( ( $t followings ; $t followers ) | uniq | sort -R | tail -1) $t update "$tweet @$mention"
This one gets a random translation from l10n.org.al, with the command:
curl -k https://l10n.org.al/btr/tweet
Then it appends to it the name of a random friend and sends it to twitter.


A BUG'S LIFE A film review by Mark R. Leeper
Capsule: Our second animated ant film of the season edges out the first for humor and actual animation style. A BUG'S LIFE has a mediocre story-line, seemingly derived from Kurosawa's SEVEN SAMURAI, but its animation will dazzle the eye. This film offers adults a little less plot and characterization than did ANTZ, but the visuals are better and jokes are funnier. Don't miss the closing credit sequence. Rating: 7 (0 to 10), low +2 (-4 to +4)
Let's get the expository lump out of the way at the beginning. All bugs are insects but not all insects are bugs. Bugs have tube-like mouthparts and wings that are joined to the body in a sort of thickened area. Scientists call them "Hemiptera." Ants, walking sticks, and certainly spiders are not bugs. And to further correct, ants have six legs as we saw in ANTZ (well, sort of), not four as portrayed in A BUG'S LIFE. And speaking of inaccuracies, in my review of ANTZ I said that Pixar and Disney might have to look to their laurels to match the animation quality of ANTZ. Well I was wrong; Pixar and Disney are doing just fine, thank you. A BUG'S LIFE actually is the more visually sophisticated of the two animated films, but each has far surpassed TOY STORY. There may be more animated figures in some scenes in ANTZ, but Pixar has the edge on life-like animation and in giving a three- dimensional look. They have also made some perceptive improvements in digital representation light and surface texture. Facial animation is also better. In fact looking at the faces of the grasshoppers as they talk, they really have more texture than a camera would pick up looking at a real grasshopper. They have gone into a kind of hyper-reality, much like the saturated Technicolor of musicals of the 50s created a sort of hyper-reality. But since people get a little squeamish looking too closely at real insects, Pixar seems to reserve this over-texturing for the villainous grasshoppers and there to make them a somewhat more repulsive foe.
Where A BUG'S LIFE has a problem is that it has a less sophisticated or interesting plot than either TOY STORY or ANTZ. The plot is directly or indirectly a rehash of the late Akira Kurosawa's SEVEN SAMURAI. The ants of Ant Island could live very well if they were not obliged to pay a heavy tribute of food each year to a ferocious band of grasshoppers. In the colony ant Flik has many ideas how to do things differently-he even has ideas for how to deal with the grasshoppers. The catch is that none of his ideas seems to work very well. Flik's idea for how to deal with the grasshoppers is to get bigger bugs to fight for the ant colony. So the colony decides to send someone to find defenders. And who do they choose? The ant they can most spare, Flik. Our intrepid ant finds defenders, but does not realize that they are not fighters but flea circus performers. With Flik's ideas and with the aid of what they think of as fighter insects, the colony prepares to defend itself against the cruel grasshoppers. The grasshopper leader is the nasty Hopper, voiced with real menace by Kevin Spacey.
It is tempting to compare this film's weaker plot but impressive visuals with the current trend of sci-fi films being taken over by special effects. But many respected classic films did much the same. Busby Berkley musicals had real visual style but had relatively bland and cliched plots. Then as so frequently now the entertainment was in what the audience saw, not what the film said.
It is worthwhile to see A BUG'S LIFE just to see how the animation technology is progressing. If the story- line is weak at least there are moments of really good humor, though many are in the closing credits. It would be interesting to know if ANTZ eats into the profits of A BUG'S LIFE. ANTZ seems to have been timed to do just that, but if so it may have been a miscalculation. The two films dealing with the one non- conformist ant in the colony could co-exist at the box-office much like DEEP IMPACT and ARMAGEDDON have.
I would rate A BUG'S LIFE a 7 on the 0 to 10 scale and a low +2 on the -4 to +4 scale. There are a few scenes that could be frightening to younger children.
This film has been released with the short film "Geri's Game" which won Pixar an Oscar. It is a funny little short of an old man playing chess with himself in the park. It humorously makes the point that playing chess with yourself as an opponent is not really like playing a different person. Actually it could almost be a study of schizophrenia, though I think that is reading more into the short than Pixar intends. One of the more interesting aspects of "Geri's Game" is to see how far Pixar has progressed in representing computer animation of human figures. It is one thing to represent in animation toys and insects with their rigid surfaces, but it is harder to represent humans realistically. Human characters were kept to a minimum in TOY STORY and are not present at all in A BUG'S LIFE.
I have one question about the closing credits. (Hopefully this will be meaningless to anyone who has not seen the film.) How genuine is what we are hearing? The joke is obviously that the visuals are false, but the audio track may be genuine.
Mark R. Leeper<EMAIL_ADDRESS>Copyright 1998 Mark R. Leeper
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


Locations
The Menagerie
25
The Menagerie
Menagerie
unknown
['https://rickandmortyapi.com/api/character/67', 'https://rickandmortyapi.com/api/character/490', 'https://rickandmortyapi.com/api/character/491']


{% load staticfiles %}
Curators can supplement their own original educational content with well-known, high-quality sources that could previously only be found online, like Khan Academy, OpenStax textbooks, CK12, and more.
We make it easy to create new quizzes and exercises that are catered to your curriculum, or import existing ones from other channels. Teachers have the option to create quizzes in multiple choice and free answer format, and also create exercises where students can practice what they've learned.
Learners can view their progress, and receive recommendations about what lessons or exercises to do next. Teachers or mentors can also track students’ progress by viewing “Coach Reports”, allowing them to see which students need extra help and identify where they can most effectively intervene to help students overcome learning barriers.
Learners can provide feedback by rating and commenting on the content they view. Kolibri’s content recommendation system then highlights the most useful and relevant content on a learner’s dashboard. These ratings and feedback can also be synced to the online server to help content curators improve existing content.


Take Me Out fans can holiday at Fernando's - but they've got to be quick
Anyone looking for love can grab a special trip to the idyllic isle location featured on the show to celebrate the latest run of the dating programme
by Brett Gibbons, https://www.birminghammail.co.uk/authors/brett-gibbons/Fans of TV dating show Take Me Out know that the Isle of Fernando's is the location for would-be couples who want to get to know each other.
The destination, located in real-life at Puerto de la Cruz in Tenerife, is the ideal location for a romantic getaway and anyone looking for love can grab a special trip to the idyllic isle inspired by the show to celebrate the latest run of the dating show.
The limited edition chance to secure a place at the Take Me Out resort is available for one day only on November 18 with bookings through holiday website Travel Republic.
fans of the show can enjoy the sun, sea and glamour of the programme’s famous destination - staying in a 5* hotel and enjoying everything from spa treatments and local excursions.
Frank Rejwan, managing director at Travel Republic, commented: “Working with Take Me Out to bring Fernando’s to life has been a great way to make fans feel part of the show whilst also giving them an incredible holiday experience.
"At Travel Republic, we work hard to make sure we can provide everyone with the holidays they want so we’re delighted to bring this never-seen-before bookable package holiday to our customers.”
To book your Take Me Out Fernando’s - inspired trip, visit https://www.travelrepublic.co.uk/offers/take-me-to-fernandos


{% include head.html %}
Ministers Under the Influence is a project that came out of the #HackBrexit Hackathon which took place during one weekend, July 23-24 in 2016. The project was further developed during the Hack Brexit accelerator programme which ran September - December 2016. See the site for the full project here.
The aim of Ministers Under the Influence is to unearth public-but-obscure information about who ministers are meeting. By making this data more accessible we can hope to better hold our government to account. We hope this will feed into a wider project about tracking who is influencing our elected politicians.
Whilst the 3 month accelerator programme which ran September - December 2016 is now over, we would still welcome contributions for the project. Please join us on slack to begin contributing here. and on the github repository here.


In this paper, we introduce Point2Mesh, a technique for reconstructing a surface mesh from an input point cloud. Instead of explicitly specifying a prior that encodes the expected shape properties, the prior is defined automatically using the input point cloud, which we refer to as a self-prior. The self-prior encapsulates reoccurring geometric repetitions from a single shape within the weights of a deep neural network. We optimize the network weights to deform an initial mesh to shrink-wrap a single input point cloud. This explicitly considers the entire reconstructed shape, since shared local kernels are calculated to fit the overall object. The convolutional kernels are optimized globally across the entire shape, which inherently encourages local-scale geometric self-similarity across the shape surface. We show that shrink-wrapping a point cloud with a self-prior converges to a desirable solution; compared to a prescribed smoothness prior, which often becomes trapped in undesirable local minima. While the performance of traditional reconstruction approaches degrades in non-ideal conditions that are often present in real world scanning, i.e., unoriented normals, noise and missing (low density) parts, Point2Mesh is robust to non-ideal conditions. We demonstrate the performance of Point2Mesh on a large variety of shapes with varying complexity.
Point2Mesh is a technique for reconstructing a surface mesh from an input point cloud. This approach "learns" from a single object, by optimizing the weights of a CNN to deform some initial mesh to shrink-wrap the input point cloud:
The optimized CNN weights act as a prior, which encode the expected shape properties, which we refer to as a self-prior. The premise is that shapes are not random, and contain strong self-correlation across multiple scales.
Central to the self-prior is the weight-sharing structure of a CNN, which inherently models recurring and correlated structures and, hence, is weak in modeling noise and outliers, which have non-recurring geometries.
We thank Daniele Panozzo for his helpful suggestions. We are also thankful for help from Shihao Wu, Francis Williams, Teseo Schneider, Noa Fish and Yifan Wang. We are grateful for the 3D scans provided by Tom Pierce and Pierce Design. This work is supported by the NSF-BSF grant (No. 2017729), the European research council (ERC-StG 757497 PI Giryes), ISF grant 2366/16, and the Israel Science Foundation ISF-NSFC joint program grant number 2472/17.