Created
July 12, 2017 07:36
-
-
Save forresty/839e9c551bfeb0238dde9094cb964281 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Androsphinx | |
| class Node | |
| # 最基础的树节点 | |
| def initialize(name) | |
| @name = name | |
| @childrens = [] | |
| end | |
| def add_child(child) | |
| @childrens << child | |
| end | |
| end | |
| class Questionnaire < Node | |
| # Questionnaire has_many sections | |
| def initialize(name) | |
| super | |
| @next_section_id = 'A' | |
| end | |
| def add_child(section) | |
| @childrens << section | |
| section.id = @next_section_id | |
| @next_section_id = @next_section_id.next | |
| end | |
| alias_method :add_section, :add_child | |
| def to_h | |
| { | |
| name: @name, | |
| sections: @childrens.map(&:to_h) | |
| } | |
| end | |
| end | |
| class QuestionGroup < Node | |
| # 是一个抽象的问题组合 | |
| attr_accessor :id | |
| alias_method :add_question, :add_child | |
| def depends_on(choice) | |
| @depends_on = choice | |
| end | |
| def to_h | |
| hash = { | |
| id: @id, | |
| depends_on: @depends_on, | |
| name: @name, | |
| type: self.class.name.split('::').last, | |
| questions: @childrens.map(&:to_h) | |
| } | |
| hash.reject { |_, v| v.nil? } | |
| end | |
| end | |
| class Section < QuestionGroup | |
| # section 是二级元素 | |
| def initialize(name) | |
| super | |
| @next_question_id = 1 | |
| end | |
| def add_child(q) | |
| super | |
| q.id = "#{@id}#{@next_question_id}" | |
| @next_question_id = @next_question_id.next | |
| end | |
| alias_method :add_question, :add_child | |
| def to_h | |
| { | |
| section_id: @id, | |
| name: @name, | |
| questions: @childrens.map(&:to_h) | |
| } | |
| end | |
| end | |
| class RepeatedGroup < QuestionGroup | |
| end | |
| class Question < Node | |
| attr_accessor :id | |
| def to_h | |
| hash = { | |
| id: @id, | |
| name: @name, | |
| type: self.class.name.split('::').last | |
| } | |
| hash.reject { |_, v| v.nil? } | |
| end | |
| end | |
| class FormattedQuestion < Question | |
| attr_accessor :validations | |
| def initialize(name, format, validations = {}) | |
| super(name) | |
| @format = format | |
| @validations = validations | |
| end | |
| def to_h | |
| super.merge(format: @format, validations: @validations) | |
| end | |
| end | |
| class ConditionalRepeatedGroupQuestion < FormattedQuestion | |
| alias_method :add_question, :add_child | |
| def to_h | |
| super.merge(questions: @childrens.map(&:to_h)) | |
| end | |
| end | |
| class SingleChoiceQuestion < Question | |
| attr_accessor :options | |
| def initialize(name, options) | |
| super(name) | |
| @options = options | |
| end | |
| def to_h | |
| if @childrens.empty? | |
| super.merge(options: @options) | |
| else | |
| super.merge(options: @options) | |
| .merge(questions: @childrens.map(&:to_h)) | |
| end | |
| end | |
| end | |
| class MultipleChoicesQuestion < SingleChoiceQuestion | |
| end | |
| class DateQuestion < Question | |
| end | |
| class DSLContext | |
| def self.execute(subject, block) | |
| context = new(subject) | |
| context.instance_exec(subject, &block) | |
| end | |
| private | |
| def initialize(subject) | |
| @subject = subject | |
| end | |
| def section(name, &block) | |
| s = Section.new(name) | |
| @subject.add_section(s) | |
| self.class.execute(s, block) if block_given? | |
| end | |
| def question(name) | |
| q = FormattedQuestion.new(name, '__') | |
| @subject.add_question(q) | |
| end | |
| def question_group(name, &block) | |
| group = QuestionGroup.new(name) | |
| @subject.add_child(group) | |
| self.class.execute(group, block) | |
| end | |
| def date_question(name = '时间') | |
| q = DateQuestion.new(name) | |
| @subject.add_question(q) | |
| end | |
| module ChoiceQuestionExtension | |
| def options(choices) | |
| @subject.options = choices | |
| end | |
| def selected(option, &block) | |
| raise 'require a block' unless block_given? | |
| unless @subject.is_a?(SingleChoiceQuestion) || @subject.is_a?(MultipleChoicesQuestion) | |
| raise "require SingleChoiceQuestion or MultipleChoicesQuestion, got #{@subject}" | |
| end | |
| # 一般情况下 @subject 现在是一个 SingleChoiceQuestion | |
| group = QuestionGroup.new(nil) | |
| group.depends_on(option) | |
| @subject.add_child(group) | |
| self.class.execute(group, block) | |
| end | |
| def single_choice_question(name, options = nil, &block) | |
| _choice_question(SingleChoiceQuestion, name, options, &block) | |
| end | |
| def multiple_choices_question(name, options = nil, &block) | |
| _choice_question(MultipleChoicesQuestion, name, options, &block) | |
| end | |
| private | |
| def _choice_question(klass, name, options = nil, &block) | |
| q = klass.new(name, options) | |
| @subject.add_question(q) | |
| if block_given? | |
| result = self.class.execute(q, block) | |
| # 特殊处理: | |
| # 如果 block 返回 array of strings, 认为这个 array 是 options | |
| if result.is_a?(Array) && | |
| result.size > 0 && | |
| result.all? { |e| e.is_a?(String) } && | |
| q.options.nil? | |
| q.options = result | |
| end | |
| end | |
| raise '缺少 options' unless q.options && q.options.size > 0 | |
| end | |
| end | |
| include ChoiceQuestionExtension | |
| module FormattedQuestionExtension | |
| def formatted_question(*args) | |
| name, format, validations = _extract_args(args) | |
| q = FormattedQuestion.new(name, format, validations) | |
| @subject.add_question(q) | |
| end | |
| private | |
| def _extract_args(args) | |
| case args.size | |
| when 1 | |
| name, format, validations = nil, args.first, nil | |
| when 2 | |
| if args.last.is_a?(Symbol) | |
| name, format, validations = nil, args.first, args.last | |
| else | |
| name, format, validations = args.first, args.last, nil | |
| end | |
| when 3 | |
| name, format, validations = args | |
| else | |
| raise "不支持的 args: #{args}" | |
| end | |
| if validations.is_a?(Symbol) | |
| # shorthand form for: | |
| # | |
| # formatted_question('身高', '__cm', :number) | |
| case validations | |
| when :number | |
| validations = { type: validations, minimum: 0.1 } | |
| when :integer | |
| validations = { type: validations, minimum: 1 } | |
| else | |
| validations = { type: validations } | |
| end | |
| end | |
| # replace ______cm with __cm | |
| format = format.gsub(/_{2,}/, '__') | |
| [name, format, validations] | |
| end | |
| end | |
| include FormattedQuestionExtension | |
| module RepeatedGroupExtension | |
| def repeated_group(name = nil, &block) | |
| group = RepeatedGroup.new(name) | |
| @subject.add_child(group) | |
| self.class.execute(group, block) | |
| end | |
| def repeat_group_with_question(*args, &block) | |
| name, format, validations = _extract_args(args) | |
| q = ConditionalRepeatedGroupQuestion.new(name, format, validations) | |
| @subject.add_question(q) | |
| self.class.execute(q, block) if block_given? | |
| end | |
| end | |
| include RepeatedGroupExtension | |
| module BooleanQuestionExtension | |
| def boolean_question(name, &block) | |
| single_choice_question(name, %w{ 否 是 }, &block) | |
| end | |
| def truthy(&block) | |
| selected('是', &block) | |
| end | |
| end | |
| include BooleanQuestionExtension | |
| module PolarQuestionExtension | |
| def polar_question(name, &block) | |
| single_choice_question(name, %w{ (+) (-) }, &block) | |
| end | |
| def positive(&block) | |
| selected('(+)', &block) | |
| end | |
| end | |
| include PolarQuestionExtension | |
| module PresenceQuestionExtension | |
| def presence_question(name, &block) | |
| single_choice_question(name, %w{ 无 有 }, &block) | |
| end | |
| def present(&block) | |
| selected('有', &block) | |
| end | |
| end | |
| include PresenceQuestionExtension | |
| end | |
| module DSL | |
| def questionnaire(name, &block) | |
| q = Questionnaire.new(name) | |
| DSLContext.execute(q, block) if block_given? | |
| q | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment