Skip to content

Commit

Permalink
#151 add schema for questions
Browse files Browse the repository at this point in the history
  • Loading branch information
deleuterio committed Mar 30, 2018
1 parent 8a3b0c3 commit fb346fd
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 0 deletions.
191 changes: 191 additions & 0 deletions packages/lern-model/collections/questions/schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import _ from 'lodash';
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { Class } from 'meteor/jagi:astronomy';
import Author from '../../behaviors/author.js';
import Content from '../../schemas/content/schema.js';
import StaticCollections from '../static.js';
import log from 'loglevel';

const Questions = new Mongo.Collection('questions');

const RangeSchema = Class.create({
name: 'QuestionRange',
fields: {
min: {
type: Number,
immutable: true,
optional: true,
},
max: {
type: Number,
immutable: true,
optional: true,
},
},
});

const Question = Class.create({
name: 'Question',
collection: Questions,
fields: {
description: {
type: [Content],
validators: [{ type: 'minLength', param: 1 }],
default: () => [],
},
text: {
type: String,
optional: true,
},
description: {
type: [Content],
optional: true,
},
type: {
type: String,
validators: [
{
type: 'choice',
param: StaticCollections.QuestionTypes,
},
],
immutable: true,
},
answer: {
validators: [{ type: 'QuestionAnswer' }],
optional: true,
immutable: true,
},
range: {
type: [QuestionRange],
validators: [{ type: QuestionRange }],
optional: true,
immutable: true,
},
sudoku: [Number],
level: {
type: String,
optional: true,
},
score: {
type: Number,
optional: true,
},
options: {
type: [Content],
validators: [{ type: 'QuestionOptions' }],
optional: true,
},
},
helpers: {
validateGame(answer) {
var conflict = false;
var conflictRow = false;
for (var row = 0; row < 9; row++) {
var cRow = _.fill(new Array(9), false);
for (var col = 0; col < 9; col++) {
conflictRow = conflictRow || cRow[answer[row * 9 + col] - 1];
cRow[answer[row * 9 + col] - 1] = true;
}

log.info('row: ' + row, cRow, conflictRow, _.every(cRow));
}

var conflictCol = false;
for (var col = 0; col < 9; col++) {
var cCol = _.fill(new Array(9), false);
for (var row = 0; row < 9; row++) {
conflictCol = conflictCol || cCol[answer[row * 9 + col] - 1];
cCol[answer[row * 9 + col] - 1] = true;
}

log.info('col: ' + col, cCol, conflictCol, _.every(cCol));
}

var conflictGrid = false;
for (var i = 0; i < 9; i += 3) {
for (var j = 0; j < 9; j += 3) {
var cGrid = _.fill(new Array(9), false);
for (var row = i; row < i + 3; row++) {
for (var col = j; col < j + 3; col++) {
conflictGrid = conflictGrid || cGrid[answer[row * 9 + col] - 1];
cGrid[answer[row * 9 + col] - 1] = true;
}
}

log.info('grid: ' + i + ',' + j, cGrid, conflictGrid, _.every(cGrid));
}
}

log.info(conflictRow, conflictCol, conflictGrid);

conflict |= conflictRow || conflictCol || conflictGrid;

return !conflict;
},

isComplete(answer) {
return _.every(answer, a => a && a >= 1 && a <= 9);
},
},
behaviors: {
timestamp: {
hasCreatedField: true,
createdFieldName: 'createdAt',
hasUpdatedField: true,
updatedFieldName: 'updatedAt',
},
},
});

Questions.extend({
fields: {
answerCount: {
type: Number,
default: 0,
},
hitCount: {
type: Number,
default: 0,
},
hitRate: {
type: Number,
default: -1,
},
},
events: {
afterInc({ data: { fieldName }, currentTarget: sudoku }) {
if (fieldName === 'answerCount' || fieldName === 'hitCount') {
const { answerCount, hitCount } = sudoku;
sudoku.set('hitRate', hitCount / answerCount);
}
},

beforeSave(e) {
const contentText = _.join(
_.map(
_.flatten(_.compact(_.map(this.get('content'), 'text.blocks'))),
'text'),
' ') || '';
const optionsText = _.join(
_.map(
_.flatten(_.compact(_.map(this.get('options'), 'text.blocks'))),
'text'),
' ') || '';

this.set('text', _.join([contentText, optionsText], ' '));

if (this.get('range.min'))
this.set('range.min', _.toNumber(this.get('range.min')));
if (this.get('range.max'))
this.set('range.max', _.toNumber(this.get('range.max')));
},
},
});

Author(Question);

Question.RangeSchema = RangeSchema;

export default Question;
80 changes: 80 additions & 0 deletions packages/lern-model/collections/questions/validators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import _ from 'lodash';
import { Validator } from 'meteor/jagi:astronomy';
import StaticCollections from '../static.js';

const QuestionAnswer = {
name: 'QuestionAnswer',

isValid({ value }) {
const { type, options, range } = this;
if (type === 'open')
return (
!_.isNull(value) &&
_.isString(value) &&
_.inRange(value.length, 4, 10000)
);
else if (type === 'number')
return !(_.isNull(range.min) || _.isNull(range.max))
&& range.min <= value && value <= range.max && range.min < range.max;
else if (type === 'closed')
return (
!_.isNull(value) &&
_.isNumber(value) &&
_.inRange(value, 0, options.length)
);
else if (type === 'sudoku')
return (
!_.isNull(value) &&
_.isArray(value) &&
value.length === 81 &&
!_.some(value, (v) => v === null || (v <= 0 || v >= 10))
);
else return false;
},

resolveError({ name }) {
return `The field ${name} contains inappropriate options`;
},
};

const QuestionOptions = {
name: 'QuestionOptions',

isValid({ value }) {
const { type } = this;
if (type === 'number') return _.isNull(value);
else if (type === 'open') return _.isNull(value);
else if (type === 'closed') {
return (
!_.isNull(value) &&
_.isArray(value) &&
value.length > 1
);
} else return false;
},

resolveError({ name }) {
return `The field ${name} contains inappropriate options`;
},
};

const QuestionRange = {
name: 'QuestionRange',

isValid({ value: range }) {
const { type } = this;
if (type === 'number')
return range
&& !(_.isNull(range.min) || _.isNull(range.max))
&& (!range.max || range.min < range.max);
else return true;
},

resolveError({ name }) {
return `The field ${name} contains inappropriate range`;
},
};

Validator.create(QuestionAnswer);
Validator.create(QuestionOptions);
Validator.create(QuestionRange);
1 change: 1 addition & 0 deletions packages/lern-model/collections/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const StaticCollections = {
TestTimeTypes: ['range', 'none'],
TestResolutions: ['content', 'sudoku'],
SudokuLevel: ['easy', 'medium', 'hard'],
QuestionTypes: ['sudoku', 'open', 'closed', 'number', 'unanswered'],
};

export default StaticCollections;

0 comments on commit fb346fd

Please sign in to comment.