开发者

Application design of controller (Rails 3)

开发者 https://www.devze.com 2023-02-15 11:25 出处:网络
This is hard to explain, but I will do my best: I am building a system where user\'s can take courses. Courses are made up of steps, that must be taken in order. In the system there are 6 step types

This is hard to explain, but I will do my best:

I am building a system where user's can take courses. Courses are made up of steps, that must be taken in order. In the system there are 6 step types (Download, Presentation, Video, Text, Quiz, and Survey)

The way a user accesses a STEP currently is:

http://example.com/courses/2/course_steps/1

As you can tell course_steps are nested under courses.

Below is the show method in course steps:

def show
  render "show_#{@course_step.step.step_type.name.downcase}"
end

As you can tell it basically picks a view to show_[TYPE] (quiz, survey, text..etc)

This works fine for simple steps such as a text, video, or download, but for co开发者_StackOverflowmplicated steps such as a quiz, this model does not work well for the following reasons:

  • How do I validate a form for a quiz or survey as I would be using a different controller (QuizAttemptsController).
  • It seems to break the REST principal as a quiz, survey..etc should be treated separately. (I know they are step types, but they can have their own actions and validations)

Step Model

class Step < ActiveRecord::Base
  belongs_to :step_type
  belongs_to :client
  has_one :step_quiz, :dependent => :destroy
  has_one :step_survey, :dependent => :destroy
  has_one :step_text, :dependent => :destroy
  has_one :step_download, :dependent => :destroy
  has_one :step_video, :dependent => :destroy
  has_one :step_presentation, :dependent => :destroy
  has_many :course_steps, :dependent => :destroy
  has_many :courses, :through => :course_steps
  has_many :patient_course_steps, :dependent => :destroy

  attr_accessible :step_type_id, :client_id, :title, :subtitle, :summary

  validates :title, :presence=>true
  validates :summary, :presence=>true

  def getSpecificStepObject()
    case self.step_type.name.downcase
      when "text"
        return StepText.find_by_step_id(self.id)
      when "quiz"
        return StepQuiz.find_by_step_id(self.id)
      when "survey"
        return StepSurvey.find_by_step_id(self.id)
      when "download"
        return StepDownload.find_by_step_id(self.id)
      when "video"
        return StepVideo.find_by_step_id(self.id)
      when "presentation"
        return StepPresentation.find_by_step_id(self.id)
    end
  end
end

Step Quiz Model:

class StepQuiz < ActiveRecord::Base
  belongs_to :step, :dependent => :destroy
  has_many :step_quiz_questions, :dependent => :destroy
  has_many :quiz_attempts, :dependent => :destroy

  accepts_nested_attributes_for :step
  accepts_nested_attributes_for :step_quiz_questions, :allow_destroy => true
  attr_accessible :step_id, :instructions, :step_attributes, :step_quiz_questions_attributes
  validates :instructions, :presence=>true
end

CourseStep Model

class CourseStep < ActiveRecord::Base
  belongs_to :step
  belongs_to :course

  validates_uniqueness_of :step_id, :scope => :course_id

  def next_step()
    Course.find(self.course.id).course_steps.order(:position).where("position >= ?", self.position).limit(1).offset(1).first
  end

  def previous_step()
     Course.find(self.course.id).course_steps.order("position DESC").where("position <= ?", self.position).limit(1).offset(1).first
  end
end

How would you suggest fixing this?


What you want to do is implement your Model as a Finite State Machine and continually reload the new or edit action until the desired state is reached, then your controller can display different views depending on state to allow multiple steps to happen.


One way I have solved the problem is by adding a member action of "submit_quiz"to the course_steps controller. I am not sure if I like this, as the code looks kind of ugly. I would appreciate feedback.(Note: I Am using CanCan so @course_step is created automagically in the course_steps_controller)

The things I don't like are:

  • show_quiz view has a lot of code in it
  • submit_quiz is in the course_steps_controller
  • quiz_attempt model has virtual attribute of quiz_questions (for validation purposes)

show_quiz.html.erb

<%= form_for (@quiz_attempt.blank? ? QuizAttempt.new(:started => Time.now.utc, :step_quiz_id => @course_step.step.step_quiz.id) : @quiz_attempt, :url => submit_quiz_course_course_step_path(@course_step.course, @course_step)) do |f| %>
<%= render :partial => 'shared/error_messages', :object => f.object %>
    <% @course_step.step.step_quiz.step_quiz_questions.each do |quiz_question| %>
        <h3><%= quiz_question.value %></h3>
        <% quiz_question.step_quiz_question_choices.each do |quiz_question_choice| %>
        <%= radio_button_tag("quiz_attempt[quiz_questions][#{quiz_question.id}]", quiz_question_choice.value, f.object.get_quiz_question_choice(quiz_question.id) == quiz_question_choice.value)%>
        <%= quiz_question_choice.value %><br />
        <% end %>

    <% end %>
    <%= f.hidden_field(:step_quiz_id)%>
    <%= f.hidden_field(:started)%>
    <%= submit_tag("Submit Quiz")%>
<% end %>

course_steps_controller.rb

 def show
    PatientCourseStep.viewed(current_user.id, params[:course_id], @course_step.step.id )
    render "show_#{@course_step.step.step_type.name.downcase}"
  end

  def submit_quiz
    @quiz_attempt = QuizAttempt.new(params[:quiz_attempt])

    if !@quiz_attempt.save()
      render 'show_quiz'
    end
  end

quiz_attempt.rb

class QuizAttempt < ActiveRecord::Base
  belongs_to :step_quiz
  belongs_to :patient

  attr_accessor :quiz_questions
  attr_accessible :step_quiz_id, :patient_id, :started, :ended, :correct, :incorrect, :quiz_questions

  validate :answered_all_questions?

  def get_quiz_question_choice(quiz_question_id)
    unless self.quiz_questions.blank?
      quiz_questions[quiz_question_id.to_s]
    end
  end

  private
    def answered_all_questions?
      #Making sure they answered all the questions
      if self.quiz_questions.blank? or self.quiz_questions.try(:keys).try(:count) != self.step_quiz.step_quiz_questions.count
        errors.add_to_base "Not all questions were answered"
    end
  end
end


  def submit_quiz
    @quiz_attempt = QuizAttempt.new(params[:quiz_attempt])

    if !@quiz_attempt.save()
      render 'show_quiz'
    end
  end
0

精彩评论

暂无评论...
验证码 换一张
取 消