简体   繁体   中英

Rails AngularJS polymorphic best practice

I have a rails polymorphic model "Task". There will be many different models that have "tasks". Right now I have a model "Patient" and for each patient I need to pull the tasks assigned to them, put them on each patient index page, and then handle the crud actions of tasks within that page using angular. Herein comes the internal struggle I have been having. I can see two very different ways of doing this and I'm not sure which way would be considered best design practice:

  1. There will be many rails objects that have tasks, so tasks should keep all the logic to themselves and decipher what object is asking for the tasks. There will be a TasksController in angular that will know by the page that you are on, what the "taskable" object is that you are dealing with. It will then query the tasks_controller to pull all tasks related to that taskable object. For each crud action the task object and resources will handle the data.

  2. Each object (in this case each patient), knows what tasks it has by association (patient.tasks), so the logic should flow through whatever the taskable object is. In angular there will be a PatientsController that will find the current patient, then query the patients_controller in rails, and return the relevant tasks assigned to that patient. For each crud action, the patient object and resources will handle the data.

Right now I have gone with option 1. Here is my TasksController.js:

BC.controller('TasksController', ['$scope', 'TaskResource', function($scope, TaskResource){
  $scope.init = function(taskableId, taskableType, currentUserId) {
    $scope.task = {};
    $scope.task.taskableId = taskableId;
    $scope.task.taskableType = taskableType;
    $scope.task.creatorId = currentUserId;

    TaskResource.query({taskable_id: $scope.task.taskableId, taskable_type: $scope.task.taskableType}).then(function(result){
      $scope.tasks = result.reverse();
    });
  };

 $scope.taskCreate = function($event, task){
   $event.preventDefault();
   new TaskResource({text: task.text, taskableType: task.taskableType, taskableId: task.taskableId, creatorId: task.creatorId})
   .create().then(function(result){
     $scope.tasks.unshift(result);
     $scope.task.text = '';
   });
 };
}]);

I have an init function where I pass in the taskable_id and taskable_type of whatever page I am on so that the correct tasks are queried from the tasks_controller and Task model. This is currently working but I am starting to see some possible design problems and can't help feel that this might be a bad design practice. Any help/thoughts on this would be greatly appreciated. Thank you.

Short Answer

The short answer, the first option is in line with 'standard practice' and is the better choice. Best Practice starts at how you implement this standard practice - or standard need that arises in Front End - Back End Applications

Long Answer

Choose the solution that will equate to less cost and make change in the future easy

Once you've identified a standard practice - how best to arrange the communication of Angular <=> Rails, the quest for best practice as how that should flow begins. There are many methods one can take. Restangular , Angular low level $http , Angulars high level ngResource , or a Rails specific method - angularjs-rails-resource . These all work and there are more.

But you are using one of those, and that isn't your question. The subtle question being asked here is not what method should these use to talk, but in what way should they talk. Best Practice in this situation is actually dictated by a set of guidelines rather than rules. These guidelines dictate that the best solution is probably somewhat unique to your own App, and it's all about leveraging the cost of each choice you take.

The problem is at the early stages of an app, we never really know the actual costs. As Sandi Metz "You will never know less than you do right now". So, I'd recommend in the beginning stages, anytime you are faced with decisions such as this, do the one that utilizes your API the most. Rather than doing any form of figuring out what you want in Angularjs, pass the data to the Rails API and let it figure that out. At this level you will want to use pain driven development - listen to the pains of your app. When things get hard, take a step back and see what is causing the pain. Listen to the pain. It will tell you when something is wrong.

Structure your Public Interface with SOLID Principles

This concept thrives off of SOLID Obect Oriented Design. The Gist of it is that a guideline for making applications that are easy to change, operate in a way that Angular is saying *"Hello Rails API, I know you have what I want and I trust you to return it to me" Rather than saying *"Hello Rails, I know you have what I want, I know how you need to get it" Which only somewhat better than the worst case, "Hello, Mr. Rails. I Know you have what I want, I know what I want, and I know how to get it".

This is best explained by Sandi Metz in her book "Practical Object Oriented Design in Ruby". Chapter 4 explains it very well. While in that chapter your dealing with a public interface (API) of an Object , and in this question your referring to an actual public interface (API) of a Rails App, once you look past the surface layer of the Rails app, you end up dealing with an Object Interface, as in the end you'll probably end up calling a method in your controller, which at that point the Object Public Interface applies.

Code speaks louder than words, if some of these don't make sense, post more code and we can try explaining this in code which is easier to understand than words :)

Edit: I plan to work through this again and see if I can't restructure it in a more comprehensible way

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM