Ruby on rails Ruby(on rails)检查树是否不是圆形的

Ruby on rails Ruby(on rails)检查树是否不是圆形的,ruby-on-rails,ruby,Ruby On Rails,Ruby,我有一系列问题。每个问题都有4个答案,每个答案都链接到下一个问题,下一个问题有4个答案链接到下一个问题,直到没有答案的“结束” 这是一个类似于树的结构,我想确定它保持这种状态——没有答案与已经提出的问题相关联 我想实现这一点的唯一方法是使用递归函数 我是这样想的: mq=[question.id] q=question.id def not_circular(q, mq) mother_questions = mq sister_questions = [] question = Q

我有一系列问题。每个问题都有4个答案,每个答案都链接到下一个问题,下一个问题有4个答案链接到下一个问题,直到没有答案的“结束”

这是一个类似于树的结构,我想确定它保持这种状态——没有答案与已经提出的问题相关联

我想实现这一点的唯一方法是使用递归函数

我是这样想的:

mq=[question.id] q=question.id

def not_circular(q, mq)
  mother_questions = mq
  sister_questions = []
  question = Question.find(q)
  question.answers.each do |a|
    if mother_questions.include?(a.next_question)
      return a.content
    else
      if !a.endlevel
        sister_questions << a.next_question
      end          
    end  
  end
  mother_questions = mother_questions + sister_questions

    question.answers.each do |a|
      if !a.endlevel
      return not_circular(a.next_question, mother_questions)
      end 
    end  
    return false
  end
def非循环(q,mq)
母亲的问题=mq
修女问题=[]
问题=问题。查找(q)
问题。答案。每个都做一个|
如果母亲有问题。包括?(a.下一个问题)
返回a.content
其他的
如果!a、 末级

修女问题我认为这项测试的责任(即有效性)应该在答案上,而不是问题上:如果有人将答案与已经提出的问题联系起来,那么答案就是错误的,即无效的,而不是选择的问题

所以,我会把这个测试转移到答案课上,这样做:为了使这个测试有效,你需要一个新的关联,它指向下一个问题的答案。我还没有测试过这个,但我认为它应该可以工作。在尝试这些方法之前,先测试
以前的\u答案
关联是否有效

#in Question
has_many :answers
has_many :previous_answers, :class_name => "Answer", :source => :next_question

#in Answer
belongs_to :question
belongs_to :next_question

validate :does_not_link_to_previously_asked_question

def does_not_link_to_previously_asked_question
  if self.previous_questions.include?(self.next_question)
    self.errors.add(:next_question_id, "This question has already been asked")
  end
end

def previous_questions
  current = [self.question]
  questions = []
  while current.size > 0
    current = current.collect(&:previous_answers).flatten.collect(&:question).reject(&:blank?)
    questions += current
  end
  questions.uniq
end

这里有一些理论我还没有完全考虑过,但如果你想要一个简单的保证,即不存在循环路径,实现这一点的最简单方法是确保没有答案可以从比它本身更高的层次链接到一个问题。因此,问题知道它们在树中的位置,答案不能链接到问题的级别之上。答案仍然可以链接到低于他们自己水平的任何问题,只是不能高于他们自己水平

否则,我认为您需要将此计算看作是一个独立的Ruby,而不是试图将其与您的模型联系得太远—您可以创建一个相当简单的问答树,其中只包含Id和父/子关系,以对数据建模,而不必在层之间进行太多对话。如果它在它自己的模块中,这也将使它更容易测试和管理


确保您无法添加可能导致当前答案的问题的方法是确保从答案中获得的问题不存在于该答案的任何路径上。如果在树模型中,每个节点都知道它的父节点和子节点,那么这将是一个相当简单的问题,即从当前答案(通过它的
parent
数组的每个成员)向上遍历树并识别该路径上的每个问题。这些问题构成了一个“排除”列表,不能合法地添加到当前答案中。

我最喜欢在树中甚至在有向非循环图中处理这个问题的方法是在联接表上进行验证。我不确定我是否完全理解您正在使用的结构,因此我将学习一个共同的问题,即互为先决条件的课程

class Course < ActiveRecord::Base
  has_many :course_relationships, dependent: :destroy
  has_many :prereqs, through: :course_relationships

  has_many :inverse_course_relationships, class_name: 'CourseRelationship', foreign_key: 'prereq_id', dependent: :destroy
  has_many :inverse_prereqs, through: :inverse_course_relationships, source: :course
end
课程
然后在联接表中放置验证:

class CourseRelationship < ActiveRecord::Base
  belongs_to :course
  belongs_to :prereq, class_name: 'Course'

  validate :is_acyclic

  def is_acyclic
    if course == prereq
      errors.add(:base, "A course can't be a prerequisite to itself")
      return false
    end

    check_for_course = Proc.new do |current_course|
      if course == current_course
        errors.add(:base, "Catch 22 detected. \"#{course.title}\" is already required before \"#{prereq.title}\".")
        return false
      end

      current_course.prereqs.each do |course_to_check|
        check_for_course.call course_to_check
      end
    end

    check_for_course.call prereq
    return true
  end
end
class CourseRelationship

这确保了每次建立新关系时,课程本身永远不会成为先决条件(即使是间接的)。

谢谢大家的建议

事实上,我用自己的方式解决了这个问题(或者至少我的测试让我觉得我成功地解决了这个问题)

这是我的密码:

mq = [Question.id]
q = Question.id

def not_circular(q, mq)
  if mq.empty?
   mother_questions = [q]
  else
   mother_questions = mq + [q]
  end  
  question = Question.find(q)
  question.answers.each do |a|
    if mother_questions.include?(a.next_question)
      return a.content
    else
      if a.next_question != 0 && !a.last_question
        if not_circular(a.next_question, mother_questions)
         return not_circular(a.next_question, mother_questions)
        end
      end          
    end  
  end      
  return false
end

现在,链接到上一个问题的答案(从而形成一个圆圈)返回给管理员,管理员可以更改它

你能在你的帖子中添加你的问题和答案吗?谢谢你的回答。但我真的不想在模型中这样做,把问题和答案联系起来。我希望这是在一个控制器的方法,检查一套问题的测验是否可以没有圆圈。谢谢你的答案!我真的很希望能够做到这一点,但我是否必须拯救所有问题的父母才能做到这一点?我想做的是创建一个自定义验证,告诉用户“更改下一个问题的答案xy,因为它会创建一个圆圈”,这取决于您在哪里执行此操作;你必须在某个地方有一个完整的树的表示——无论你采取什么方法,如果没有它,你都不可能验证它——但是我建议你把它从ActiveRecord模型中去掉,你根本不需要保存它。当我提到一个ID时,它可能只是一个guid或类似的。事实上,如果您对限制用户选项感兴趣,您甚至可以在客户机上实现签入JavaScript,这可能会使其更具响应性。