login about faq

I'm coming from a .NET background, where it is a practice to not bind domain/entity models directly to the view in not-so-basic CRUD-ish applications where the view does not directly project entity fields as-is.

I'm wondering what's the practice in RoR, where the default persistence mechanism is ActiveRecord. I would assert that presentation-related info should not be leaked to the entities, not sure though if this is how real RoR heads would do it.

If DTOs/model per view is the approach, how will you do it in Rails?

Your thoughts?

Some examples: - A view shows a list of invoices, with the number of unique items in one column. - A view shows a list of credit card accounts, where possibly fraudulent transactions are executed. For that, the UI needs to show accounts that have more than one transaction in red.

For both scenarios, The lists don't show all of the fields of the entities, just a few to show in the list (like invoice #, transaction date, name of the account, the amount of the transaction)

For the invoice example, The invoice entity doesn't have a field "No. of line items" mapped on it. The database has not been denormalized for perf reasons and it will be computed during query time using aggregate functions.

For the credit card accounts example, surely the card transaction entity doesn't have a "Show-in-red" or "IsFraudulent" invariant. Yes it may be a business rule, but for this example, that is a presentation concern, so I would like to keep it out of my domain model.

EDIT: A new example:

    //The persistent/Domain model, can be implemented as AR in RoR

public class Bus : Entity
{   
    public int BusNo { get; set; }
    //to represent a one-to-many association mapping    
    public IList<Person> passengers {get; set;}

//to represent many-to-one association mapping
    public Person driver {get; set;}
    public LtfrbFranchise permit {get; set;}
    public Location currentLocation {get; set;}

//other data fields/association mappings

public void AddPassenger(Person pax){
        pax.Ride(this);
        passengers.Add(pax);        
    }

public bool IsColorum(){  
        return !permit.AllowsTripOn(currentLocation);                       
    }

public bool IsOverloading(){
        return (passengers.Count() > permit.SeatingCapacity);
    }
}

//The UI only requires the following:
[Serializable]
public class BusListDTO
{  
    public int BusNo {get; set;}  
    public string DriverName {get; set;} //from Bus.driver.LastName + "," + Bus.driver.FirstName
    public bool ShouldBeDisciplined {get; set;}  //this is TRUE when both IsColorum() and IsOverloading() of Bus are TRUE. If TRUE. a grid row should show 3 buttons.
}

BusListDTO can be used to show information to the UI, or serialize it as JSON/XML should the need arises.

Without a DTO, the entity/AR will have a lot of members that are not really persisted since the entity evaluates them for every call. Some examples:

  • You can add a DriverName attribute to the Bus entity but the attribute DriverName is a presentation concern.
  • You can add ShouldBeDisciplined to the Bus entity, perhaps persist it to save up on DB roundtrips.

Should the Bus entity be involved in a lot of views that does not directly show its shape, you end up with a monolithic AR class. In a large team scenario, this is a maintenance nightmare, where you spend a lot of time communicating what attributes do in specific use-cases and you have devs who use attributes indiscriminately.

asked Feb 24 '10 at 09:25

leypascua's gravatar image

leypascua
1.6k115

edited Apr 17 '10 at 09:04

Looks like the RoR community does not have a concept of data transfer objects/view models. I asked the same question in stackoverflow with few takers ehehe

(Feb 25 '10 at 10:12) leypascua leypascua's gravatar image
2

Most - or let's rephrase it this way - a lot of RoR heads are Web developers. And compared to the enterprise, the problem domain or scope that an average Rails developer will encounter is not on the scale as to warrant the use of DTO's. If you would also notice, the average RoR team is small - at around 3-4 devs working on a project - and in this setup, the benefits of having an extra layer of abstraction that a DTO provides may seem nil or non-existant.

(Mar 09 '10 at 12:46) Erol Erol's gravatar image
2

I think you should ask first whether a DTO is needed or not. I have the impression that you believe the DTO is the right way to solve the problem you showed. For example, why is BusNO not part of your model? DriverName is part of your model though it is not a persistent information by itself but rather an aggregate of persistent attributes, or perhaps delegated to another model.

Perhaps you can give a better example because from what you've shown, I am not convinced that these aren't part of your model.

(Apr 17 '10 at 05:51) Greg Moreno Greg%20Moreno's gravatar image

Flattened or unattached ActiveRecord models are a bad choice for DTO's. As the name implies, AR objects are meant to be persistence objects, bundled with ALL the functionality to handle data persistence - dynamic finders, validations, observers, callbacks, as in everything - which I'm pretty sure you won't be needing for presentation purposes alone.

What you maybe looking for is a mix of simple Ruby classes and ActiveModel. You can transform any Ruby class to act like ActiveRecord, without having to load all of its functionality and avoid the unnecessary bloat.

class BusDTO
  attr_accessor :bus_no, :driver_name, :should_be_disciplined

  def initialize(attributes = {})
    @bus_no = attributes[:bus_no]
    @driver_name = attributes[:driver_name]
    @should_be_disciplined = attributes[:should_be_disciplined]
  end
end

Need serialization? Include ActiveModel::Serialization in your class:

class BusDTO
  include ActiveModel::Serialization
end

bus_dto = BusDTO.new(:bus_no => "123", :driver_name => "Test", :should_be_disciplined => false)
bus_dto.serializable_hash
bus_dto.to_xml
bus_dto.to_json

You can then implement your own "build_transfer_object" method inside your AR model:

class Bus << ActiveRecord::Base
  def build_transfer_object
    BusDTO.new :bus_no => self.bus_no, :driver_name => self.driver.name, :should_be_discipline => [whatever conditions apply]
  end
end

bus = Bus.first(1)
bus_dto = bus.build_transfer_object
bus_dto.to_xml # Or do whatever you want to do with it
link

answered Apr 17 '10 at 20:39

Erol's gravatar image

Erol
1.3k1416

edited Apr 18 '10 at 05:52

Been pondering on this and here are some of my notes...

  1. DTOs are just flattened objects, so why not have ActiveRecord models that are as flat as a DTO where its fields directly map to the data model? These ActiveRecord models will rarely have object association mapped with them. Since these are not persistent/domain models, these classes may contain presentation-related concerns.
  2. Like what the guys from ALT.NET and DomainDrivenDesign Y!Groups are advocating, "There is no single model to provide for all of your needs." Models are not reusable across bounded contexts. For example, given an ERP scenario where a marketing department has a concept of a "quote" and sales has an "invoice" when a quote is approved. It is highly discouraged to have one model that represents both concepts even if they have a lot of data in common. Therefore, aside from having a Quote and an Invoice class which may have their own invariants in place, you can also create flattened ActiveRecord classes that correspond to the fields of your views.
link

answered Mar 09 '10 at 00:47

leypascua's gravatar image

leypascua
1.6k115

Everything in RoR is a class, in essence, objects. Hence data transfer objects aren't explicitly needed. Want a field called line_item for the invoices to represent the number of transactions mapped to it? Declare an instance method in the invoice model which may be then be called anywhere you can instantiate an invoice. You can also go ahead and declare an :invalid? method on the invoice model that will simply return true if the invoice_count is greater than 1.

Want to dynamically render different HTML or CSS in relation to your invoice? Use a helper (resource module accessible from the views) method which will read the value of the :invalid method you just made, then rendering the "Red" CSS class should the current invoice being rendered is indeed, invalid.

Based solely on you example, and assuming all Models, Views and Controllers have been set up, rails can render the invoice view with something like:

#views/invoices/index.rhtml

<h1>Invoices</h1>

<table id = "invoice-list>
 <% @invoice.each do |invoice| %>
  <% content_tag :tr, :class => (invoice.line_items > 1)? ? 'Red' : 'Normal'  do %>
   <td> <%= invoice.id %> </td>
   .
   # other fields here
   .
   <td> <%= invoice.amount %> </td>
  <% end %>
<% end %>
</ul>

As for the RoR community not being familiar with Data Transfer objects - I agree, in that we rarely (if ever) use the term. As far as following the spirit of the principle though, any RoR developer worth his oats will already be doing so - keeping data manipulation and other convenience method declarations away from the view, stored in either helpers, libraries, or models.

It's just that DTO's no longer need to be explicitly defined, as the structure of Rails is built around the MVC design principle so tightly that it forces them on you without even introducing what they are.

March 9, 2010 EDIT:

Most if not all of those concerns come from theoretical standpoints, and I believe you're not getting a good idea of what ActiveRecord really is. If you have something specific that you can't get ActiveRecord to do, post it here and I'll be glad to answer. :)

Some specific comments:

  1. Why would the :invalid method be an invariant if its value changes depending on the invoice object calling it?

  2. As far as a flat data mapping is concerned, ActiveRecord already provides one out of the box - the :attributes method.

  3. I agree, no single model can handle everything. That's why unless it's a subclass, one should never use one model to represent both a quote and an invoice.

  4. If roundtrips and excessive queries are you're concern, an AR model rarely ever needs to get called more than once for simple to moderate data manipulation. Once the column values have been loaded into memory, it's yours to play with. Need to load only specific columns so you can save some milliseconds on the query? AR can help you with that as well.

link

answered Mar 08 '10 at 17:54

nicosuria's gravatar image

nicosuria
412

edited Mar 09 '10 at 02:18

In Java and .NET, a DTO is also an instance of a class. In most cases, it is a flattened version of your persistent/ActiveRecord model. It is generally not recommended to pass around an instance of your persistent models since it contains relationship information in the form of object associations. DTOs are used to fetch and project information that are only needed to save on round trips and excessive data fetching.

My concern is if you always use and re-use persistent entities to project data to the screen, it will be full of invariants (just like the :invalid method you're proposing)

(Mar 09 '10 at 00:21) leypascua leypascua's gravatar image

I've actually adopted the concepts you're proposing in .NET using an ActiveRecord implementation. It is easy and straightforward like what it would have been in RoR but you'll probably have a lot of headaches when your views don't exactly map to your persistent/ActiveRecord models. This is where the ActiveRecord approach fails as a pattern as most object-oriented heads would say. It just doesn't scale if your problem domain is quite complex.

(Mar 09 '10 at 00:25) leypascua leypascua's gravatar image

I think we're veering off-topic, any specifics? Also please see my edit.

(Mar 09 '10 at 02:19) nicosuria nicosuria's gravatar image

Oh and this may not answer your questions directly, but it's an interesting read nonetheless: http://stackoverflow.com/questions/7864/why-all-the-active-record-hate

(Mar 09 '10 at 02:24) nicosuria nicosuria's gravatar image

Updated the question to show an example of Entity/AR to DTO mapping. I think I should treat my DTOs as ARs too and fetch data from denormalized database view-like objects.

(Mar 09 '10 at 03:52) leypascua leypascua's gravatar image
1

I think you guys are debating on a "design" (aka more of an art) issue. You can always make something complicated or simpler regardless of what implementation framework you use.

(Apr 17 '10 at 06:04) Greg Moreno Greg%20Moreno's gravatar image

The question is not for your everyday CRUD needs, the setting is more on an enterprise level where models tend to be very complex as they are being used in different bounded contexts. First hand experience tells me the humble activerecord won't scale for very complex requirements, most especially if you have a lot of different screens where your AR will be used.

(Apr 19 '10 at 00:19) leypascua leypascua's gravatar image
1

"Everything in RoR is a class, in essence, objects"

This should be "everything in ruby is an object, including a class" :)

(Apr 20 '10 at 23:20) Greg Moreno Greg%20Moreno's gravatar image
showing 5 of 8 show all
Your answer
toggle preview

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Markdown Basics

  • *italic* or __italic__
  • **bold** or __bold__
  • link:[text](http://url.com/ "title")
  • image?![alt text](/path/img.jpg "title")
  • numbered list: 1. Foo 2. Bar
  • to add a line break simply add two spaces to where you would like the new line to be.
  • basic HTML tags are also supported

Tags:

×15
×8
×5

Asked: Feb 24 '10 at 09:25

Seen: 1,789 times

Last updated: Apr 18 '10 at 05:52