rubyrestdatabase-designsinatraobject-composition

Preventing broken object composition in REST


I have a Sinatra app with a Project model and a Task model, and a project has_many tasks.

It's a composition type of relationship, meaning a project cannot exist without tasks associated.

I create a project on /projects/new, then redirect to /projects/:id/tasks/new to add tasks to the project. On the latter page, I:

  1. instantiate the newly-created project instance from the param in the URL
  2. create a number of tasks
  3. validate all the tasks
  4. add them to the project.

The problem is, if one gets interrupted during any of the step above, then no tasks will be added and the project will be saved in the database with no tasks associated. This will result in errors when I calculate total task duration, and in other situations.

I could instantiate and save both records on the same page, and that would solve the problem.

But, is there a way to split Project and Task creation across dedicated URLs without this resulting in childless projects?


Solution

  • I would advise you to not create the Project until at least one Task has been created. You can do this by saving the project details in session and then actually creating the Project from the tasks controller, for example (pseudocode):

    # projects controller
    def create
      session[:new_project] = <extract session params to PORO>
      redirect_to new_tasks_url
    end
    
    # tasks controller
    def new
      @new_project_params = session[:new_project]
      # use this in the view as needed
    end
    
    def create
      Project.transaction do
        project = Project.create(session[:new_project])
        project.tasks.create(task_params)
      end
    end
    

    You can see how to use session in Sinatra here: https://sinatrarb.com/faq.html#sessions

    Another option is to change your code to allow Projects with no Tasks. My hunch tells me this is what users expect; if they create a project and then don't have time to create any tasks; they would maybe assume that the project would be actually saved.

    This will result in errors when I calculate total task duration, and in other situations.

    It seems like this particular problem could be resolved by just defaulting total task duration to 0. But then again, I don't know the actual purpose of your application here, so maybe I'm wrong.