chipmunk chipmunk - 1 month ago 6x
Ruby Question

Ruby Interface, Collection of Objects

I have created an interface called

to hold in a collection of any objects from my project's models. I want this collection vs an array as I want other fields in

module Collection
def self.included(klass)
klass.attr_writer :list, type: Array
klass.attr_writer :class_type, type: Class
# Other fields go in here, along with their validations
klass.validate :validate_list

def validate_list
self.list.each { |o|
if(!o.instance_of? self.class_type)
klass.errors.add :list, 'Objects in list must be of the same type'

I want to use this
to hold a list of Models::Company 's objects, apart from other lists which I will add to Portfolio Model in the future. I want this list of companies to be only a part of the Portfolio Model.

class Portfolio
include Model::Collection

@schema = {
'type' => 'object',
'properties' => {
'id' => { 'type' => 'string' },
'title' => { 'type' => 'string' },
'description' => { 'type' => 'string' },
'companies_list' => {'type' => '?'}, # 1. Should this be array or Collections?
@modelName = 'portfolios'
@collectionName = 'portfolios'

store_in collection: 'portfolios'

field :title, type: String
field :description, type: String
field :companies_list, type: Array # 2. Should this be array or array of Collections?

embeds_many :companies


Any help is appreciated.


I saw that you come from Java world, and I guess you want to bring Java's generics to Ruby. But, at first place, why Java has generics? Let's have a history lesson.

In early Java (before 1.5), there are no generic types, so the programmers have to write code like this:

List list = new ArrayList();
// add some strings to the list

// we have to iterate over each element as an Object
for (Object obj : list) {
  // and then cast it to String
  String str = (String) obj;
  // in order to call String methods on it.
  String uppercased = str.toUpperCase();

  // ...

This is certainly not DRY. To ease the pain of casting, Java 1.5 introduces generics.

List<String> list = new ArrayList<String>();
// add some strings to the list

// now we can iterate over the elements as strings
for (String str : list) {
  // no more casting, yay!
  String uppercased = str.toUpperCase();

  // ...

But wait, where does the non-generic version go wrong in the first place?

In Java, the variable type determines which methods can be called on the object, not the object itself. If you declare the variable in a more general type (i.e. a superclass), you can't call methods that belongs to a more special type (i.e. a subclass). If you want to call those methods, you have to cast.

But what if the object itself can decide which methods can be called on it? Suddenly generics become useless. Ruby and many many other dynamic languages follow this way. Rubyists call it duck typing - if something walks like a duck and quacks like a duck, it is a duck.

list = ['foo', 'bar']
list.each do |str|
  # we don't care what type str is,
  # as long as it has the method upcase.
  str.upcase if str.respond_to?(:upcase)

So rubyists usually don't define container classes, they just use arrays. If type restriction should be applied, they just apply it when the object is added to the array.

list = []
list << something if something.is_a? Portfolio

Another reason to stick to arrays is that arrays have awesome literals like ['foo', 'bar'] and %w(foo bar) and %i(foo bar), which custom container types don't have.