senecajs / seneca-vote Goto Github PK
View Code? Open in Web Editor NEWA voting plugin for Seneca.js
License: MIT License
A voting plugin for Seneca.js
License: MIT License
seneca-vote becomes @seneca/vote
Preferred coverage:
Stmts >= 90%
Branch >= 90%
Funcs >= 90%
Lines >= 90%
Suggested CI: Travis
Such validation would be implemented in index.js, and make sure a client passes supported well-formed options.
To complete the testing we will also need tests at the message level using: https://github.com/voxgig/seneca-msg-test
seneca
.fix('sys:vote')
.message('open:poll', <action>)
...
Where <action>
is an async function
For example, currently if the plugin crashes when trying to de-normalize a poll rating, the submission of the vote will not be rolled back (i.e. undone). This violates ACID and must be handled via implementation of a transaction.
The why
property should not be a human friendly message - rather an arbitrary code string for use by client code.
Nor should it contain any supporting information - this goes into a details
property at the same level.
{ ok: false, why: 'invalid-field', details: { field: { name:'foo', ... }, ... } }
Thus client code can decide what to do with it, generate translated human friendly messages, custom logic, retry, etc, etc
when you work on many plugins this convention helps to keep the files your editor separate
Please add Richard @rjrodger as a contributor.
$ npm test
> [email protected] test /Users/richard/Projects/seneca/vote
> ./node_modules/jasmine/bin/jasmine.js --config=test/support/jasmine.json
Randomized with seed 04499
Started
.............................F.....................
Failures:
1) the OpenPoll action when the poll with the given title does not exist when bombarded with messages to create a poll with the same title does not result in a race condition
Message:
Expected 3 to equal 1.
Stack:
Error: Expected 3 to equal 1.
at <Jasmine>
at /Users/richard/Projects/seneca/vote/test/lib/open_poll_msg.test.js:176:57
at runMicrotasks (<anonymous>)
at runNextTicks (internal/process/task_queues.js:58:5)
51 specs, 1 failure
Finished in 1.666 seconds
Randomized with seed 04499 (jasmine --random=true --seed=04499)
npm ERR! Test failed. See above for more details.
https://github.com/lilsweetcaligula/seneca-vote/blob/master/lib/lock.js#L25
Instead of throwing an error from the callback, we should pass the error to the callback.
The idiom for entity creation is to use this in situ:
let pollent = seneca.make('sys/poll')
separate utility functions are not needed
The $ npm run clean
and $ npm run reset
commands are currently missing documentation (in the README.md
file). Please remedy that.
Allow developers to pass an option to the plugin. This option, if passed, will indicate mapping from some entity (indicated by its canon form, e.g. base/name
) to the field intended for storing the total count of votes. This field should be updated with the total count of votes after each vote submission.
Example client code:
seneca.use('vote', {
dependents: {
'red': { // 'red' is the entity's 'kind' field
'mars': { // 'mars' is the entity's 'code' field
totals: {
'foo/bar': { field: 'votes' },
'zed/qaz': { field: 'total' }
}
}
}
}
})
Let's assume we have the following entities in our store (i.e. db):
foo/bar:
{ id: 101, name:'Q', votes: 0 }
sys/vote:
{ id: 01, kind:'red', code:'mars', voter_id:...}
{ id: 02, kind:'red', code:'mars', voter_id:...}
{ id: 03, kind:'red', code:'jupiter', voter_id:...}
{ id: 04, kind:'blue', code:'earth', voter_id:...}
Now let's assume someone voted with a message that looks like this:
{ sys:vote, poll_id: ..., voter_id:..., kind:'red', code:'mars', dependents: { 'foo/bar': 101 } }
This vote causes a new entity to be created:
{ id: 05, kind:'red', code:'mars', voter_id:...}
With the dependents
option specified above in the snippet in mind, after the new vote has been cast, the following votes will contribute toward the total count of votes:
{ id: 01, kind:'red', code:'mars', voter_id:...}
{ id: 02, kind:'red', code:'mars', voter_id:...}
{ id: 05, kind:'red', code:'mars', voter_id:...}
This gives the total number of 3 votes. Notice how we only calculate the total number of votes who match the kind
/code
fields. Basically, to put it in pseudo-code (SQL in this example):
select count(1) from sys/vote
where poll_id = ? and kind = 'red' and code = 'mars'
However, and this is important - the votes entity in the plugin is built according to the tombstone pattern, which means new votes are created in the store/db as opposed to updating the existing ones. That means, for each vote, only the newest vote by that voter on that poll contributes to the total count.
Now, the dependents
option in the snippet above lists entities foo/bar
and zed/qaz
, and the client who cast the vote, passed a parameter: dependents: { 'foo/bar': 101 }
, which means the entity foo/bar
with the id
of 101
has to be updated to the total count of votes.
If no such dependent exists (e.g. no foo/bar
entity exists with the id of 101
, requested by the client), then we must respond to the client with an error (respond but do not throw the error!).
So let's assume prior to the vote the following foo/bar
record existed in the store/db. This is our "dependent" record":
{ id: 101, name:'Q', votes: 2 }
After the vote has been cast, the foo/bar
record will look like so:
{ id: 101, name:'Q', votes: 3 }
Votes of vote_type 'up' add +1 to the total count, and votes of vote_type 'down' add -1 to the total count.
P.S.: First step: define some seneca-msg-test scenarios for this - TODO: @rjrodger
These are informal, but will become standard. In the return values, use props:
ok: boolean
in place of status
why: string
a string code representing the error or other issue if ok === false
This normal mode of use for system plugins is generally:
seneca.use('vote', { ... options ... })
Which adds some set of sys:vote
messages, and a plugin called sys_vote
.
Some of this is checked by seneca-plugin-validator (as to be added for v2).
The mini-plugins (OpenPoll, GetPoll), etc. will need to be converted to plain messages - this avoids a proliferation of plugins in a client system.
The code base will need to be converted to TypeScript. This will also remove the need for fetchProp, asserts etc as property access can be statically verified.
If, for example, a user previously upvoted on a poll, and then chose to downvote instead, we update his existing vote to the new status. When that happens, currently, we do not set the vote.updated_at
field to the new date, although we should. Please update the vote.updated_at
field to the new date whenever the status of a vote is overwritten.
Use https://github.com/voxgig/seneca-group/blob/master/group.js as a sort of template
except move the action definitions into separate files in the lib folder, more like:
https://github.com/senecajs/seneca-entity-history (this is probably a better template, just ignore the weird action implementation code! (the work
var - all that stuff is a failed experiment))
This is awesome - good catch - we need to investigate further.
Please create a small-as-possible reproduction in this repo, then we can start digging.
https://github.com/lilsweetcaligula/seneca-vote/blob/master/lib/lock.js
plugin_opts
to options
, as this is the conventional name used in other core pluginsDuring implementation work on seneca-vote
, in order to meet certain MVP requirements, such as that there can only ever be a single poll record with the same title, - locks were implemented as part of seneca-vote
to prevent race conditions, that may otherwise occur under high loads.
It was ultimately decided to disable the locks by default in order to comply with the rest of the Seneca eco-system. Entity upserts are planned to be added to Seneca entities in the future. They will work as upserts normally do:
https://docs.mongodb.com/drivers/node/fundamentals/crud/write-operations/upsert/
https://www.postgresqltutorial.com/postgresql-upsert/
Further work on the data model of seneca-vote
is expected in order to both prevent race conditions and remain compliant with the rest of the Seneca eco-system.
Please outline this information in the README file.
see:
https://github.com/rjrodger/inks
https://github.com/voxgig/seneca-msg-test/blob/master/example/test-spec.js#L29
this avoids the need to pass arguments into the msg tests
In order to make seneca-vote
uniform across the Seneca eco-system, it is best to disable the custom entity locks (./lib/lock.js
) by default. Locks can be enabled explicitly by the client by passing a flag via the seneca-vote
plugin options.
While we do have the LICENSE
document in the root directory of the project, we also have to specify the license in the package.json
file.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.