0

I have developed a struct that generates random data for testing purposes in my app but only does so if the Realm database is empty. The service class responsible for CRUD operations returns outcomes / results as RxSwift Observables. Generating new data is predicated on the contents of the Realm - using guard is the logical choice but requires adding a new method to the service that returns Int rather than an Observable which seems to be an unnecessary duplication of code and somewhat inconsistent.

i.e.

class Service {
  let realm = try! Realm()
  ...
  // Existing reactive code
  func patients() -> Observable<Result<Patient>> {
    let results = realm.objects(Patient.self)
    return Observable.collection(from: results)
  }

  func objectCount() -> Int {
    let realm = try! Realm()
    return realm.objects(Patient.self).count
  }
}

struct DataGenerator() {
...
  func createRandomPatients() {
    let service = Service()
    guard service.objectCount() == 0 else { return }
    ...
    //go on to generate patients

The only way I can figure to replicate this control flow using the reactive service method is to bind the Observable to a Variable

i.e.

... service code as above ...
func createRandomPatients() {
   let isEmpty = Variable<Bool>(false)
   service.patients().map{ $0 == 0 }.bind(to: isEmpty)
   guard isEmpty.value else { return }
   // etc

It seems like a bit of a fudge as it perhaps isn't quite as clear, but it avoids adding code to the service class that won't be used in the production app. Is this bad practice or just personal preference? I'm open to alternative solutions if anyone has one...

rustproofFish
  • 931
  • 10
  • 32

1 Answers1

0

Faking is definitely something that should be handled with polymorphism.

A good design would be to define a common interface; for example it can be PatientsSource:

protocol PatientsSource {

    func patients() -> Observable<Result<[Patient]>>

}

Than you can take advantage of polymorphism by defining:

class PatientsService: PatientsSource... {
    //w/e
}

struct FakePatients: PatientsSource {
    //your random patients generation
}

If you use a Service style architecture then you should pass the Service and the PatientsSource to the user (like your controller) separately even though they might end up to be the same object.

Timofey Solonin
  • 1,393
  • 13
  • 20
  • A good point and, as it happens, I’m 50% there anyway as PatientService is actually based on a protocol (PatientServiceType) so faking/mocking is something on my to-do refactoring list. Any thoughts on the control flow issue though? – rustproofFish Apr 17 '18 at 11:39
  • @rustproofFish I totally misread your question but now I have a bit of a trouble understanding what is the issue. Can you give a bit more info as to where you are calling `createRandomPatients()`? I feel like the control flow issue can be solved with something like a `Refreshable` decorator https://stackoverflow.com/questions/47031210/rxswift-reload-tableview/47031790#47031790 – Timofey Solonin Apr 17 '18 at 12:24
  • @rustproofFish and the `guard` statement sounds like something that should be a `filter` part of the `patients` chain. Is it a UI requirement? – Timofey Solonin Apr 17 '18 at 12:29
  • Sorry for the confusion. The example I've provided is really pseudocode because my app uses a lot of protocols and listing the full code would get a bit confusing. PatientService provides CRUD services (associated with a Realm data store) to the rest of the app in a reactive fashion using RxSwift. While I'm developing the app, I want the Realm populated with randomly generated entities and thought it would be easier to utilise an existing PatientService method (which returns Observable>) than write an imperative method just for this purpose. Of course... – rustproofFish Apr 17 '18 at 16:18
  • ...I could use Realm.isEmpty but I will be adding other entities for it so wanted to check specifically for Patient entities. The call to a separate clsss/struct that generates random data has been placed in PatientService just because it seems to be an appropriate place, but it may have caused confusion here! Maybe AppDelegate (or a similar top-level class) might be better. Does that help? – rustproofFish Apr 17 '18 at 16:21
  • @rustproofFish if you main objective is to have a prepopulated Realm then it is probably better to use a postcompile script or some single run `AppDelegate` snippet. I am not sure why you would use a `Variable` to check if a particular collection from Realm is empty. Just use tools provided by the Realm without incorporating RxSwift even if it means a slight code duplication. – Timofey Solonin Apr 18 '18 at 19:25