How can I model a domain that include roles with several attributes? - datomic

I have read the documentation, and I am struggling with the following case (probably I am thinking in neo4j and hyperedges and that's why I can think clearly here):
Assume that I want to model the following entities: :person (with some attributes like :name, id, etc), :school (again some attributes) the relation ship of between the school and the person could be like :student or :teacher. And this could evolve through time (a :person could not be related to the school, later is a :student and much later maybe as a :teacher)
When the :person is a :student it will have an student-id, and as a :teacher it will have another id lets say teacher-id.
So, should I have:
:person/name 'John'
:school/id 'SCHOOL-1'
:student/name 'John'
:student/school 'SCHOOL-1'
? How I should include information as the student-id, What if there is more schools?
But now, it seems to me that the relationship between this entities is an hyperedge (that's why I mentioned neo4j). And I don't know what is the best way of modelling this is datomic.
Thanks in advance

I have a good example of using Datomic to keep track of James Bond and various villians available here. Everything is written in the form of unit tests using the Tupelo Datomic library so it is easy to verify the base project and any changes you have:
~/io.tupelo.demo/datomic > lct
Java HotSpot(TM) 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
lein test _bootstrap
-------------------------------
Clojure 1.10.1 Java 13
-------------------------------
lein test tst.tupelo.datomic.bond
Ran 2 tests containing 35 assertions.
0 failures, 0 errors.
The above will work with Datomic Free edition, so you don't even need a paid license to get started.
In the Bond example, the characters may have multiple weapons, which are modeled as a set:
; Create some antagonists and load them into the db. We can specify some of the
; attribute-value pairs at the time of creation, and add others later. Note that
; whenever we are adding multiple values for an attribute in a single step (e.g.
; :weapon/type), we must wrap all of the values in a set. Note that the set
; implies there can never be duplicate weapons for any one person. As before,
; we immediately commit the new entities into the DB.
(td/transact *conn*
(td/new-entity { :person/name "James Bond" :location "London" :weapon/type #{ :weapon/gun :weapon/wit } } )
(td/new-entity { :person/name "M" :location "London" :weapon/type #{ :weapon/gun :weapon/guile } } )
(td/new-entity { :person/name "Dr No" :location "Caribbean" :weapon/type :weapon/gun } ))

Related

How to ensure developers filter by a foreign key in CakePHP

In a legacy project we had issues where if a developer would forget a project_id in the query condition, rows for all projects would be shown - instead of the single project they are meant to see. For example for "Comments":
comments [id, project_id, message ]
If you forget to filter by project_id you would see all projects. This is caught by tests, sometimes not, but I would rather do a prevention - the dev should see straightaway "WRONG/Empty"!
To get around this, the product manager is insisting on separate tables for comments, like this:
project1_comments [id,message]
project2_comments [id,message]
Here if you forgot the project/table name, if something were to still pass tests and got deployed, you would get nothing or an error.
However the difficulty is then with associated tables. Example "Files" linked to "Comments":
files [ id, comment_id, path ]
3, 1, files/foo/bar
project1_comments
id | message
1 | Hello World
project2_comments
id | message
1 | Bye World
This then turns into a database per project, which seems overkill.
Another possibility, how to add a Behaviour on the Comments model to ensure any find/select query does include the foreign key, eg - project_id?
Many thanks in advance.
In a legacy project we had issues where if a developer would forget a project_id in the query condition
CakePHP generates the join conditions based upon associations you define for the tables. They are automatic when you use contains and it's unlikely a developer would make such a mistake with CakePHP.
To get around this, the product manager is insisting on separate tables for comments, like this:
Don't do it. Seems like a really bad idea to me.
Another possibility, how to add a Behaviour on the Comments model to ensure any find/select query does include the foreign key, eg - project_id?
The easiest solution is to just forbid all direct queries on the Comments table.
class Comments extends Table {
public function find($type = 'all', $options = [])
{
throw new \Cake\Network\Exception\ForbiddenException('Comments can not be used directly');
}
}
Afterwards only Comments read via an association will be allowed (associations always have valid join conditions), but think twice before doing this as I don't see any benefits in such a restriction.
You can't easily restrict direct queries on Comments to only those that contain a product_id in the where clause. The problem is that where clauses are an expression tree, and you'd have to traverse the tree and check all different kinds of expressions. It's a pain.
What I would do is restrict Comments so that product_id has to be passed as an option to the finder.
$records = $Comments->find('all', ['product_id'=>$product_id])->all();
What the above does is pass $product_id as an option to the default findAll method of the table. We can than override that methods and force product_id as a required option for all direct comment queries.
public function findAll(Query $query, array $options)
{
$product_id = Hash::get($options, 'product_id');
if (!$product_id) {
throw new ForbiddenException('product_id is required');
}
return $query->where(['product_id' => $product_id]);
}
I don't see an easy way to do the above via a behavior, because the where clause contains only expressions by the time the behavior is executed.

CakePHP 3.3 tables dedicated for different data based on the selected language

I have a non-standard question to CakePHP 3.3. Let's imagine that in my database I have two tables: A and B (both are identical, first is dedicated for data in the first language, second is dedicated for data in the second language).
I correctly coded the whole website for table A (table B is not yet in use). Additionally, I implemented the .po files mechanizm to switch the language of the interface. The language of the inteface switches correctly.
How can I easily plug the table B - I do not want to make IF-ELSE statements in all cases because the website is getting big, and there are many operations in table A already included. Is there a possibility to somehow make a simple mapping that table A equals table B if language pl_PL is selected to en_US (through .po files)?
The most simple option that comes to my mind would be to inject the current locale into your existing table class, and have it set the database table name accordingly.
Let's assume your existing table class would be called SomeSharedTable, this could look something along the lines of:
// ...
class SomeSharedTable extends Table
{
public function initialize(array $config)
{
if (!isset($config['locale'])) {
throw new \InvalidArgumentException('The `locale` config key is missing');
}
$table = 'en_table';
if ($config['locale'] === 'pl_PL') {
$table = 'pl_table';
}
$this->table($table);
// ...
}
// ...
}
And before your appplication code involves the model layer, and after it sets the locale of course (that might for example be in your bootstrap), configure the alias that you're using throughout your application (for this example we assume that the alias matches the table name):
\Cake\ORM\TableRegistry::config('SomeShared', [
'locale' => \Cake\I18n\I18n::locale()
]);
Given that it's possible that the locale might not make it into the class for whatever reason, you should implement some safety measures, I've just added that basic isset() check for example purposes. Given that a wrongly configured table class could cause quite some problems, you probably want to add some checks that are a little more sophisticated.

Given an array of (sanitized) attribute headers (metatags), how might I automatically create columns for each in my database based on those tags?

So here is the unformatted list (this one, an income statement, has over row headers like these, so yes, automation is the way to go here).
["Revenue", "Other Revenue, Total", "Total Revenue", "Cost of Revenue, Total"...]
Here is the list after I ran each array entity (string) through my simple little sanitizer program, CleanZeeString.new.go(str).
["revenue", "other_revenue_total", "total_revenue", "cost_of_revenue_total"...]
So, I want to access Rails methods that will allow me to at least partially automate the database column creation process and migration, because this list has over 50 row headers, there are more lists, and I simply do not believe in doing things by hand anymore.
LATER (personal progress):
I'm starting to believe that a solution to this problem is going to involve getting outside of the rails "box" with regards to migrations. Yes, to solve this, I think we might have to think creatively about migrations...
I know how easy this is to do either by hand, or with the assistance of some sort of third party scripting solution, but I simply refuse. I should have been able to do this automatically last night after a couple of drinks if I wanted to. Given the array, and the fact that each column is the same type ("decimal" in rails), this should be doable in an automatic, rails-like way.
migration files are just normal ruby files. working on a solution based off that fact. time to get fancy. String#to_sym
Got it---
class CreateIncomeStatements < ActiveRecord::Migration
def change
f = File.open(File.join(Rails.root, 'lib', 'assets', 'is_list.json'))
is_ary = JSON.parse(f.read)
create_table :income_statements do |t|
is_ary.each do |k|
eval("t.decimal k.to_sym")
end
t.timestamps
end
end
end
I used the eval() method, and felt the ghost of my teacher slap me on the wrist, but, it worked. The key "ah hah" was re-considering the fact that migration files are just ruby files, and as such, I can just do whatever I want.

Prolog Doing a Query

This is directly from a tutorial online, and I get a top down level design error, help?
employee(193,'Jones','John','173 Elm St.','Hoboken','NJ',
12345,1,'25 Jun 93',25500).
employee(181,'Doe','Betty','11 Spring St.','Paterson','NJ',
12354,3,'12 May 91',28500).
employee(198,'Smith','Al','2 Ace Ave.','Paterson','NJ',
12354,3,'12 Sep 93',27000).
Given these basic relations (also called extensional relations), we can define other relations using Prolog procedure definitions to give us answers to questions we might have about the data. For example, we can define a new relation containing the names of all employees making more than $28,000:
well_paid_emp(First,Last) :-
employee(_Num,Last,First,_Addr,_City,_St,_Zip,_Dept,_Date,Sal),
Sal > 28000.
It could be that you are using a Prolog system which shows a singleton warning for well_paid_emp/2.
Not all Prolog systems allow _<Capital><Rest> as singletons, i.e. variables that occur only once in a rule.

get_by_id() will not return model instance

I have a Model called Version that looks like this:
from google.appengine.ext import db
import piece
class Version(db.Model):
"A particular version of a piece of writing."
parent_piece = db.ReferenceProperty(piece.Piece, collection_name='versions')
"The Piece to which this version belongs."
note = db.TextProperty()
"A note from the Author about this version."
content = db.TextProperty()
"The actual content of this version of the Piece."
published_on = db.DateProperty(auto_now_add=True)
"The date on which the version was published."
I would like to access instances of Version via their IDs, using Version.get_by_id(), but this call always returns None. I can see in the Datastore Viewer that they have ID values, and in the debugger, I can query for them but not use them:
>>> for each_ver in version.Version.all():
... print each_ver.key().id()
...
34
35
36
31
32
>>> a = version.Version.get_by_id(34)
>>> type(a)
<type 'NoneType'>
I see that there are plenty of questions here where people are able to use get_by_id() effectively just as I wish, and they do not see the results that I am seeing.
Could the problem be that each Version instance is a child in an Entity Group rather than a root of an Entity Group? Each Version lives in an Entity Group that looks like Member->Piece->Version. If that is the problem, is there a way that I can refer to Version entity without using its entire key? If that is not the problem, can anyone tell me what I can do to make get_by_id() work as expected?
Could the problem be that each Version
instance is a child in an Entity Group
rather than a root of an Entity Group?
Yes. An entity's key includes the keys of any parent entities.
If that is the problem, is there a
way that I can refer to Version entity
without using its entire key?
No. An entity is uniquely identified only by its entire key, which includes the keys of all the parent entities. If you know the kinds of its parent entities, though, you can use db.Key.from_path to construct the key from the chain of IDs or key names.
I had your same problem but in ndb.Model and I found that I need to convert the ID to an int. So maybe using version.Version.get_by_id(int(34)) can solve your problem.

Resources