0

I'm trying to implement a Facade in idiomatic Ruby while coming from Java. I can see that Rails' ActiveRecord is fond of using class methods for things like find_by(criteria) and does not use Repository pattern for that task.

My Facade wraps a specific webservice with several methods. My original idea was to make it's API similar to ActiveRecord (learning by imitation):

class MyEntity
  # ....

  def get_name
    @loaded_name + @loaded_surname
  end

  def delete
    @entity_access_service.delete(@id)
  end

  def save 
    @entity_access_service.save(@id, @loaded_name , @loaded_surname)
  end

  def self.find(id)
    data = @entity_access_service.get_data_for(id)
    MyEntity.new(data) #Or whatever way to populate my entity
  end
end

This, in theory, would work great:

e = MyEntity.find(10)
p e.get_name
e.delete

Or:

e = MyEntity.new(some stuff)
e.save

Question: For save and delete instance methods to work, I need to somehow get an instance of EntityAccessService. This instance should be mockable to test it in isolated environment. What is the correct way to do it?

I'm expecting my tests to look as simple as possible and without some weird hacks, as what I'm trying to implement seems fairly trivial.

I have thought of several options to do that:

  1. Having a class-level variable holding entity_access_service used by all of the entities created in application. In this case, where should I initialize this field? For example:

    class MyEntity
      @@entity_access_service = nil
    end
    
    # Somewhere else (where?):
    MyEntity.entity_access_service = MyEntityService.new(some_params_from_env)
    

    This way, in my tests I would have to initialize/mock it at start.

  2. Similar to 1 but initialize it in the class. This looks weird, especially if I know that my tests do not have required ENV params populated at all.

  3. Have an extra constructor/attribute to set the entity_service. This won't work, as save would not have this field initialized.

  4. Create a Repository class. This would work pretty ok, but seems to be not what Ruby people do.

Christopher Creutzig
  • 8,656
  • 35
  • 45
bezmax
  • 25,562
  • 10
  • 53
  • 84
  • And you can omit empty parentheses in both, method definitions and method calls. – Stefan Apr 06 '16 at 12:57
  • 1
    You *can*, that doesn't mean you *should*. That's far more of an opinion/personal choice than the strong preference for snake_case. – Dave Newton Apr 06 '16 at 12:59

1 Answers1

1

Following ActiveRecord's example, you can create a method on your class itself, or on the base class from which your other classes are derived.

ActiveRecord provides a method ActiveRecord::Base.connection which returns the connection object which all models use to access the database. You can do something similar:

class MyEntity
    ....

    def self.entity_access_service
      # return your service object
    end

    def self.find(id)
        MyEntity.entity_access_service.get_data_for(id)
        MyEntity.new(data) # Or whatever way to populate my entity
    end

    def save() 
        MyEntity.entity_access_service.save(@id, @loadedName, @loadedSurname)
    end
end

As far as initialization goes, you either have to have a initialization step in your app (and test suite) where service credentials are read from config files and passed into your MyEntity object, or your entity_access_service method can lazily create the object it returns on first access using a very common Ruby idiom:

def self.entity_access_service
  @entity_access_service || = # build entity_access_service object
end

Note that, by wrapping your class-level instance variables in class-level accessor methods, you can avoid the use of @@ which is a recommended best practice.

user229044
  • 232,980
  • 40
  • 330
  • 338
  • Given I'm using Rspec, where should I mock this service? Am I correct that I should create an instance-double, and write it directly into MyEntity.service_field before running the test? Or should I make some kind of `self.initialize_entity_service(service)` method as a "setter" for this field? Or, should I make a partial double of my MyEntity class, mocking the whole method `self.entity_access_service` alltogether? – bezmax Apr 06 '16 at 13:56
  • Yes to instance_double! The answer to how you provide the double really depends on what you want your api to look like. If you follow the method mentioned above where the class instantiates the access service itself, then you can mock whatever call instantiates the access service and return the instance double. Alternatively, you could provide a setter for the access service and use a before block to set the value in testing. I would probably lean towards the class instantiation path; the class setter path likely means a large set of initializers somewhere. – AndyV Apr 06 '16 at 16:40