nandosola / dilithium-rb

A tiny framework to power your enterprise-ish stuff in Ruby
BSD 3-Clause "New" or "Revised" License
3 stars 3 forks source link

Move all finders from Entities to the Repository #41

Open mcamou opened 10 years ago

mcamou commented 10 years ago

Sample usage:

def spanish_active_users
  Repository.for(User).where(:active => true).and(:iso2 => 'ES').all
end

# Operators: :gt, :lt, :gte, :lte, :like, :not
# See: http://sequel.jeremyevans.net/rdoc/files/doc/querying_rdoc.html

Repository.for(User).where(:name => {:gt => 'Arthur Dent'}).all
Repository.for(User).where(:name => {:lt => 'Arthur Dent'}).order_by(:name, :descending).first
Repository.for(User).where(:name => nil}.order_by(:name, :descending).first

# :not can receive a Hash
# Should :not be a method? -> Whatever is easier with Sequel Datasets
# this
Repository.for(User).where(:name => {:not => nil}).all
Repository.for(User).where(:iso2 => {:not => {:like => 'A%'}}).all
# or this
Repository.for(User).where(:name => nil).not.all
Repository.for(User).where(:iso2 => {:like => 'A%'}).not.all

# Via a reference/child/parent attribute

all_users = Repository.for(User).where(:schedule => {:name => '9to5'})
                            .and(:computers =>{:brand => 'Coleco'}).all
#all_users.first.schedule ; all_users.first.computers

class User < BaseEntity
  reference :schedule
  children :computers
end

class Computer < BaseEntity
  parent :user
  attribute :brand
end

class Schedule < BaseEntity
  attribute :name, String
end

# Should we accept arrays? -> Again, whatever Sequel Datasets does
Repository.for(User).where(:iso2 => ['ES', 'MX']).all

class AbstractQueryObject
  # Builder methods
  def initialize(klazz)
  def where
  def and
  def or
  def order_by
  ...
  # Execution methods - execute the query and call create_object on each result
  def all 
  def first
  def count
end

class SequelQueryObject < AbstractQueryObject
class InMemoryQueryObject
class MongoQueryObject

class Repository
  def self.[](klazz)
    @repository_registry(klazz).new_query_object
  end
end
nandosola commented 10 years ago
Repository.for(User).where(:schedule => {:name => '9to5'}).
  and(:computers =>{:brand => 'Coleco'}).all

Here the root (User) is asked for user aggregates satisfying the query conditions. The result would be Array<User>. But, what would happen if filtering by children or reference is needed? Then I think each root should act as its own repository, using the tools already in place:

Repository.for(User).where(:schedule => {:name => '9to5'}).
  and(:computers =>{:brand => 'Coleco'}).all.select{|user_root| ... }

Thus using BaseEntity's default accessors.

IMHO in the example above, maybe an Array<ImmutableChildKlaz> or Array<ImmutableReferenceKlazz> should be returned by these methods but again, this is not necessary right now.

mcamou commented 10 years ago

Initial work done as part of #49: we now have a Repository.for method that returns a Repository for the given DomainObject subclass. Currently for subclasses of BaseEntity it returns the class itself (to preserve backwards compatibility) but once the client code is refactored to call Repository.for it would be the place to hook this into.

nandosola commented 10 years ago

Well, a special case that just shot me in the foot is this CustomFinder:

module Dilithium::Repository
  module Sequel
    module PermisoCustomFinders
      def fetch_by_department department_id
        unless department_id.nil?
          result_list = DB[:permisos_departamentos].where({departamento_id:department_id, active:true}).all
          permiso_departamento_h = result_list.first
          self.load_object(permiso_departamento_h)
        end
      end
    end
  end
end
...
module Abstra::BlueMountain
  module Base::Config::Model
    class PermisoDepartamento < Dilithium::BaseEntity
      extend Dilithium::Repository::Sequel::PermisoCustomFinders

      reference :departamento, Departamento
      multi_reference :permisos_acceso, Departamento
      multi_reference :permisos_ejecucion
    end
....
    class Departamento < Dilithium::BaseEntity
      parent :delegacion

      attribute :descripcion, String
      attribute :contacto, String
      attribute :telefono, String
      attribute :fax, String
      attribute :email, String

      reference :flujo, Flujo
      reference :actividad, Actividad
    end
  end
end

The thing is that :departamento is correctly resolved and attached as ImmutableEntityReference by the repository. However, Departamento has a parent :delegacion, which should be pruned in this special case.

The correct thing to do would be:

Repository.for(PermisoDepartamento).where(:departamento =>{:id => 42})
#> which in turn forwards the query to the Delegacion root (reference's parent)

In any case, I don't like passing raw :ids to the query. JPA, for instance, takes the following approaches:

//JPA
Employee employee = em.find(Employee.class, 1);
Employee employee = em.getReference(Employee.class, 1);  // lazy version

//JPQL
SELECT u FROM Users u WHERE u.partyAsUser = :partyAsUser AND u.party  = :party
Party party = new Party(id);
query.setParameter("party", party);