lib/appcelerator/service.rb in appcelerator-2.0.1.1 vs lib/appcelerator/service.rb in appcelerator-2.0.2

- old
+ new

@@ -1,266 +1,264 @@ -module Appcelerator - class Service - include Singleton - - def Service.Service(messagetype,handler,responsetype) - self.instance.preregister(messagetype,handler,responsetype) - end +module Appcelerator + class Service + include Singleton - def Service.service_scaffold(model) - servicename = model_name(model) - self.instance.model_class = eval(model.to_s.camelize) - %w(create retrieve update delete list assembly search).each do |operation| - messagetype = "app."+ servicename +"."+ operation - request = messagetype + ".request" - response = messagetype + ".response" - self.instance.preregister(request,operation,response) - APP_SERVICES << {'name'=>name, 'model'=>servicename} - end + # registration point of standard services + def Service.Service(messagetype,handler,responsetype = nil) + self.instance.register(messagetype,handler,responsetype) end + + # registration point for service handler filters for authentication, etc + def Service.before_filter(filter) + self.instance.before_filters << filter + end - # - # The following methods are usable by services that call the "service_scaffold" method - # to provide CRUD (and more) operations on a single model object. - # - def assembly(request,message) - if self.respond_to?(:before_assembly) - request,message = send(:before_assembly,request,message) - end - data = Array.new - @model_class.columns.each do |column| - data << {'name'=>column.name,'type'=>column.type, 'limit'=>column.limit, - 'nullable'=>column.null, 'primary'=>column.primary, - 'default'=>column.default} - end - response = {'success'=>true,'columns'=>data} - if self.respond_to?(:after_assembly) - response = send(:after_assembly,response) - end - response - end - - def create(request,message) - if self.respond_to?(:before_create) - request,message = send(:before_create,request,message) - end - o = @model_class.new(message) - o.save - response = {'success'=>true,'id'=>o.id} - if self.respond_to?(:after_create) - response = send(:after_create,response) - end - response - end - - def retrieve(request,message) - if self.respond_to?(:before_retrieve) - request,message = send(:before_retrieve,request,message) - end - o = @model_class.find(message['id']) - response = {'success'=>true} - o.attributes.each do |key,val| - response[key]=val - end - response - if self.respond_to?(:after_retrieve) - response = send(:after_retrieve,response) - end - response - end - - def update(request,message) - if self.respond_to?(:before_update) - request,message = send(:before_update,request,message) - end - o = @model_class.find(message['id']) - return {'success'=>false,'message'=>'record does not exist'} unless o - message.each do |key,value| - if o.attributes.has_key?(key) - o[key]=true if value=='true' - o[key]=false if value=='false' - o[key]=value if o[key]!=true && o[key]!=false - end - end - o.save! - response = {'success'=>true,'id'=>o.id} - if self.respond_to?(:after_update) - response = send(:after_update,response) - end - response - end - - def delete(request,message) - if self.respond_to?(:before_delete) - request,message = send(:before_delete,request,message) - end - @model_class.delete(message['id']) - response = {'success'=>true,'id'=>message['id']} - if self.respond_to?(:after_delete) - response = send(:after_delete,response) - end - response - end - - def search(request,message) - if self.respond_to?(:before_search) - request,message = send(:before_search,request,message) - end - query = '%%' + message['query'] + '%%' - conditions_str = Array.new - @model_class.content_columns.each do |col| - name = col.name + ' like ?' - conditions_str << name if col.type == :string - end - - conditions = Array.new - conditions << conditions_str.join(' OR ') - conditions_str.length.times {|e| conditions<<query} - - results = @model_class.find(:all,:conditions=>conditions) - response = {'success'=>true, 'rows'=>results, 'total'=>@model_class.count, 'count'=>results.length} - - if self.respond_to?(:after_search) - response = send(:after_search,response) - end - response - end - - def list(request,message) - pk = message['id'] - limit = message['limit'] || 100 - if pk - objs = [@model_class.find_by_id(pk)] - else - objs = @model_class.find(:all,:limit=>limit) - end - response = {'success'=>true,'rows'=>objs, 'total'=>@model_class.count} - if self.respond_to?(:after_list) - response = send(:after_list,response) - end - response - end - + # registration point for post-service-handling filters + def Service.after_filter(filter) + self.instance.after_filters << filter + end - def self.load_services - APP_SERVICES.clear - # TODO: remove this and solve the multiple singleton issue - Appcelerator::ServiceBroker.clear_listeners - Dir[RAILS_ROOT + '/app/services/*_service.rb'].each do |file| - - name = Inflector.camelize(File.basename(file).chomp('_service.rb')) + 'Service' - - if Dependencies.load? - if defined?(name) - klass = eval(name) - # clear the service class's existing listeners, if possible - klass.instance.clear_listeners - end - load file - else - require file[0..-4] - end - - begin - klass = eval(name) - klass.instance.register_listeners - rescue - puts 'Unable to find class "'+ name +'" in file "'+ file +'"' - puts $! - end + # for use by message handlers + attr_accessor :request,:params,:session,:message_type,:response,:response_type + + # call this method from a before filter to prevent the remain handlers from running + def respond_with(response,response_type=nil) + @response = response + if response_type + @response_type = response_type end - puts 'done loading services' - puts Appcelerator::ServiceBroker.diagnostics end + + + # internal + attr_accessor :before_filters, :after_filters + def initialize @listeners = [] - @preregistrations = [] + @before_filters = [] + @after_filters = [] end - - def preregister(*args) - @preregistrations << args - end - def register_listeners - # call after class-load when all methods are defined - @listeners = @preregistrations.map do |reg| - register(*reg) - end - @preregistrations.clear - @listeners.length - end - def clear_listeners num_cleared = @listeners.length @listeners.each do |listener| ServiceBroker.unregister_listener(listener.msgtype, listener) end @listeners.clear num_cleared end - def register(msgtype,methodname,responsetype = nil) - service = self.class # add to the ServiceProc binding - handler = self.method(methodname) - args = handler.arity - proc = ServiceProc.new do |req,msgtype,obj| - - # - # try and be smart about sending in the request to the - # service based on the arguments he supports - # - case args - when 2 - resp = handler.call(req,obj) - when 3 - resp = handler.call(req,obj,req['session']) - when 4 - resp = handler.call(req,obj,req['session'],req['session']['username']) - end - - if responsetype - Dispatcher.instance.outgoing(req,responsetype,resp||{}) - end + def do_before_filters + @before_filters.each do |filter| + method(filter).call() + break if @response end - ServiceBroker.register_listener(msgtype,proc) + end + + def do_after_filters + @after_filters.each do |filter| + method(filter).call() + end + end + + # handle a message + def handle(msg, method_name, response_type) + @request = msg.request + @session = msg.session + @message_type = msg.message_type + @params = msg.params + @response = nil # this is the handler's response, not the request/response pair + @response_type = response_type + + do_before_filters() + + # any response added by the before_filters prevents the handler from running + if not @response + @response = method(method_name).call() + + do_after_filters() + end + + if @response_type + msg.response_type = @response_type + msg.response = @response || {} + end + msg + end + + def register(message_type, method_name, response_type = nil) + # adding to the ServiceProc binding, for preventing dups # this need may have been removed by properly reloading + service_name = self.class.name + + proc = ServiceProc.new do |msg| + # is this const_get stuff important? + instance = Object.const_get(service_name).instance + instance.handle(msg,method_name,response_type) + end + + ServiceBroker.register_listener(message_type, proc) proc end - - def send_message(req,type,message) - ServiceBroker.send(req,type,message) - end - def secure_password_matches?(request,message,password_field,password) - - password_obj = message[password_field] - - if password_obj - return Digest::MD5.hexdigest(password + request['authtoken']) == password_obj['auth'] - end - - false - end end + # TODO: spin this into another file when we can resolve dependecy issues + class CrudService < Service + + # registration point for crud services + def Service.service_scaffold(model) + servicename = model_name(model) + self.instance.model_class = eval(model.to_s.camelize) + %w(create retrieve update delete list assembly search).each do |operation| + messagetype = "app."+ servicename +"."+ operation + request = messagetype + ".request" + response = messagetype + ".response" + self.instance.register(request,operation,response) + APP_SERVICES << {'name'=>name, 'model'=>servicename} + end + end + + attr_accessor :model_class + + def assembly(request,message) + if self.respond_to?(:before_assembly) + request,message = send(:before_assembly,request,message) + end + data = Array.new + @model_class.columns.each do |column| + data << {'name'=>column.name,'type'=>column.type, 'limit'=>column.limit, + 'nullable'=>column.null, 'primary'=>column.primary, + 'default'=>column.default} + end + response = {'success'=>true,'columns'=>data} + if self.respond_to?(:after_assembly) + response = send(:after_assembly,response) + end + response + end + + def create(request,message) + if self.respond_to?(:before_create) + request,message = send(:before_create,request,message) + end + o = @model_class.new(message) + o.save + response = {'success'=>true,'id'=>o.id} + if self.respond_to?(:after_create) + response = send(:after_create,response) + end + response + end + + def retrieve(request,message) + if self.respond_to?(:before_retrieve) + request,message = send(:before_retrieve,request,message) + end + o = @model_class.find(message['id']) + response = {'success'=>true} + o.attributes.each do |key,val| + response[key]=val + end + response + if self.respond_to?(:after_retrieve) + response = send(:after_retrieve,response) + end + response + end + + def update(request,message) + if self.respond_to?(:before_update) + request,message = send(:before_update,request,message) + end + o = @model_class.find(message['id']) + return {'success'=>false,'message'=>'record does not exist'} unless o + message.each do |key,value| + if o.attributes.has_key?(key) + o[key]=true if value=='true' + o[key]=false if value=='false' + o[key]=value if o[key]!=true && o[key]!=false + end + end + o.save! + response = {'success'=>true,'id'=>o.id} + if self.respond_to?(:after_update) + response = send(:after_update,response) + end + response + end + + def delete(request,message) + if self.respond_to?(:before_delete) + request,message = send(:before_delete,request,message) + end + @model_class.delete(message['id']) + response = {'success'=>true,'id'=>message['id']} + if self.respond_to?(:after_delete) + response = send(:after_delete,response) + end + response + end + + def search(request,message) + if self.respond_to?(:before_search) + request,message = send(:before_search,request,message) + end + query = '%%' + message['query'] + '%%' + conditions_str = Array.new + @model_class.content_columns.each do |col| + name = col.name + ' like ?' + conditions_str << name if col.type == :string + end + + conditions = Array.new + conditions << conditions_str.join(' OR ') + conditions_str.length.times {|e| conditions<<query} + + results = @model_class.find(:all,:conditions=>conditions) + response = {'success'=>true, 'rows'=>results, 'total'=>@model_class.count, 'count'=>results.length} + + if self.respond_to?(:after_search) + response = send(:after_search,response) + end + response + end + + def list(request,message) + pk = message['id'] + limit = message['limit'] || 100 + if pk + objs = [@model_class.find_by_id(pk)] + else + objs = @model_class.find(:all,:limit=>limit) + end + response = {'success'=>true,'rows'=>objs, 'total'=>@model_class.count} + if self.respond_to?(:after_list) + response = send(:after_list,response) + end + response + end + end + # # Class to allow inspection and equality checking (for adding to sets) # TODO: remove the to_s in eql? and hash when the singleton issue is resolved # class ServiceProc < Proc def self.vars - [:msgtype, :service, :methodname, :responsetype] + [:message_type, :service_name, :method_name, :response_type] end def method_missing name eval(name.to_s, self.binding) end def to_s - "#{msgtype} -> #{service}.#{methodname} -> #{responsetype}" + service = Object.const_get(service_name).instance + "#{message_type} -> #{service.before_filters} #{service_name}.#{method_name} #{service.after_filters} -> #{response_type}" end def eql?(other) self.class.vars.all? do |var| self.send(var).to_s == other.send(var).to_s @@ -286,7 +284,7 @@ return false end end return true end - end -end \ No newline at end of file + end +end