0

I have the code as follow :

class Synchronization

  def initialize

  end
  
  def perform
    detect_outdated_documents
    update_documents
  end

  private

  attr_reader :documents


  def detect_outdated_documents
    @documents = DetectOutdatedDocument.new.perform
  end

  def update_documents
    UpdateOutdatedDocument.new(documents).perform
  end

@documents is an array of Hashes I return from a method in DetectOutdatedDocument. I then use this array of Hash to initialize the UpdateOutdatedDocument class and run the perform method.

Is something like this correct? Or should I use associations or something else?

class diagrams

Tom Lord
  • 27,404
  • 4
  • 50
  • 77
Sanico
  • 39
  • 7

2 Answers2

2

Ruby to UML mapping

I'm not a Ruby expert, but what I understand from your snippet given its syntax is:

  • There's a Ruby class Synchronization: That's one UML class
  • The Ruby class has 4 methods initialize, perform, detect_outdated_documents, and update_documents, the two last being private. These would be 4 UML operations.
  • initialize is the constructor, and since it's empty, you have not mentioned it in your UML class diagram, and that's ok.
  • The Ruby class has 1 instance variable @documents. In UML, that would be a property, or a role of an association end.
  • The Ruby class has a getter created with attr_reader. But since it is in a private section, its visibility should be -. This other answer explains how to work with getters and setters elegantly and accurately in UML (big thanks to @engineersmnky for the explanations on getters in Ruby, and for having corrected my initial misunderstanding in this regard)
  • I understand that SomeClass.new creates in Ruby a new object of class SomeClass.

Ruby and dynamic typing in UML

UML class diagrams are based on well-defined types/classes. You would normally indicate associations, aggregations and compositions only with known classes with whom there’s for sure a stable relation. Ruby is dynamically typed, and all what is known for sure about an instance variable is that it's of type Object, the highest generalization possible in Ruby.

Moreover, Ruby methods return the value of the latest statement/expression in its execution path. If you did not care about a return value of an object, you'd just mark it as being Object (Thanks engineersmnky for the explanation).

Additional remarks:

  • There is no void type in UML (see also this SO question). An UML operation that does not return anything, would just be an operation with no return type indicated.
  • Keep also in mind that the use of types that do not belong to the UML standard (such as Array, Hash, Object, ...) would suppose the use of a language specific UML profile.

Based on all this, and considering that an array is also an Object, your code would lead to a very simple UML diagram, with 3 classes, that are all specializations of Object, and a one-to-many association between Synchronization and Object, with the role @documents at the Object end.

Is it all what we can hope for?

The very general class diagram, may perhaps match very well the implementation. But it might not accurately represent the design.

It's your right to model in UML a design independently of the implementation. Hence, if the types of instance variables are known by design (e.g. you want it to be of some type and make sure via the initialization and the API design that the type will be enforced), you may well show this in your diagram even if it deviates from the code:

  • You have done some manual type inferencing to deduce the return type of the UML operations. Since all Ruby methods return something, we'd expect for all Ruby methods at least an Object return type. But it would be ok for you not to indicate any return type (the UML equivalent to void) to express taht the return value is not important.
  • You also have done some type inference for the instance variable (UML property): you clarify that the only value it can take is the value return by DetectOutdatedDocument.new.perform.
  • Your diagram indicates that the class is related to an unspecified number of DetectOutdatedDocument objects, and we guess it's becaus of the possible values of @documents. And the property is indicated as an array of objects. It's very misleading to have both on the diagram. So I recommend to remove the document property. Instead, prefer a document role at the association end on the side of DetectOutdatedDocument. This would greatly clarify for the non-Ruby-native readers why there is a second class on the diagram. :-) (It took me a while)
  • Now you should not use the black diamond for composition. Because documents has a public reader; so other objects could also be assigned to the same documents. Since Ruby seems to have reference semantic for objects, the copy would then refer to the same objects. That's shared aggregation (white diamond) at best. And since UML has not defined very well the aggregation semantic, you could even show a simple association.

A last remark: from the code you show, we cannot confirm that there is an aggregation between UpdateOutdatedDocument and DetectOutdatedDocument. If you are sure there is such a relationship, you may keep it. But if it's only based on the snippet you showed us, remove the aggregation relation. You could at best show a usage dependency. But normally in UML you would not show such a dependency if it is about the body of a method, since the operation could be implemented very differently without being obliged to have this dependency.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Generally when documenting a "no return type you care about" in Ruby it would be documented as returns Object (YARD does this by default when the method or the return type is undocumented). – engineersmnky May 18 '21 at 19:24
  • @engineersmnky thank you for this feedback. Today I’ve learned a lot about Ruby ;-) I’ve updated my wording to clarify that void is not even part of UML standard (it’s just a common practice of adepts of languages using this type). If I understand well your explanation, and unless a more specific type is known, the return type of a method should be Object. – Christophe May 18 '21 at 19:39
  • Correct. Since everything in ruby is an Object, and every method always returns, stating that a method returns an instance of Object is universally ambiguous. – engineersmnky May 18 '21 at 19:44
  • How is UML strongly typed? That's soething you have on compiled languages. UML can perfectly be used with no types at all (which is done during rough design). Also (I know this is Mickeysoft) classes do not have methods but operations. A method descibes behavior. _The Operation specifies the name, type, Parameters, and Constraints for such invocations._ (p. 114 of UML 2.5) – qwerty_so May 18 '21 at 23:41
  • @qwerty_so in Ruby classes have methods. Other than a limited list of keywords every invocation in Ruby is a method call, due to the fact that every thing is an Object and every object is an instance. Technically Ruby classes do not even have "*class methods*" they have instance methods of the class because a class declaration creates an instance of the class `Class`. – engineersmnky May 19 '21 at 03:01
  • @engineersmnky The term _method_ is wrong from an UML perspective. I know it's commonly used from from that perspective. But when answering from UML the terms should be used correctly. Despite their common use otherwise. However, what really itches me is the _typing_. – qwerty_so May 19 '21 at 06:58
  • 1
    @qwerty_so My answer tries to make the bridge between the Ruby terminology and the UML terminology. In this regard, I can use both terms: when using method I mean the Ruby method, and when I say operation I mean the UML operation, but as both refers to the same named “thing” … this being said, about the strong typing you are completely right and I’ll reword later today to more accurately express what I meant. – Christophe May 19 '21 at 07:03
  • @engineersmnky Actually a more clear statement is already on p. 15 of UML 2.5 _For example, an operation owned by a class may have a related method that defines its detailed behavior._ I know that the term _method_ is used different all over the IT. But the UML guys made a rather strict definition. I think for good. But they won't change the world. – qwerty_so May 19 '21 at 09:12
  • 1
    @qwerty_so indeed, the difference in terminology is on purpose. Method invocation is already a very precise language specific implementation technique. Some languages like smalltalk don’t have methods but use messages instead. Some others define operations with normal procedures and functions using explicit type arguments (I think to remember that’s the ada way). And some have methods but call them functions. UML intends to be implementation independent. I think that operation was borrowed from some abstract type theory. – Christophe May 19 '21 at 10:14
  • 1
    @qwerty_so I've reworded now my answer about the typing. (btw I didn't find an ambiguous use of "method", but I've added Ruby and UML to disambiguate any possible confusion). Thanks for the constructive remarks. – Christophe May 19 '21 at 19:49
  • @engineersmnky I've reworded my answer to take into account the clarifications and hint in the comments. I hope I did get it right on Ruby (I did a quick check on an online compiler to check about reference semantic). But you seem to know the language very well: can you confirm that I didn't write anything misleading ? – Christophe May 19 '21 at 19:51
  • @Christophe I would hazard to comment, even generally, on the concept of UML diagramming, as @qwerty_so has shown I currently lack the semantic knowledge to offer a truly educated opinion. That being said the only flaw I see with the ruby portion is that `attr_reader` creates a getter however its declaration below the `private` line also makes this getter privatized as well. `attr_reader` does not create a setter of any kind however you can "set" the value of an instance variable of the same name which will modify the return from the getter method. – engineersmnky May 19 '21 at 20:28
  • `attr_reader :name` generates a getter method (which would be a 5th method) that resembles the following long hand `def name; @name; end` so by changing the value of `@name` directly through assignment `=` or other mutable methods as may be available you can change the value returned by the getter. All that being said nothing in ruby is technically private it is more like putting up a gate without any surrounding fence, it suggests you shouldn't but certainly does not prohibit you. – engineersmnky May 19 '21 at 20:32
  • 1
    @engineersmnky Thanks again for your helpful explanations :-) – Christophe May 19 '21 at 20:53
0

There is no relation, UML or otherwise, in the posted code. In fact, at first glance it might seem like a Synchronization has-many @documents, but the variable and its contents are never defined, initialized, or assigned.

If this is a homework assignment, you probably need to ask your instructor what the objective is, and what the correct answer should be. If it's a real-world project, you haven't done the following:

  1. defined the collaborator objects like Document
  2. initialized @documents in a way that's accessible to the Synchronization class
  3. allowed your class method to accept any dependency injections

Without at least one of the items listed, your UML diagram doesn't really fit the posted code.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199