rpc-mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,68 @@
1
+ require 'rpc_mapper/cacheable/store'
2
+ require 'rpc_mapper/cacheable/entry'
3
+ require 'digest/md5'
4
+
5
+ module RPCMapper::Cacheable
6
+
7
+ module ClassMethods
8
+
9
+ # TRP: Default to a 5 minute cache
10
+ DEFAULT_LONGEVITY = 5*60
11
+
12
+ protected
13
+
14
+ def fetch_records_with_caching(options={})
15
+ key = Digest::MD5.hexdigest(self.to_s + options.to_a.sort { |a,b| a.to_s.first <=> b.to_s.first }.inspect)
16
+ cache_hit = self.cacheable.read(key)
17
+
18
+ if cache_hit
19
+ self.adapter.log(options, "CACHE #{self.service_namespace}__#{self.service}")
20
+ cache_hit.each { |fv| fv.fresh = false.freeze }
21
+ cache_hit
22
+ else
23
+ fresh_value = fetch_records_without_caching(options)
24
+
25
+ # TRP: Only store in cache if record count is below the cache_record_count_threshold (if it is set)
26
+ if !self.cache_record_count_threshold || fresh_value.size <= self.cache_record_count_threshold
27
+ self.cacheable.write(key, fresh_value, (self.cache_expires) ? fresh_value[0].send(self.cache_expires) : Time.now + DEFAULT_LONGEVITY) unless fresh_value.empty?
28
+ fresh_value.each { |fv| fv.fresh = true.freeze }
29
+ end
30
+
31
+ fresh_value
32
+ end
33
+ end
34
+
35
+ def enable_caching(options)
36
+ self.cacheable = RPCMapper::Cacheable::Store.new(DEFAULT_LONGEVITY)
37
+ self.cache_expires = options[:expires]
38
+ self.cache_record_count_threshold = options[:record_count_threshold]
39
+ end
40
+
41
+ end
42
+
43
+ module InstanceMethods
44
+
45
+ def fresh?
46
+ @fresh
47
+ end
48
+
49
+ end
50
+
51
+ def self.included(receiver)
52
+ receiver.class_inheritable_accessor :cache_expires, :cache_record_count_threshold
53
+ receiver.send :attr_accessor, :fresh
54
+
55
+ receiver.extend ClassMethods
56
+ receiver.send :include, InstanceMethods
57
+
58
+ # TRP: Remap calls for RPC through the caching mechanism
59
+ receiver.class_eval do
60
+ class << self
61
+ alias_method :fetch_records_without_caching, :fetch_records
62
+ alias_method :fetch_records, :fetch_records_with_caching
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,41 @@
1
+ module RPCMapper
2
+
3
+ module ConfigOptions
4
+ module ClassMethods
5
+
6
+ def config_options(options={})
7
+ options.each do |option, default_value|
8
+ class_eval do
9
+ self.configure_options << option
10
+ class_inheritable_accessor option
11
+ self.send "#{option}=", default_value
12
+ end
13
+ # TRP: We run a direct string eval on the class because we cannot get a closure on the class << self scope
14
+ # We need that in order to use our option variable to define a class method of the same name.
15
+ self.class_eval <<-EOS
16
+ def self.#{option} # def self.my_awesome_option
17
+ val = read_inheritable_attribute(:#{option}) # val = read_inheritable_attribute(:my_awesome_option)
18
+ val.is_a?(Proc) ? write_inheritable_attribute(:#{option}, val.call) : val # val.is_a?(Proc) ? write_inheritable_attribute(:my_awesome_option, val.call) : val
19
+ end # end
20
+ EOS
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ module InstanceMethods
27
+
28
+ end
29
+
30
+ def self.included(receiver)
31
+ receiver.class_eval do
32
+ class_inheritable_accessor :configure_options
33
+ self.configure_options = []
34
+ end
35
+
36
+ receiver.extend ClassMethods
37
+ receiver.send :include, InstanceMethods
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,14 @@
1
+ # TRP: Copied from Rails 3RC1 on 8/17/2010 (not available in Rails2)
2
+ module Kernel
3
+ # Returns the object's singleton class.
4
+ def singleton_class
5
+ class << self
6
+ self
7
+ end
8
+ end unless respond_to?(:singleton_class) # exists in 1.9.2
9
+
10
+ # class_eval on an object acts like singleton_class.class_eval.
11
+ def class_eval(*args, &block)
12
+ singleton_class.class_eval(*args, &block)
13
+ end
14
+ end
@@ -0,0 +1,54 @@
1
+ module RPCMapper
2
+
3
+ module Logger
4
+ module ClassMethods
5
+
6
+ def log(params, name)
7
+ if block_given?
8
+ result = nil
9
+ ms = Benchmark.measure { result = yield }.real
10
+ log_info(params, name, ms*1000)
11
+ result
12
+ else
13
+ log_info(params, name, 0)
14
+ []
15
+ end
16
+ rescue Exception => err
17
+ log_info(params, name, 0)
18
+ RPCMapper.logger.error("#{err.message} \n\n#{err.backtrace.join('\n')}")
19
+ []
20
+ end
21
+
22
+ private
23
+
24
+ def log_info(params, name, ms)
25
+ if RPCMapper.logger && RPCMapper.logger.debug?
26
+ name = '%s (%.1fms)' % [name || 'RPC', ms]
27
+ RPCMapper.logger.debug(format_log_entry(name, params.inspect))
28
+ end
29
+ end
30
+
31
+ def format_log_entry(message, dump=nil)
32
+ message_color, dump_color = "4;33;1", "0;1"
33
+
34
+ log_entry = " \e[#{message_color}m#{message}\e[0m "
35
+ log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
36
+ log_entry
37
+ end
38
+
39
+ end
40
+
41
+ module InstanceMethods
42
+
43
+ def log(*args, &block)
44
+ self.class.send(:log, *args, &block)
45
+ end
46
+ end
47
+
48
+ def self.included(receiver)
49
+ receiver.extend ClassMethods
50
+ receiver.send :include, InstanceMethods
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,138 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module RPCMapper
5
+
6
+ module Mutable
7
+
8
+ module ClassMethods
9
+
10
+ protected
11
+
12
+ def save_mutable_configuration(options={})
13
+ self.mutable_service = options[:service] || self.service
14
+ self.mutable_post_body_wrapper = options[:post_body_wrapper]
15
+ end
16
+
17
+ def create_writer(attribute)
18
+ define_method("#{attribute}=") do |value|
19
+ self[attribute] = value
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+
26
+ module InstanceMethods
27
+
28
+ def []=(attribute, value)
29
+ set_attribute(attribute, value)
30
+ end
31
+
32
+ def attributes=(attributes)
33
+ set_attributes(attributes)
34
+ end
35
+
36
+ def save
37
+ new_record? ? post_http(build_params_from_attributes) : put_http(build_params_from_attributes)
38
+ end
39
+
40
+ def update_attributes(attributes)
41
+ self.attributes = attributes
42
+ save
43
+ end
44
+
45
+ def destroy
46
+ delete_http
47
+ end
48
+ alias :delete :destroy
49
+
50
+
51
+ protected
52
+
53
+
54
+ def post_http(params)
55
+ http_call(:post, params)
56
+ end
57
+
58
+ def put_http(params)
59
+ http_call(:put, params)
60
+ end
61
+
62
+ def delete_http
63
+ http_call(:delete, self.mutable_default_parameters)
64
+ end
65
+
66
+ def http_call(method, params={})
67
+ request_klass = case method
68
+ when :post then Net::HTTP::Post
69
+ when :put then Net::HTTP::Put
70
+ when :delete then Net::HTTP::Delete
71
+ end
72
+
73
+ req_url = self.build_url(method)
74
+
75
+ request = if method == :delete
76
+ request = request_klass.new([req_url.path, req_url.query].join('?'))
77
+ else
78
+ request = request_klass.new(req_url.path)
79
+ request.set_form_data(params) unless method == :delete
80
+ request
81
+ end
82
+
83
+ self.class.send(:adapter).log(params, "#{method.to_s.upcase} #{req_url}") do
84
+ @response = Net::HTTP.new(req_url.host, req_url.port).start { |http| http.request(request) }
85
+ end
86
+ parse_response_code(@response)
87
+ end
88
+
89
+ def parse_response_code(response)
90
+ case response
91
+ when Net::HTTPSuccess, Net::HTTPRedirection
92
+ true
93
+ else
94
+ false
95
+ end
96
+ end
97
+
98
+ def build_params_from_attributes
99
+ if self.mutable_post_body_wrapper
100
+ params = self.mutable_default_parameters
101
+ @attributes.each do |attribute, value|
102
+ params.merge!({"#{self.mutable_post_body_wrapper}[#{attribute}]" => value})
103
+ end
104
+ # TRP: Append ivar mutable_params to params if set
105
+ params.merge!(self.mutable_params) if self.mutable_params.is_a?(Hash)
106
+
107
+ params
108
+ else
109
+ @attributes
110
+ end
111
+ end
112
+
113
+ def build_url(method)
114
+ url = [self.mutable_host.gsub(%r{/$}, ''), self.mutable_service]
115
+ url << self.id unless self.new_record?
116
+ uri = URI.parse "#{url.join('/')}.json"
117
+
118
+ if method == :delete
119
+ uri.query = self.mutable_default_parameters.collect { |key, value| "#{key}=#{value}" }.join('&')
120
+ end
121
+
122
+ uri
123
+ end
124
+
125
+ end
126
+
127
+ def self.included(receiver)
128
+ receiver.class_inheritable_accessor :mutable_service, :mutable_post_body_wrapper
129
+ receiver.send :attr_accessor, :mutable_params
130
+ receiver.send(:attr_accessor, :response)
131
+
132
+ receiver.extend ClassMethods
133
+ receiver.send :include, InstanceMethods
134
+ end
135
+
136
+ end
137
+
138
+ end
@@ -0,0 +1,52 @@
1
+ module RPCMapper::FinderMethods
2
+
3
+ def find(ids_or_mode, options={})
4
+ case ids_or_mode
5
+ when Fixnum, String
6
+ self.where(:id => ids_or_mode.to_i).first(options)
7
+ when Array
8
+ self.where(:id => ids_or_mode).all(options)
9
+ when :all
10
+ self.all(options)
11
+ when :first
12
+ self.first(options)
13
+ else
14
+ raise ArgumentError, "Unknown arguments for method find"
15
+ end
16
+ end
17
+
18
+ def all(options={})
19
+ self.apply_finder_options(options).fetch_records
20
+ end
21
+
22
+ def first(options={})
23
+ self.apply_finder_options(options).limit(1).fetch_records.first
24
+ end
25
+
26
+ def search(options={})
27
+ relation = self
28
+ options.each do |search, *args|
29
+ relation = relation.send(search, *args) if @klass.send(:condition_details, search)
30
+ end
31
+ relation
32
+ end
33
+
34
+ def apply_finder_options(options)
35
+ relation = clone
36
+ return relation unless options
37
+
38
+ [:joins, :limit, :offset, :order, :select, :group, :having, :from].each do |finder|
39
+ relation = relation.send(finder, options[finder]) if options[finder]
40
+ end
41
+
42
+ relation = relation.where(options[:conditions]) if options.has_key?(:conditions)
43
+ relation = relation.where(options[:where]) if options.has_key?(:where)
44
+
45
+ relation = relation.search(options[:search]) if options.has_key?(:search)
46
+
47
+ relation = relation.sql(options[:sql]) if options.has_key?(:sql)
48
+
49
+ relation
50
+ end
51
+
52
+ end
@@ -0,0 +1,50 @@
1
+ module RPCMapper::QueryMethods
2
+ # TRP: Define each of the variables the query options will be stored in.
3
+ attr_accessor :select_values, :group_values, :order_values, :joins_values, :where_values,
4
+ :having_values, :limit_value, :offset_value, :from_value, :raw_sql_value
5
+
6
+ def select(*args)
7
+ if block_given?
8
+ to_a.select {|*block_args| yield(*block_args) }
9
+ else
10
+ clone.tap { |r| r.select_values += args if args && !args.empty? }
11
+ end
12
+ end
13
+
14
+ def group(*args)
15
+ clone.tap { |r| r.group_values += args if args && !args.empty? }
16
+ end
17
+
18
+ def order(*args)
19
+ clone.tap { |r| r.order_values += args if args && !args.empty? }
20
+ end
21
+
22
+ def joins(*args)
23
+ clone.tap { |r| r.joins_values += args if args && !args.empty? }
24
+ end
25
+
26
+ def where(*args)
27
+ clone.tap { |r| r.where_values += args if args && !args.empty? }
28
+ end
29
+
30
+ def having(*args)
31
+ clone.tap { |r| r.having_values += args if args && !args.empty? }
32
+ end
33
+
34
+ def limit(value = true)
35
+ clone.tap { |r| r.limit_value = value }
36
+ end
37
+
38
+ def offset(value = true)
39
+ clone.tap { |r| r.offset_value = value }
40
+ end
41
+
42
+ def from(table)
43
+ clone.tap { |r| r.from_value = table }
44
+ end
45
+
46
+ def sql(raw_sql)
47
+ clone.tap { |r| r.raw_sql_value = raw_sql }
48
+ end
49
+
50
+ end
@@ -0,0 +1,125 @@
1
+ require 'rpc_mapper/relation/query_methods'
2
+ require 'rpc_mapper/relation/finder_methods'
3
+
4
+ # TRP: The concepts behind this class are heavily influenced by ActiveRecord::Relation v.3.0.0RC1
5
+ # => http://github.com/rails/rails
6
+ # Used to achieve the chainability of scopes -- methods are delegated back and forth from BM::Base and BM::Relation
7
+ class RPCMapper::Relation
8
+ SINGLE_VALUE_METHODS = [:limit, :offset, :from]
9
+ MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
10
+
11
+ FINDER_OPTIONS = SINGLE_VALUE_METHODS + MULTI_VALUE_METHODS + [:conditions, :search, :sql]
12
+
13
+ include RPCMapper::QueryMethods
14
+ include RPCMapper::FinderMethods
15
+
16
+ def initialize(klass)
17
+ @klass = klass
18
+
19
+ SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
20
+ MULTI_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_values", [])}
21
+ end
22
+
23
+ def merge(r)
24
+ merged_relation = clone
25
+ return merged_relation unless r
26
+
27
+ SINGLE_VALUE_METHODS.each do |option|
28
+ new_value = r.send("#{option}_value")
29
+ merged_relation = merged_relation.send(option, new_value) if new_value
30
+ end
31
+
32
+ MULTI_VALUE_METHODS.each do |option|
33
+ merged_relation = merged_relation.send(option, *r.send("#{option}_values"))
34
+ end
35
+
36
+ merged_relation
37
+ end
38
+
39
+ def to_hash
40
+ # TRP: If present pass :sql option alone as it trumps all other options
41
+ if self.raw_sql_value
42
+ { :sql => raw_sql_value }
43
+ else
44
+ hash = SINGLE_VALUE_METHODS.inject({}) do |h, option|
45
+ value = self.send("#{option}_value")
46
+ value ? h.merge(option => value) : h
47
+ end
48
+
49
+ hash.merge!((MULTI_VALUE_METHODS - [:select]).inject({}) do |h, option|
50
+ value = self.send("#{option}_values")
51
+ value && !value.empty? ? h.merge(option => value.uniq) : h
52
+ end)
53
+
54
+ # TRP: If one of the select options contains a * than select options are ignored
55
+ if select_values && !select_values.empty? && !select_values.any? { |val| val.to_s.match(/\*$/) }
56
+ hash.merge!(:select => select_values.uniq)
57
+ end
58
+
59
+ hash
60
+ end
61
+ end
62
+
63
+ def to_a
64
+ @records ||= fetch_records
65
+ end
66
+
67
+ def inspect
68
+ to_a.inspect
69
+ end
70
+
71
+ def scoping
72
+ @klass.scoped_methods << self
73
+ begin
74
+ yield
75
+ ensure
76
+ @klass.scoped_methods.pop
77
+ end
78
+ end
79
+
80
+ def respond_to?(method, include_private=false)
81
+ super ||
82
+ Array.method_defined?(method) ||
83
+ @klass.scopes[method] ||
84
+ dynamic_finder_method(method) ||
85
+ @klass.respond_to?(method, false)
86
+ end
87
+
88
+ def dynamic_finder_method(method)
89
+ defined_attributes = (@klass.defined_attributes || []).join('|')
90
+ if method.to_s =~ /^(find_by|find_all_by)_(#{defined_attributes})/
91
+ [$1, $2]
92
+ else
93
+ nil
94
+ end
95
+ end
96
+
97
+ protected
98
+
99
+ def method_missing(method, *args, &block)
100
+ if Array.method_defined?(method)
101
+ to_a.send(method, *args, &block)
102
+ elsif result = dynamic_finder_method(method)
103
+ method, attribute = result
104
+ options = { :conditions => { attribute => args[0] } }
105
+ options.merge!(args[1]) if args[1] && args[1].is_a?(Hash)
106
+ case method.to_sym
107
+ when :find_by
108
+ self.first(options)
109
+ when :find_all_by
110
+ self.all(options)
111
+ end
112
+ elsif @klass.scopes[method]
113
+ merge(@klass.send(method, *args, &block))
114
+ elsif @klass.respond_to?(method, false)
115
+ scoping { @klass.send(method, *args, &block) }
116
+ else
117
+ super
118
+ end
119
+ end
120
+
121
+ def fetch_records
122
+ @klass.send(:fetch_records, to_hash)
123
+ end
124
+
125
+ end
@@ -0,0 +1,101 @@
1
+
2
+ # TRP: Implementation of this feature was heavily influenced by binarylogic's Searchlogic-2.4.19
3
+ # => http://github.com/binarylogic/searchlogic
4
+ #
5
+ # It is designed to mimick much of the API of searchlogic so that it can be used alongside AR objects utilizing Searchlogic
6
+ # without developer confusion. There are certain features that are skipped because of the nature of RPCMapper.
7
+
8
+ module RPCMapper::Scopes
9
+
10
+ module Conditions
11
+
12
+ COMPARISON_CONDITIONS = {
13
+ :equals => [:is, :eq],
14
+ :does_not_equal => [:not_equal_to, :is_not, :not, :ne],
15
+ :less_than => [:lt, :before],
16
+ :less_than_or_equal_to => [:lte],
17
+ :greater_than => [:gt, :after],
18
+ :greater_than_or_equal_to => [:gte],
19
+ }
20
+
21
+ WILDCARD_CONDITIONS = {
22
+ :like => [:contains, :includes],
23
+ :not_like => [:does_not_include],
24
+ :begins_with => [:bw],
25
+ :not_begin_with => [:does_not_begin_with],
26
+ :ends_with => [:ew],
27
+ :not_end_with => [:does_not_end_with]
28
+ }
29
+
30
+ CONDITIONS = {}
31
+
32
+ # Add any / all variations to every comparison and wildcard condition
33
+ COMPARISON_CONDITIONS.merge(WILDCARD_CONDITIONS).each do |condition, aliases|
34
+ CONDITIONS[condition] = aliases
35
+ CONDITIONS["#{condition}_any".to_sym] = aliases.collect { |a| "#{a}_any".to_sym }
36
+ CONDITIONS["#{condition}_all".to_sym] = aliases.collect { |a| "#{a}_all".to_sym }
37
+ end
38
+
39
+ CONDITIONS[:equals_any] = CONDITIONS[:equals_any] + [:in]
40
+ CONDITIONS[:does_not_equal_all] = CONDITIONS[:does_not_equal_all] + [:not_in]
41
+
42
+ PRIMARY_CONDITIONS = CONDITIONS.keys
43
+ ALIAS_CONDITIONS = CONDITIONS.values.flatten
44
+
45
+ def respond_to?(name, include_private=false)
46
+ super ||
47
+ condition_details(name)
48
+ end
49
+
50
+ private
51
+
52
+ def method_missing(name, *args, &block)
53
+ if details = condition_details(name)
54
+ create_condition(details[:attribute], details[:condition], args)
55
+ send(name, *args)
56
+ else
57
+ super
58
+ end
59
+ end
60
+
61
+ def condition_details(method_name)
62
+ return nil unless defined_attributes
63
+
64
+ attribute_name_matcher = defined_attributes.join("|")
65
+ conditions_matcher = (PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")
66
+
67
+ if method_name.to_s =~ /^(#{attribute_name_matcher})_(#{conditions_matcher})$/
68
+ {:attribute => $1, :condition => $2}
69
+ end
70
+ end
71
+
72
+ def create_condition(attribute, condition, args)
73
+ if PRIMARY_CONDITIONS.include?(condition.to_sym)
74
+ create_primary_condition(attribute, condition)
75
+ elsif ALIAS_CONDITIONS.include?(condition.to_sym)
76
+ create_alias_condition(attribute, condition, args)
77
+ end
78
+ end
79
+
80
+ def create_primary_condition(attribute, condition)
81
+ scope_name = "#{attribute}_#{condition}"
82
+ scope scope_name, lambda { |a| where(scope_name => a) }
83
+ end
84
+
85
+ def create_alias_condition(attribute, condition, args)
86
+ primary_condition = primary_condition(condition)
87
+ alias_name = "#{attribute}_#{condition}"
88
+ primary_name = "#{attribute}_#{primary_condition}"
89
+ send(primary_name, *args) # go back to method_missing and make sure we create the method
90
+ singleton_class.class_eval { alias_method alias_name, primary_name }
91
+ end
92
+
93
+ # Returns the primary condition for the given alias. Ex:
94
+ #
95
+ # primary_condition(:gt) => :greater_than
96
+ def primary_condition(alias_condition)
97
+ CONDITIONS.find { |k, v| k == alias_condition.to_sym || v.include?(alias_condition.to_sym) }.first
98
+ end
99
+ end
100
+
101
+ end
@@ -0,0 +1,45 @@
1
+ require 'rpc_mapper/scopes/conditions'
2
+
3
+ module RPCMapper::Scopes
4
+
5
+ module ClassMethods
6
+
7
+ def scopes
8
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
9
+ end
10
+
11
+ def scoped
12
+ current_scope ? relation.merge(current_scope) : relation.clone
13
+ end
14
+
15
+ def scope(name, scope_options={})
16
+ name = name.to_sym
17
+
18
+ # TRP: Define the scope and merge onto the relation
19
+ scopes[name] = lambda do |*args|
20
+ options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
21
+ if options.is_a?(Hash)
22
+ scoped.apply_finder_options(options)
23
+ else
24
+ scoped.merge(options)
25
+ end
26
+ end
27
+
28
+ # TRP: Bind the above block to a method for easy access
29
+ singleton_class.send(:define_method, name, &scopes[name])
30
+ end
31
+
32
+ end
33
+
34
+ module InstanceMethods
35
+
36
+ end
37
+
38
+ def self.included(receiver)
39
+ receiver.extend ClassMethods
40
+ receiver.send :include, InstanceMethods
41
+
42
+ receiver.extend Conditions
43
+ end
44
+
45
+ end