module Shamu::Services::ActiveRecordCrud
Adds standard CRUD builders to an {ActiveRecordService} to reduce boilerplate for common methods.
@example
class UsersService < Shamu::Services::Service include Shamu::Services::ActievRecordCrud # Define the resource that the service will manage resource UserEntity, Models::User # Define finder methods #find, #list and #lookup using the given # default scope. define_finders Models::User.active # Define change methods define_create define_update # Common update/change behavior for #create and #update define_change do |request, model| model.last_updated_at = Time.now end # Standard destroy method define_destroy # Build the entity class from the given record. define_build_entities do |records| records.map do |record| parent = lookup_association( record.parent_id, self ) do records.pluck( :parent_id ) end scorpion.fetch UserEntity, { parent: parent } end end end
Constants
- DSL_METHODS
Known DSL methods defined by {ActiveRecordCrud}.
Private Instance Methods
Define a private `build_entities( records )` method that constructs an {Entities::Entity} for each of the given `records`.
If no block is given, creates a simple builder that simply constructs an instance of the {.entity_class} passing `record: record` to the initializer.
See {Service#lookup_association} for details on association caching.
@yield ( records ) @yieldparam [ActiveRecord::Relation] records to be mapped to
entities.
@yieldreturn [Array<Entities::Entity>] the projected entities. @return [void]
# File lib/shamu/services/active_record_crud.rb, line 367 def define_build_entities( &block ) if block_given? define_method :build_entities, &block else define_method :build_entities do |records| records.map do |record| entity = scorpion.fetch( entity_class, record: record ) authorize! :read, entity end end end private :build_entities end
Define an change `method` on the service that takes the id of the resource to modify and a corresponding {Request} parameter.
@yield ( request, record, *args ) @yieldparam [Services::Request] request object. @yieldparam [ActiveRecord::Base] record. @yieldparam [Array] args any additional arguments injected by an overridden {#with_request} method. @return [Result] the result of the request. @return [void]
# File lib/shamu/services/active_record_crud.rb, line 191 def define_change( method, default_scope = model_class, &block ) define_method method do |id, params = nil| klass = request_class( method ) id, params = extract_params( id, params ) with_partial_request params, klass do |request, *args| record = default_scope.find( id.to_model_id || request.id ) entity = build_entity( record ) backfill_attributes = entity.to_attributes( only: request.unassigned_attributes ) request.assign_attributes backfill_attributes next unless request.valid? authorize! method, entity, request request.apply_to( record ) if block_given? result = instance_exec record, request, *args, &block next result if result.is_a?( Services::Result ) next unless request.valid? end next record unless record.save build_entity record end end end
Define a `#create` method on the service that takes a single {Request} parameter.
@yield ( request, record, *args ) @yieldparam [Services::Request] request object. @yieldparam [ActiveRecord::Base] record. @yieldparam [Array] args any additional arguments injected by an overridden {#with_request} method. @return [void]
# File lib/shamu/services/active_record_crud.rb, line 162 def define_create( method = :create, &block ) define_method method do |params = nil| with_request params, request_class( method ) do |request, *args| record = request.apply_to( model_class.new ) if block_given? result = instance_exec record, request, *args, &block next result if result.is_a?( Services::Result ) next unless request.valid? end authorize! method, build_entity( record ), request next record unless record.save build_entity record end end end
Define all basic CRUD methods without any customization.
# File lib/shamu/services/active_record_crud.rb, line 146 def define_crud define_create define_update define_destroy define_finders end
Define a `destroy( id )` method that takes an {Entities::Entity} {Entities::Entity#id} and destroys the resource.
@yield ( request, record, *args ) @yieldparam [Services::Request] request object. @yieldparam [ActiveRecord::Base] record. @yieldparam [Array] args any additional arguments injected by an overridden {#with_request} method. @param [ActiveRecord::Relation] default_scope to use when finding
records.
@return [void]
# File lib/shamu/services/active_record_crud.rb, line 236 def define_destroy( method = :destroy, default_scope = model_class, &block ) define_method method do |params| klass = request_class( method ) params = { id: params } if params.respond_to?( :to_model_id ) with_request params, klass do |request, *args| record = default_scope.find( request.id ) authorize! method, build_entity( record ), request if block_given? instance_exec record, request, *args, &block next unless request.valid? end next record unless record.destroy end end end
Define a `find( id )` method on the service that returns the entity with the given id if found or raises a {Shamu::NotFoundError} if the entity does not exist.
@param [ActiveRecord::Relation] default_scope to use when finding
records.
@yield (id) @yieldreturn (ActiveRecord::Base) the found record. @return [void]
# File lib/shamu/services/active_record_crud.rb, line 279 def define_find( default_scope = model_class.all, &block ) if block_given? define_method :_find_block, &block define_method :find do |id| wrap_not_found do record = _find_block( id ) authorize! :read, build_entity( record ) end end else define_method :find do |id| authorize! :read, find_by_lookup( id ) end end end
Define the standard finder methods {.find}, {.lookup} and {.list}.
@param [ActiveRecord::Relation] default_scope to use when finding
records.
@return [void]
# File lib/shamu/services/active_record_crud.rb, line 261 def define_finders( default_scope = model_class.all, only: nil, except: nil ) methods = Array( only || [ :find, :lookup, :list ] ) methods -= Array( except ) if except methods.each do |method| send :"define_#{ method }", default_scope end end
Define a `list( params = nil )` method that takes a {Entities::ListScope} and returns all the entities selected by that scope.
@param [ActiveRecord::Relation] default_scope to use when finding
records.
@yield (scope) @yieldparam [ListScope] scope to apply. @yieldreturn [ActiveRecord::Relation] records matching the given scope. @return [void]
# File lib/shamu/services/active_record_crud.rb, line 335 def define_list( default_scope = model_class.all, &block ) define_method :list do |params = nil| list_scope = Entities::ListScope.for( entity_class ).coerce( params ) authorize! :list, entity_class, list_scope records = if block_given? instance_exec( list_scope, &block ) else scope_relation( default_scope, list_scope ) end records = authorize_relation( :read, records, list_scope ) entity_list records end end
Define a `lookup( *ids )` method that takes a list of entity ids to find. Calls {#build_entities} to map all found records to entities, or constructs a {Entities::NullEntity} for ids that were not found.
@param [ActiveRecord::Relation] default_scope to use when finding
records.
@yield (uncached_ids) @yieldparam [Array<Object>] ids that need to be fetched from the
underlying resource.
@yieldreturn [ActiveRecord::Relation] records for ids found in the
underlying resource.
@return [void]
# File lib/shamu/services/active_record_crud.rb, line 307 def define_lookup( default_scope = model_class.all, &block ) if block_given? define_method :_lookup_block, &block else define_method :_lookup_block do |ids| default_scope.where( id: ids ) end end define_method :lookup do |*ids| cached_lookup( ids ) do |uncached_ids| records = _lookup_block( uncached_ids ) records = authorize_relation :read, records entity_lookup_list records, uncached_ids, entity_class.null_entity end end end
Defines an update method. See {#define_change} for details.
# File lib/shamu/services/active_record_crud.rb, line 221 def define_update( default_scope = model_class, &block ) define_change :update, default_scope, &block end
# File lib/shamu/services/active_record_crud.rb, line 59 def entity_class self.class.model_class end
# File lib/shamu/services/active_record_crud.rb, line 393 def inferred_namespace parts = ( name || "Resource" ).split( "::" ) parts.pop return "" if parts.empty? parts.join( "::" ) << "::" end
# File lib/shamu/services/active_record_crud.rb, line 388 def inferred_resource_name inferred = name || "Resource" inferred.split( "::" ).last.sub( /Service/, "" ).singularize end
# File lib/shamu/services/active_record_crud.rb, line 55 def model_class self.class.model_class end
# File lib/shamu/services/active_record_crud.rb, line 95 def not_found!( id = :not_set ) raise Shamu::NotFoundError, id: id, resource: entity_class end
Declare the entity and resource classes used by the service.
Creates instance and class level methods `entity_class` and `model_class`.
See {.build_entities} for build_entities block details.
@param [Class] entity_class
the {Entities::Entity} class that will be
returned by finders and mutator methods.
@param [Class] model_class
the {ActiveRecord::Base} model @param [Array<Symbol>] methods the {DSL_METHODS DSL methods} to
include (eg :create, :update, :find, etc.)
@yield ( records ) @yieldparam [ActiveRecord::Relation] records to be mapped to an
entity.
@yieldreturn [Entities::Entity] the entity projection for the given
record.
@return [void]
# File lib/shamu/services/active_record_crud.rb, line 119 def resource( entity_class, model_class, methods: nil, &block ) private define_method( :entity_class ) { entity_class } define_singleton_method( :entity_class ) { entity_class } private define_method( :model_class ) { model_class } define_singleton_method( :model_class ) { model_class } ( Array( methods ) & DSL_METHODS ).each do |method| send :"define_#{ method }" end define_build_entities( &block ) end
# File lib/shamu/services/active_record_crud.rb, line 384 def resource_not_configured raise IncompleteSetupError, "Resource has not been defined. Add `resource #{ inferred_namespace }#{ inferred_resource_name }Entity, #{ inferred_namespace }Models::#{ inferred_resource_name }` to #{ name }." # rubocop:disable Metrics/LineLength end