kangaroo 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +6 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +66 -0
  6. data/LICENSE +22 -0
  7. data/README.md +65 -0
  8. data/Rakefile +36 -0
  9. data/config/kangaroo.yml.sample +12 -0
  10. data/docs/AdditionalServices.md +3 -0
  11. data/docs/Architecture.md +50 -0
  12. data/docs/Installation.md +73 -0
  13. data/docs/Usage.md +161 -0
  14. data/features/connection.feature +5 -0
  15. data/features/env.rb +8 -0
  16. data/features/step_definitions/basic_steps.rb +24 -0
  17. data/features/step_definitions/connection_steps.rb +13 -0
  18. data/features/support/test.yml +11 -0
  19. data/features/utility_services.feature +39 -0
  20. data/kangaroo.gemspec +29 -0
  21. data/lib/kangaroo.rb +6 -0
  22. data/lib/kangaroo/exception.rb +7 -0
  23. data/lib/kangaroo/model/attributes.rb +124 -0
  24. data/lib/kangaroo/model/base.rb +70 -0
  25. data/lib/kangaroo/model/condition_normalizer.rb +63 -0
  26. data/lib/kangaroo/model/default_attributes.rb +22 -0
  27. data/lib/kangaroo/model/field.rb +20 -0
  28. data/lib/kangaroo/model/finder.rb +92 -0
  29. data/lib/kangaroo/model/inspector.rb +55 -0
  30. data/lib/kangaroo/model/open_object_orm.rb +117 -0
  31. data/lib/kangaroo/model/persistence.rb +180 -0
  32. data/lib/kangaroo/model/relation.rb +212 -0
  33. data/lib/kangaroo/model/remote_execute.rb +29 -0
  34. data/lib/kangaroo/railtie.rb +13 -0
  35. data/lib/kangaroo/ruby_adapter/base.rb +28 -0
  36. data/lib/kangaroo/ruby_adapter/class_definition.rb +46 -0
  37. data/lib/kangaroo/ruby_adapter/fields.rb +18 -0
  38. data/lib/kangaroo/util/client.rb +59 -0
  39. data/lib/kangaroo/util/configuration.rb +82 -0
  40. data/lib/kangaroo/util/database.rb +92 -0
  41. data/lib/kangaroo/util/loader.rb +98 -0
  42. data/lib/kangaroo/util/loader/model.rb +21 -0
  43. data/lib/kangaroo/util/loader/namespace.rb +15 -0
  44. data/lib/kangaroo/util/proxy.rb +35 -0
  45. data/lib/kangaroo/util/proxy/common.rb +111 -0
  46. data/lib/kangaroo/util/proxy/db.rb +34 -0
  47. data/lib/kangaroo/util/proxy/object.rb +153 -0
  48. data/lib/kangaroo/util/proxy/report.rb +24 -0
  49. data/lib/kangaroo/util/proxy/superadmin.rb +71 -0
  50. data/lib/kangaroo/util/proxy/wizard.rb +25 -0
  51. data/lib/kangaroo/util/proxy/workflow.rb +14 -0
  52. data/lib/kangaroo/version.rb +3 -0
  53. data/spec/model/attributes_spec.rb +70 -0
  54. data/spec/model/base_spec.rb +19 -0
  55. data/spec/model/default_attributes_spec.rb +37 -0
  56. data/spec/model/finder_spec.rb +104 -0
  57. data/spec/model/inspector_spec.rb +56 -0
  58. data/spec/model/open_object_orm_spec.rb +134 -0
  59. data/spec/model/persistence_spec.rb +53 -0
  60. data/spec/model/relation_spec.rb +122 -0
  61. data/spec/ruby_adapter/class_definition_spec.rb +51 -0
  62. data/spec/server_helper.rb +167 -0
  63. data/spec/spec_helper.rb +14 -0
  64. data/spec/test_env/test.yml +11 -0
  65. data/spec/util/configuration_spec.rb +36 -0
  66. data/spec/util/loader_spec.rb +50 -0
  67. data/spec/util/proxy_spec.rb +61 -0
  68. metadata +260 -0
@@ -0,0 +1,92 @@
1
+ require 'active_support/core_ext/module'
2
+
3
+ module Kangaroo
4
+ module Model
5
+ module Finder
6
+ RELATION_DELEGATES = %w(where limit offset order select context reverse)
7
+ delegate *(RELATION_DELEGATES + [:to => :relation])
8
+
9
+ # Retrieve all records
10
+ #
11
+ # @return [Array] records
12
+ def all
13
+ relation.all
14
+ end
15
+
16
+ # ActiveRecord-ish find method
17
+ #
18
+ # @overload find(id)
19
+ # Find a record by id
20
+ # @param [Number] id
21
+ # @overload find(keyword)
22
+ # Find all, first or last record
23
+ # @param [String, Symbol] keyword :all, :first or :last
24
+ def find id_or_keyword
25
+ case id_or_keyword
26
+ when :all, 'all'
27
+ all
28
+ when :first, 'first'
29
+ first
30
+ when :last, 'last'
31
+ last
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def exists? ids
38
+ where(:id => ids).exists?
39
+ end
40
+
41
+ # Retrieve first record
42
+ #
43
+ # @return record
44
+ def first
45
+ relation.first
46
+ end
47
+
48
+ # Retrieve last record
49
+ #
50
+ # @return record
51
+ def last
52
+ relation.last
53
+ end
54
+
55
+ # Count number of records
56
+ #
57
+ # @return [Number]
58
+ def count
59
+ count_by
60
+ end
61
+
62
+ # @private
63
+ # Search, read and instantiate records at once
64
+ #
65
+ # @param [Array, Hash, String] conditions
66
+ # @param [Hash] search_options
67
+ # @param [Hash] read_options
68
+ # @return [Array]
69
+ def search_and_read conditions, search_options = {}, read_options = {}
70
+ ids = search conditions, search_options.merge(:count => false)
71
+ return [] if ids.blank?
72
+
73
+ read ids, read_options
74
+ end
75
+
76
+ # @private
77
+ # Count objects matching the conditions
78
+ #
79
+ # @param [Array, Hash, String] conditions
80
+ # @param [Hash] search options
81
+ # @return [Number]
82
+ def count_by conditions = [], search_options = {}
83
+ search conditions, search_options.merge(:count => true)
84
+ end
85
+
86
+ # @private
87
+ def relation
88
+ Relation.new self
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,55 @@
1
+ module Kangaroo
2
+ module Model
3
+ module Inspector
4
+ # @private
5
+ def self.included klass
6
+ klass.extend ClassMethods
7
+ end
8
+
9
+ # @private
10
+ def inspect
11
+ inspect_wrap do |str|
12
+ str << [inspect_id, *inspect_attributes].join(', ')
13
+ end
14
+ end
15
+
16
+ private
17
+ def inspect_id
18
+ "id: #{id.inspect}"
19
+ end
20
+
21
+ def inspect_attributes
22
+ attributes.to_a.map do |key_val|
23
+ name, value = key_val
24
+ [name, value.inspect].join ": "
25
+ end
26
+ end
27
+
28
+ def inspect_wrap
29
+ "".tap do |str|
30
+ str << "<#{self.class.name} "
31
+ yield str
32
+ str << ">"
33
+ end
34
+ end
35
+
36
+ # @private
37
+ module ClassMethods
38
+ def inspect
39
+ inspect_wrap do |str|
40
+ str << ['id', *attribute_names].join(', ')
41
+ end
42
+ end
43
+
44
+ private
45
+ def inspect_wrap
46
+ "".tap do |str|
47
+ str << "<#{name} "
48
+ yield str
49
+ str << ">"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,117 @@
1
+ require 'kangaroo/model/condition_normalizer'
2
+ require 'kangaroo/model/field'
3
+
4
+ module Kangaroo
5
+ module Model
6
+ module OpenObjectOrm
7
+ include ConditionNormalizer
8
+
9
+ # Search for records in the OpenERP database
10
+ #
11
+ # @param [Hash, Array, String] conditions
12
+ # @param [Hash] options
13
+ # @option options [Number] limit
14
+ # @option options [Number] offset
15
+ # @option options [String] order
16
+ # @option options [Hash] context
17
+ # @option options [boolean] count
18
+ # @return list of ids
19
+ def search conditions, options = {}
20
+ options = {
21
+ :limit => false,
22
+ :offset => 0
23
+ }.merge(options)
24
+
25
+ remote.search normalize_conditions(conditions),
26
+ options[:offset],
27
+ options[:limit],
28
+ options[:order],
29
+ options[:context],
30
+ options[:count]
31
+ end
32
+
33
+ # Read records and instantiate them
34
+ #
35
+ # @param [Array] ids
36
+ # @param [Hash] options
37
+ # @option options [Array] fields
38
+ # @option options [Hash] context
39
+ # @return [Array] list of Kangaroo::Model::Base instances
40
+ def read ids, options = {}
41
+ fields = options[:fields]
42
+ fields = attribute_names if options[:fields].blank?
43
+ context = options[:context]
44
+
45
+ [].tap do |result|
46
+ remote.read(ids, fields, context).each do |attributes|
47
+ position = ids.index(attributes[:id].to_i)
48
+ result[position] = instantiate attributes, context
49
+ end
50
+ end
51
+ end
52
+
53
+ # Retrieve field informations
54
+ #
55
+ # @param [Hash] options
56
+ # @option options [Array] fields
57
+ # @option options [Hash] context
58
+ # @return [Hash]
59
+ def fields_get options = {}
60
+ options = {
61
+ :fields => attribute_names,
62
+ :context => {}
63
+ }.merge(options)
64
+
65
+ remote.fields_get(options[:fields], options[:context]).map do |key, val|
66
+ Field.new key, val
67
+ end
68
+ end
69
+
70
+ # Create a OpenObject record.
71
+ #
72
+ # @param [Hash] attributes
73
+ # @param [Hash] options
74
+ # @option options [Hash] context
75
+ # @return [Number] id
76
+ def create_record attributes, options = {}
77
+ remote.create attributes, options[:context]
78
+ end
79
+
80
+
81
+ # Fetch default attribute values from OpenERP database
82
+ #
83
+ # @param [Hash] options
84
+ # @option options [Array] fields
85
+ # @option options [Hash] context
86
+ # @return [Hash] default values
87
+ def default_get options = {}
88
+ options = {
89
+ :fields => attribute_names
90
+ }.merge(options)
91
+
92
+ remote.default_get options[:fields], options[:context]
93
+ end
94
+
95
+ # Write values to records
96
+ #
97
+ # @param [Array] ids
98
+ # @param [Hash] attributes
99
+ # @param [Hash] options
100
+ # @option options [Hash] context
101
+ # @return [boolean] true/false
102
+ def write_record ids, attributes, options = {}
103
+ remote.write ids, attributes, options[:context]
104
+ end
105
+
106
+ # Remove records
107
+ #
108
+ # @param [Array] ids
109
+ # @param [Hash] options
110
+ # @option options [Hash] context
111
+ # @return [boolean] true/false
112
+ def unlink ids, options = {}
113
+ remote.unlink ids, options[:context]
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,180 @@
1
+ require 'kangaroo/exception'
2
+ require 'active_support/core_ext/hash'
3
+
4
+ module Kangaroo
5
+ module Model
6
+ module Persistence
7
+ # @private
8
+ def self.included klass
9
+ klass.extend ClassMethods
10
+ klass.define_model_callbacks :destroy, :save, :update, :create, :find
11
+ klass.after_destroy :mark_destroyed
12
+ klass.after_create :mark_persisted
13
+ klass.after_save :reload
14
+
15
+ klass.send :attr_accessor, :context
16
+
17
+ klass.before_initialize do
18
+ @destroyed = false
19
+ @readonly = false
20
+ @new_record = !@id
21
+ end
22
+ end
23
+
24
+ # Check if this record hasnt been persisted yet
25
+ #
26
+ # @return [boolean] true/false
27
+ def new_record?
28
+ @new_record
29
+ end
30
+
31
+ # Check if this record has been persisted yet
32
+ #
33
+ # @return [boolean] true/false
34
+ def persisted?
35
+ !@new_record
36
+ end
37
+
38
+ # Check if this record has been destroyed
39
+ #
40
+ # @return [boolean] true/false
41
+ def destroyed?
42
+ @destroyed
43
+ end
44
+
45
+ # Destroy this record
46
+ #
47
+ # @return self
48
+ def destroy
49
+ return true if destroyed? || new_record?
50
+ _run_destroy_callbacks do
51
+ remote.unlink [id], :context => context
52
+ end
53
+
54
+ self
55
+ end
56
+
57
+ # Save this record
58
+ #
59
+ # @param [Hash] options unused
60
+ # @return [boolean] true/false
61
+ def save options = {}
62
+ _run_save_callbacks do
63
+ create_or_update
64
+ end
65
+ end
66
+
67
+ # Save this record or raise an error
68
+ #
69
+ # @param [Hash] options unused
70
+ # @return [boolean] true
71
+ def save! options = {}
72
+ save options ||
73
+ raise(RecordSavingFailed)
74
+ end
75
+
76
+ # Reload this record, or just a subset of fields
77
+ #
78
+ # @param [Array] fields to reload
79
+ # @return self
80
+ def reload fields = self.class.attribute_names
81
+ @attributes = remote.read([id], fields, context).first.except(:id).stringify_keys
82
+ fields.each do |field|
83
+ @changed_attributes.delete field.to_s
84
+ end
85
+
86
+ self
87
+ end
88
+
89
+ module ClassMethods
90
+ # Initialize a record and immediately save it
91
+ #
92
+ # @param [Hash] attributes
93
+ # @return saved record
94
+ def create attributes = {}
95
+ new(attributes).tap do |new_record|
96
+ new_record.save
97
+ end
98
+ end
99
+
100
+ # Retrieve a record by id
101
+ #
102
+ # @param [Number] id
103
+ # @return record
104
+ # @raise RecordNotFound
105
+ def find id
106
+ Array === id ?
107
+ find_every(ids) :
108
+ find_single(id)
109
+ end
110
+
111
+ protected
112
+ def find_single id
113
+ read([id]).first ||
114
+ raise(RecordNotFound)
115
+ end
116
+
117
+ def find_every ids
118
+ read ids
119
+ end
120
+
121
+ def instantiate attributes, context = {}
122
+ allocate.tap do |instance|
123
+ instance.instance_exec(attributes.stringify_keys, context) do |attributes, context|
124
+ @attributes = attributes.except 'id'
125
+ @id = attributes['id']
126
+ @context = context
127
+ raise InstantiatedRecordNeedsIDError if @id.nil?
128
+
129
+ @new_record = false
130
+ @destroyed = false
131
+ @readonly = false
132
+
133
+ _run_initialize_callbacks
134
+ _run_find_callbacks
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ protected
141
+ def attributes_for_update
142
+ @attributes
143
+ end
144
+
145
+ def attributes_for_create
146
+ @attributes
147
+ end
148
+
149
+ def create_or_update
150
+ new_record? ? create : update
151
+ end
152
+
153
+ private
154
+ def create
155
+ _run_create_callbacks do
156
+ if id = self.class.create_record(attributes_for_create, :context => context)
157
+ @id = id
158
+ else
159
+ false
160
+ end
161
+ end
162
+ end
163
+
164
+ def update
165
+ _run_update_callbacks do
166
+ self.class.write_record [id], attributes_for_update, :context => context
167
+ end
168
+ end
169
+
170
+ def mark_persisted
171
+ @new_record = false
172
+ end
173
+
174
+ def mark_destroyed
175
+ @destroyed = true
176
+ freeze
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,212 @@
1
+ module Kangaroo
2
+ module Model
3
+ class Relation
4
+ # @private
5
+ ARRAY_DELEGATES = %w( all? any? as_json at b64encode blank? choice class clone collect collect! combination compact compact! concat
6
+ cycle decode64 delete delete_at delete_if detect drop drop_while dup duplicable? each each_cons each_index
7
+ each_slice each_with_index each_with_object empty? encode64 encode_json entries enum_cons enum_for enum_slice
8
+ enum_with_index eql? equal? exclude? extract_options! fetch fifth fill find_all find_index flatten
9
+ flatten! forty_two fourth freeze frozen? grep group_by html_safe? in_groups in_groups_of include? index
10
+ index_by indexes indices inject insert inspect instance_eval instance_exec instance_of? is_a? join kind_of?
11
+ last many? map map! max max_by member? min min_by minmax minmax_by nitems none? one? pack paginate
12
+ partition permutation pop presence present? pretty_inspect pretty_print pretty_print_cycle pretty_print_inspect
13
+ pretty_print_instance_variables product push rassoc reduce reject reject! replace respond_to? returning reverse
14
+ reverse! reverse_each rindex sample second shelljoin shift shuffle shuffle! slice slice! sort sort!
15
+ sort_by split sum take take_while tap third to to_ary to_default_s to_enum to_formatted_s to_json to_matcher
16
+ to_param to_query to_s to_sentence to_set to_xml to_xml_rpc to_yaml to_yaml_properties to_yaml_style transpose
17
+ type uniq uniq! uniq_by uniq_by! unshift values_at yaml_initialize zip |).freeze
18
+
19
+ # @private
20
+ attr_accessor :target, :where_clauses, :offset_clause, :limit_clause, :select_clause, :order_clause, :context_clause
21
+
22
+ alias_method :_clone, :clone
23
+ alias_method :_tap, :tap
24
+
25
+ delegate *(ARRAY_DELEGATES + [:to => :to_a])
26
+
27
+ # @private
28
+ def initialize target
29
+ @target = target
30
+ @where_clauses = []
31
+ @select_clause = []
32
+ @order_clause = []
33
+ @context_clause = {}
34
+ end
35
+
36
+ # Submit the query
37
+ def to_a
38
+ @target.search_and_read @where_clauses, search_parameters, read_parameters
39
+ end
40
+ alias_method :all, :to_a
41
+
42
+ # Return only the first record
43
+ def first
44
+ limit(1).to_a.first
45
+ end
46
+
47
+ # Return only the last record
48
+ def last
49
+ reverse.first
50
+ end
51
+
52
+ # Check if a record with fulfilling this conditions exist
53
+ def exists?
54
+ @target.search(@where_clauses, search_parameters.merge(:limit => 1)).present?
55
+ end
56
+
57
+ # Find record(s) by id(s)
58
+ def find ids
59
+ records = where(:id => ids)
60
+
61
+ Array === ids ?
62
+ records.all :
63
+ records.first
64
+ end
65
+
66
+ # Count how many records fulfill this conditions
67
+ def count
68
+ @target.count_by @where_clauses, search_parameters
69
+ end
70
+ alias_method :size, :count
71
+ alias_method :length, :count
72
+
73
+ # Reverse all order clauses
74
+ def reverse
75
+ if @order_clause.blank?
76
+ order('id', true)
77
+ else
78
+ _clone._tap do |c|
79
+ c.order_clause = c.order_clause.map do |order|
80
+ reverse_order order
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # Clone this relation and add the condition to the where clause
87
+ #
88
+ # @param [Hash, Array, String] condition
89
+ # @return [Relation] cloned relation
90
+ def where condition
91
+ _clone._tap do |c|
92
+ c.where_clauses += [condition]
93
+ end
94
+ end
95
+
96
+ # Clone this relation and (re)set the limit clause
97
+ #
98
+ # @param [Number] limit maximum records to retriebe
99
+ # @return [Relation] cloned relation
100
+ def limit limit
101
+ _clone._tap do |c|
102
+ c.limit_clause = limit
103
+ end
104
+ end
105
+
106
+ # Clone this relation and (re)set the offset clause
107
+ #
108
+ # @param [Number] offset number of records to skip
109
+ # @return [Relation] cloned relation
110
+ def offset offset
111
+ _clone._tap do |c|
112
+ c.offset_clause = offset
113
+ end
114
+ end
115
+
116
+ # Clone this relation and add select expressions to select clause
117
+ #
118
+ # @param [Array, String, Symbol] selects fields to retrieve
119
+ # @return [Relation] cloned relation
120
+ def select *selects
121
+ selects = selects.flatten.map &:to_s
122
+ _clone._tap do |c|
123
+ c.select_clause += selects
124
+ end
125
+ end
126
+
127
+ # Clone this relation and add a context
128
+ #
129
+ # @param [Hash] context
130
+ # @return [Relation] cloned relation
131
+ def context context = {}
132
+ _clone._tap do |c|
133
+ c.context_clause = c.context_clause.merge(context)
134
+ end
135
+ end
136
+
137
+ # Clone this relation and add an order instruction
138
+ #
139
+ # @param [String, Symbol] column field to order by
140
+ # @param [boolean] desc true to order descending
141
+ def order column, desc = false
142
+ column = column.to_s + " desc" if desc
143
+ _clone._tap do |c|
144
+ c.order_clause += [column.to_s]
145
+ end
146
+ end
147
+
148
+ # Shortcut to set limit and offset and execute queries immediately.
149
+ # If {#limit} or {#offset} are already set, [] is equal to #to_a[]
150
+ #
151
+ # @overload [](n)
152
+ # @param [Number] n
153
+ # @return [Kangaroo::Model::Base] n-th record
154
+ # @overload [](n, m)
155
+ # @param [Number] n
156
+ # @param [Number] m
157
+ # @return [Array<Kangaroo::Model::Base>] m records, offset n
158
+ # @overload [](n..m)
159
+ # @param [Range] range n..m
160
+ # @return [Array<Kangaroo::Model::Base>] records m to n
161
+ def [] start_or_range, stop = nil
162
+ if @limit_clause || @offset_clause
163
+ return to_a[start_or_range, stop] if stop
164
+ return to_a[start_or_range]
165
+ end
166
+
167
+ c = _clone
168
+
169
+ c.offset_clause = if start_or_range.is_a?(Range)
170
+ range_end = start_or_range.end
171
+ range_end += 1 unless start_or_range.exclude_end?
172
+
173
+ c.limit_clause = range_end - start_or_range.begin
174
+ start_or_range.begin
175
+ elsif stop
176
+ c.limit_clause = stop
177
+ start_or_range
178
+ else
179
+ c.limit_clause = 1
180
+ start_or_range
181
+ end
182
+
183
+ (stop.nil? && Integer===start_or_range) ? c.to_a.first : c.to_a
184
+ end
185
+
186
+ protected
187
+ def search_parameters
188
+ {
189
+ :offset => @offset_clause,
190
+ :limit => @limit_clause,
191
+ :order => @order_clause.join(", "),
192
+ :context => @context_clause
193
+ }
194
+ end
195
+
196
+ def read_parameters
197
+ {
198
+ :fields => @select_clause,
199
+ :context => @context_clause
200
+ }
201
+ end
202
+
203
+ def reverse_order order
204
+ if match = order.match(/(.*)\sdesc/i)
205
+ match[1]
206
+ else
207
+ order + " desc"
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end