lockstep_rails 0.3.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +216 -0
  4. data/Rakefile +31 -0
  5. data/app/assets/config/lockstep_rails_manifest.js +0 -0
  6. data/app/concepts/lockstep/active_records/association.rb +78 -0
  7. data/app/concepts/lockstep/api_record.rb +1118 -0
  8. data/app/concepts/lockstep/api_records/scopes.rb +20 -0
  9. data/app/concepts/lockstep/client.rb +162 -0
  10. data/app/concepts/lockstep/error.rb +50 -0
  11. data/app/concepts/lockstep/exceptions.rb +4 -0
  12. data/app/concepts/lockstep/query.rb +409 -0
  13. data/app/concepts/lockstep/query_methods.rb +75 -0
  14. data/app/concepts/lockstep/relation_array.rb +100 -0
  15. data/app/helpers/types.rb +3 -0
  16. data/app/models/lockstep/account.rb +15 -0
  17. data/app/models/lockstep/connection.rb +19 -0
  18. data/app/models/lockstep/contact.rb +9 -0
  19. data/app/models/lockstep/customer_summary.rb +6 -0
  20. data/app/models/lockstep/invoice.rb +7 -0
  21. data/app/models/lockstep/invoice_summary.rb +6 -0
  22. data/app/models/lockstep/invoices/address.rb +24 -0
  23. data/app/models/lockstep/note.rb +7 -0
  24. data/app/models/lockstep/payment.rb +7 -0
  25. data/app/models/lockstep/payment_summary.rb +6 -0
  26. data/app/models/lockstep/user.rb +10 -0
  27. data/app/platform_api/model_template.rb.erb +27 -0
  28. data/app/platform_api/schema/action_result.rb +15 -0
  29. data/app/platform_api/schema/activity.rb +141 -0
  30. data/app/platform_api/schema/activity_fetch_result.rb +26 -0
  31. data/app/platform_api/schema/activity_stream_item.rb +58 -0
  32. data/app/platform_api/schema/activity_x_ref.rb +37 -0
  33. data/app/platform_api/schema/aging.rb +24 -0
  34. data/app/platform_api/schema/api_key.rb +71 -0
  35. data/app/platform_api/schema/api_key_fetch_result.rb +26 -0
  36. data/app/platform_api/schema/app_enrollment.rb +88 -0
  37. data/app/platform_api/schema/app_enrollment_custom_field.rb +67 -0
  38. data/app/platform_api/schema/app_enrollment_custom_field_fetch_result.rb +26 -0
  39. data/app/platform_api/schema/app_enrollment_fetch_result.rb +26 -0
  40. data/app/platform_api/schema/application.rb +89 -0
  41. data/app/platform_api/schema/application_fetch_result.rb +26 -0
  42. data/app/platform_api/schema/ar_aging_header_info.rb +47 -0
  43. data/app/platform_api/schema/ar_header_info.rb +118 -0
  44. data/app/platform_api/schema/assembly.rb +68 -0
  45. data/app/platform_api/schema/at_risk_invoice_summary.rb +90 -0
  46. data/app/platform_api/schema/at_risk_invoice_summary_fetch_result.rb +26 -0
  47. data/app/platform_api/schema/attachment.rb +92 -0
  48. data/app/platform_api/schema/attachment_fetch_result.rb +26 -0
  49. data/app/platform_api/schema/attachment_header_info.rb +41 -0
  50. data/app/platform_api/schema/batch_sync.rb +18 -0
  51. data/app/platform_api/schema/bulk_currency_conversion.rb +19 -0
  52. data/app/platform_api/schema/cashflow_report.rb +35 -0
  53. data/app/platform_api/schema/code_definition.rb +58 -0
  54. data/app/platform_api/schema/code_definition_fetch_result.rb +26 -0
  55. data/app/platform_api/schema/company.rb +243 -0
  56. data/app/platform_api/schema/company_fetch_result.rb +26 -0
  57. data/app/platform_api/schema/company_sync.rb +142 -0
  58. data/app/platform_api/schema/connector_info.rb +27 -0
  59. data/app/platform_api/schema/constructor_info.rb +128 -0
  60. data/app/platform_api/schema/contact.rb +148 -0
  61. data/app/platform_api/schema/contact_fetch_result.rb +26 -0
  62. data/app/platform_api/schema/contact_sync.rb +109 -0
  63. data/app/platform_api/schema/country.rb +62 -0
  64. data/app/platform_api/schema/country_fetch_result.rb +26 -0
  65. data/app/platform_api/schema/credit_memo_applied.rb +104 -0
  66. data/app/platform_api/schema/credit_memo_applied_fetch_result.rb +26 -0
  67. data/app/platform_api/schema/credit_memo_applied_sync.rb +67 -0
  68. data/app/platform_api/schema/credit_memo_invoice.rb +77 -0
  69. data/app/platform_api/schema/currency.rb +31 -0
  70. data/app/platform_api/schema/currency_fetch_result.rb +26 -0
  71. data/app/platform_api/schema/currency_rate.rb +28 -0
  72. data/app/platform_api/schema/custom_attribute_data.rb +18 -0
  73. data/app/platform_api/schema/custom_attribute_named_argument.rb +24 -0
  74. data/app/platform_api/schema/custom_attribute_typed_argument.rb +16 -0
  75. data/app/platform_api/schema/custom_field_definition.rb +76 -0
  76. data/app/platform_api/schema/custom_field_definition_fetch_result.rb +26 -0
  77. data/app/platform_api/schema/custom_field_sync.rb +71 -0
  78. data/app/platform_api/schema/custom_field_value.rb +75 -0
  79. data/app/platform_api/schema/custom_field_value_fetch_result.rb +26 -0
  80. data/app/platform_api/schema/customer_details.rb +98 -0
  81. data/app/platform_api/schema/customer_details_payment.rb +60 -0
  82. data/app/platform_api/schema/customer_summary.rb +88 -0
  83. data/app/platform_api/schema/customer_summary_fetch_result.rb +26 -0
  84. data/app/platform_api/schema/daily_sales_outstanding_report.rb +25 -0
  85. data/app/platform_api/schema/developer_account_submit.rb +23 -0
  86. data/app/platform_api/schema/email.rb +160 -0
  87. data/app/platform_api/schema/email_fetch_result.rb +26 -0
  88. data/app/platform_api/schema/erp.rb +23 -0
  89. data/app/platform_api/schema/erp_fetch_result.rb +26 -0
  90. data/app/platform_api/schema/erp_info.rb +18 -0
  91. data/app/platform_api/schema/erp_info_data.rb +23 -0
  92. data/app/platform_api/schema/event_info.rb +59 -0
  93. data/app/platform_api/schema/exception.rb +41 -0
  94. data/app/platform_api/schema/field_info.rb +105 -0
  95. data/app/platform_api/schema/financial_account.rb +90 -0
  96. data/app/platform_api/schema/financial_account_balance_history.rb +90 -0
  97. data/app/platform_api/schema/financial_account_balance_history_fetch_result.rb +26 -0
  98. data/app/platform_api/schema/financial_account_fetch_result.rb +26 -0
  99. data/app/platform_api/schema/financial_year_setting.rb +74 -0
  100. data/app/platform_api/schema/financial_year_setting_fetch_result.rb +26 -0
  101. data/app/platform_api/schema/invite.rb +25 -0
  102. data/app/platform_api/schema/invite_data.rb +18 -0
  103. data/app/platform_api/schema/invite_submit.rb +15 -0
  104. data/app/platform_api/schema/invoice.rb +226 -0
  105. data/app/platform_api/schema/invoice_address.rb +97 -0
  106. data/app/platform_api/schema/invoice_fetch_result.rb +29 -0
  107. data/app/platform_api/schema/invoice_history.rb +188 -0
  108. data/app/platform_api/schema/invoice_history_fetch_result.rb +26 -0
  109. data/app/platform_api/schema/invoice_line.rb +142 -0
  110. data/app/platform_api/schema/invoice_line_sync.rb +208 -0
  111. data/app/platform_api/schema/invoice_payment_detail.rb +65 -0
  112. data/app/platform_api/schema/invoice_summary.rb +85 -0
  113. data/app/platform_api/schema/invoice_summary_fetch_result.rb +26 -0
  114. data/app/platform_api/schema/invoice_sync.rb +280 -0
  115. data/app/platform_api/schema/lead.rb +32 -0
  116. data/app/platform_api/schema/member_info.rb +36 -0
  117. data/app/platform_api/schema/method_base.rb +128 -0
  118. data/app/platform_api/schema/method_info.rb +137 -0
  119. data/app/platform_api/schema/module.rb +44 -0
  120. data/app/platform_api/schema/module_handle.rb +15 -0
  121. data/app/platform_api/schema/note.rb +72 -0
  122. data/app/platform_api/schema/note_fetch_result.rb +26 -0
  123. data/app/platform_api/schema/parameter_info.rb +64 -0
  124. data/app/platform_api/schema/payment.rb +147 -0
  125. data/app/platform_api/schema/payment_applied.rb +95 -0
  126. data/app/platform_api/schema/payment_applied_fetch_result.rb +26 -0
  127. data/app/platform_api/schema/payment_applied_sync.rb +74 -0
  128. data/app/platform_api/schema/payment_detail.rb +110 -0
  129. data/app/platform_api/schema/payment_detail_fetch_result.rb +26 -0
  130. data/app/platform_api/schema/payment_detail_header.rb +43 -0
  131. data/app/platform_api/schema/payment_fetch_result.rb +26 -0
  132. data/app/platform_api/schema/payment_summary.rb +79 -0
  133. data/app/platform_api/schema/payment_summary_fetch_result.rb +26 -0
  134. data/app/platform_api/schema/payment_sync.rb +112 -0
  135. data/app/platform_api/schema/problem_details.rb +31 -0
  136. data/app/platform_api/schema/property_info.rb +60 -0
  137. data/app/platform_api/schema/provisioning.rb +28 -0
  138. data/app/platform_api/schema/provisioning_finalize_request.rb +28 -0
  139. data/app/platform_api/schema/provisioning_response.rb +42 -0
  140. data/app/platform_api/schema/risk_rate.rb +57 -0
  141. data/app/platform_api/schema/runtime_field_handle.rb +13 -0
  142. data/app/platform_api/schema/runtime_method_handle.rb +13 -0
  143. data/app/platform_api/schema/runtime_type_handle.rb +13 -0
  144. data/app/platform_api/schema/state.rb +22 -0
  145. data/app/platform_api/schema/state_fetch_result.rb +26 -0
  146. data/app/platform_api/schema/status.rb +72 -0
  147. data/app/platform_api/schema/struct_layout_attribute.rb +16 -0
  148. data/app/platform_api/schema/sync_entity_result.rb +34 -0
  149. data/app/platform_api/schema/sync_request.rb +71 -0
  150. data/app/platform_api/schema/sync_request_fetch_result.rb +26 -0
  151. data/app/platform_api/schema/sync_submit.rb +15 -0
  152. data/app/platform_api/schema/test_argument_exception.rb +41 -0
  153. data/app/platform_api/schema/test_timeout_exception.rb +41 -0
  154. data/app/platform_api/schema/transfer_owner.rb +16 -0
  155. data/app/platform_api/schema/transfer_owner_submit.rb +15 -0
  156. data/app/platform_api/schema/type.rb +278 -0
  157. data/app/platform_api/schema/type_info.rb +287 -0
  158. data/app/platform_api/schema/uri.rb +15 -0
  159. data/app/platform_api/schema/user_account.rb +152 -0
  160. data/app/platform_api/schema/user_account_fetch_result.rb +26 -0
  161. data/app/platform_api/schema/user_role.rb +50 -0
  162. data/app/platform_api/schema/user_role_fetch_result.rb +26 -0
  163. data/app/platform_api/schema/webhook.rb +96 -0
  164. data/app/platform_api/schema/webhook_fetch_result.rb +26 -0
  165. data/app/platform_api/schema/webhook_history_table_storage.rb +64 -0
  166. data/app/platform_api/schema/webhook_history_table_storage_fetch_result.rb +26 -0
  167. data/app/platform_api/swagger.json +22056 -0
  168. data/config/routes.rb +2 -0
  169. data/lib/lockstep_rails/engine.rb +4 -0
  170. data/lib/lockstep_rails/version.rb +3 -0
  171. data/lib/lockstep_rails.rb +5 -0
  172. data/lib/tasks/lockstep_rails_tasks.rake +4 -0
  173. data/lib/tasks/update_api_schema.rake +159 -0
  174. metadata +230 -0
@@ -0,0 +1,20 @@
1
+ module Lockstep::ApiRecords::Scopes
2
+ extend ActiveSupport::Concern
3
+
4
+ class_methods do
5
+ def scopes
6
+ @scopes ||= {}
7
+ end
8
+
9
+ def default_scope(&block)
10
+ scopes[:default_scope] = block if block_given?
11
+ scopes[:default_scope]
12
+ end
13
+
14
+ def scope(name, block)
15
+ scopes[name] = block
16
+ define_singleton_method name, &block
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,162 @@
1
+ require 'net/http'
2
+ require 'openssl'
3
+ require 'uri'
4
+ require 'socket'
5
+
6
+ module Lockstep
7
+ class Client
8
+
9
+
10
+ def self.set_bearer_token(token)
11
+ RequestStore.store[:lockstep_bearer_token] = token
12
+ RequestStore.store[:lockstep_api_key] = nil
13
+
14
+ true
15
+ end
16
+
17
+ def self.bearer_token
18
+ RequestStore.store[:lockstep_bearer_token]
19
+ end
20
+
21
+ def self.set_api_key(key)
22
+ RequestStore.store[:lockstep_api_key] = key
23
+ RequestStore.store[:lockstep_bearer_token] = nil
24
+
25
+ true
26
+ end
27
+
28
+ def self.api_key
29
+ RequestStore.store[:lockstep_api_key]
30
+ end
31
+
32
+
33
+ ##
34
+ # Construct a new Lockstep API client targeting the specified server.
35
+ #
36
+ # @param env [string] Either "sbx", "prd", or the URI of the server, ending in a slash (/)
37
+ def initialize(env)
38
+ @version = "2022.4.32.0"
39
+ @env = case env
40
+ when "sbx"
41
+ "https://api.sbx.lockstep.io/"
42
+ when "prd"
43
+ "https://api.lockstep.io/"
44
+ else
45
+ env
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Configure this API client to use API key authentication
51
+ #
52
+ # @param api_key [string] The [Lockstep Platform API key](https://developer.lockstep.io/docs/api-keys) to use for authentication
53
+ def with_api_key(api_key)
54
+ self.class.set_api_key(api_key)
55
+
56
+ self
57
+ end
58
+
59
+ ##
60
+ # Configure this API client to use JWT Bearer Token authentication
61
+ #
62
+ # @param bearer_token [string] The [JWT Bearer Token](https://developer.lockstep.io/docs/jwt-bearer-tokens) to use for authentication
63
+ def with_bearer_token(token)
64
+ self.class.set_bearer_token(token)
65
+
66
+ self
67
+ end
68
+
69
+ def bearer_token
70
+ self.class.bearer_token
71
+ end
72
+
73
+ def api_key
74
+ self.class.api_key
75
+ end
76
+
77
+ ##
78
+ # Configure this API to use an application name
79
+ #
80
+ # @param app_name [string] The name of the application
81
+ def with_app_name(app_name)
82
+ @app_name = app_name
83
+ end
84
+
85
+ ##
86
+ # Send a request to the API and return the results
87
+ #
88
+ # Sends a request to the
89
+ def request(method, path, body, params)
90
+
91
+ url = URI(@env + path)
92
+ if !params.nil?
93
+ url.query = URI.encode_www_form(params)
94
+ end
95
+
96
+ http = Net::HTTP.new(url.host, url.port)
97
+ http.use_ssl = true
98
+
99
+ request = case method
100
+ when :get
101
+ Net::HTTP::Get.new(url)
102
+ when :post
103
+ Net::HTTP::Post.new(url)
104
+ when :patch
105
+ Net::HTTP::Patch.new(url)
106
+ when :put
107
+ Net::HTTP::Put.new(url)
108
+ when :delete
109
+ Net::HTTP::Delete.new(url)
110
+ end
111
+
112
+ # Set headers and body for request
113
+ request["Accept"] = 'application/json'
114
+ request["Content-Type"] = 'application/*+json'
115
+ request["SdkType"] = 'Ruby'
116
+ request["SdkVersion"] = '2022.4.32.0'
117
+ request["MachineName"] = Socket.gethostname
118
+ body = body.to_json unless body.is_a?(String)
119
+ request.body = body
120
+
121
+ # If there is an application name
122
+ if @app_name != nil
123
+ request["ApplicationName"] = @app_name
124
+ end
125
+
126
+ # Which authentication are we using?
127
+ if api_key != nil
128
+ request["Api-Key"] = api_key
129
+ end
130
+ if bearer_token != nil
131
+ request["Authorization"] = 'Bearer ' + bearer_token
132
+ end
133
+
134
+ # Send the request
135
+ http.request(request)
136
+ end
137
+
138
+ def get(path, params: {})
139
+ request(:get, path, nil, params)
140
+ end
141
+
142
+ def post(path, body: {}, params: {})
143
+ request(:post, path, body, params)
144
+ end
145
+
146
+ def patch(path, body: {}, params: {})
147
+ request(:patch, path, body, params)
148
+ end
149
+
150
+ def put(path, body: {}, params: {})
151
+ request(:put, path, body, params)
152
+ end
153
+
154
+ def delete(path)
155
+ request(:delete, path, {}, {})
156
+ end
157
+
158
+ def with_logger(&block)
159
+ block.call
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,50 @@
1
+ class Lockstep::Error
2
+ # ParseError actually represents both HTTP & parse.com error codes. If the
3
+ # HTTP response is 400, one can inspect the first element of the error
4
+ # converted to_array for the HTTP error code and the 2nd element for the
5
+ # parse error response.
6
+ attr_accessor :msg, :code, :error
7
+
8
+ # @param [String] an error code, e.g. "400"
9
+ # @param [Object] an optional error mesg/object.
10
+ def initialize(code, msg="")
11
+ @msg = msg
12
+ @code = code
13
+ case code.to_s
14
+ when "111"
15
+ @error = "Invalid type."
16
+ when "135"
17
+ @error = "Unknown device type."
18
+ when "202"
19
+ @error = "Username already taken."
20
+ when "203"
21
+ @error = "Email already taken."
22
+ when "400"
23
+ @error = "Bad Request: The request cannot be fulfilled due to bad syntax."
24
+ when "401"
25
+ @error = "Unauthorized: Check your App ID & Master Key."
26
+ when "403"
27
+ @error = "Forbidden: You do not have permission to access or modify this."
28
+ when "408"
29
+ @error = "Request Timeout: The request was not completed within the time the server was prepared to wait."
30
+ when "415"
31
+ @error = "Unsupported Media Type"
32
+ when "500"
33
+ @error = "Internal Server Error"
34
+ when "502"
35
+ @error = "Bad Gateway"
36
+ when "503"
37
+ @error = "Service Unavailable"
38
+ when "508"
39
+ @error = "Loop Detected"
40
+ else
41
+ @error = "Unknown Error"
42
+ raise "Parse error #{code}: #{@error} #{@msg}"
43
+ end
44
+ end
45
+
46
+ def to_array
47
+ [ @code, @msg ]
48
+ end
49
+
50
+ end
@@ -0,0 +1,4 @@
1
+ class Lockstep::Exceptions
2
+ class Lockstep::Exceptions::RecordNotFound < ArgumentError
3
+ end
4
+ end
@@ -0,0 +1,409 @@
1
+ class Lockstep::Query
2
+ def initialize(klass)
3
+ @klass = klass
4
+ end
5
+
6
+ def model
7
+ @klass
8
+ end
9
+
10
+ # Clone is used as return object as it messes with the OR query in situations like these
11
+ # unscoped = Lockstep::Connection.unscoped
12
+ # unscoped.where(a: 1).or(unscoped.where(b: 1))
13
+ # both the where conditions in the above scenario is modifying the same object causing stack-overflow
14
+ def clone
15
+ c = Lockstep::Query.new(@klass)
16
+ c.criteria.deep_merge!(self.criteria.deep_dup)
17
+ c
18
+ end
19
+
20
+ def with_clone(&block)
21
+ c = clone
22
+ c.instance_exec(&block)
23
+ c
24
+ end
25
+
26
+ def criteria
27
+ @criteria ||= { :conditions => [] }
28
+ end
29
+
30
+ def or(query)
31
+ with_clone do
32
+ criteria[:conditions] << { type: "or", query: query }
33
+ end
34
+ end
35
+
36
+ def none
37
+ with_clone do
38
+ criteria[:none] = true
39
+ end
40
+ end
41
+
42
+ def where(args)
43
+ if args.is_a?(Hash)
44
+ args.each do |k, v|
45
+ return none if v.is_a?(Array) and v.blank?
46
+ end
47
+ end
48
+
49
+ with_clone do
50
+ criteria[:conditions] << convert_arg(args)
51
+ end
52
+ end
53
+
54
+ def convert_arg(arg)
55
+ return arg.to_pointer if arg.is_a?(Lockstep::ApiRecord)
56
+ return Lockstep::ApiRecord.to_date_object(arg) if arg.is_a?(Time) || arg.is_a?(Date)
57
+ if arg.is_a?(Hash)
58
+ arg.keys.each do |key|
59
+ @klass.valid_attribute?(key, raise_exception: true)
60
+ end
61
+ return arg.update(arg) { |key, inner_arg| convert_arg(inner_arg) }
62
+ end
63
+
64
+ arg
65
+ end
66
+
67
+ def limit(limit)
68
+ with_clone do
69
+ # If > 1000, set chunking, because large queries over 1000 need it with Parse
70
+ # criteria[:chunk] = 1000 if limit > 1000
71
+ criteria[:limit] = limit
72
+ end
73
+ end
74
+
75
+ def page(page_number)
76
+ with_clone do
77
+ criteria[:page_number] = page_number
78
+ end
79
+ end
80
+
81
+ def include_object(*objects)
82
+ with_clone do
83
+ criteria[:include] ||= []
84
+ criteria[:include] += objects
85
+ criteria[:include].uniq!
86
+ end
87
+ end
88
+
89
+ # attr: {customer_name: :desc}
90
+ def order(*attr)
91
+ with_clone do
92
+ criteria[:order] ||= []
93
+ attr.each do |item|
94
+ if item.is_a?(Hash)
95
+ item.each do |k, v|
96
+ criteria[:order] << "#{k.to_s.camelize.upcase} #{v}"
97
+ end
98
+ elsif item.is_a?(String)
99
+ criteria[:order] << item
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ # attr: {customer_name: :desc}
106
+ def reorder(attr)
107
+ with_clone do
108
+ criteria[:order] = []
109
+ order(attr)
110
+ end
111
+ end
112
+
113
+ # Divides the query into multiple chunks if you're running into RestClient::BadRequest errors.
114
+ def chunk(count = 100)
115
+ with_clone do
116
+ criteria[:chunk] = count
117
+ end
118
+ end
119
+
120
+ # def related_to(obj, key)
121
+ # query = { "$relatedTo" => { "object" => obj.to_pointer, "key" => key } }
122
+ # criteria[:conditions].merge!(query)
123
+ # clone
124
+ # end
125
+
126
+ def build_filter
127
+ filter = ""
128
+ criteria[:conditions].each do |condition|
129
+ if condition.is_a?(Hash)
130
+ if condition[:type] == "or"
131
+ filter = build_filter_condition(filter, "OR", condition[:query].build_filter)
132
+ else
133
+ condition.each do |key, value|
134
+ filter = build_filter_condition(filter, "AND", build_filter_query(key, value))
135
+ end
136
+ end
137
+ elsif condition.is_a?(String)
138
+ filter = build_filter_condition(filter, "AND", condition)
139
+ end
140
+ end
141
+ filter
142
+ end
143
+
144
+ def build_filter_condition(filter, merge_predicate, condition)
145
+ if filter.present?
146
+ filter = "(#{filter}) #{merge_predicate} (#{condition})"
147
+ else
148
+ filter += condition
149
+ end
150
+ filter
151
+ end
152
+
153
+ PREDICATES = {
154
+ _not_eq: "NE",
155
+ _eq: "EQ",
156
+ _gteq: "GE",
157
+ _gt: "GT",
158
+ _lteq: "LE",
159
+ _lt: "LT",
160
+ _in: "IN",
161
+ _contains: "CONTAINS",
162
+ _starts_with: "STARTSWITH",
163
+ _ends_with: "ENDSWITH",
164
+ _is: "IS",
165
+ }.with_indifferent_access
166
+
167
+ def build_filter_query(key, value)
168
+ key = key.to_s unless key.is_a?(String)
169
+ predicate = "eq" # default
170
+ PREDICATES.each do |k, p|
171
+ if key.ends_with?(k)
172
+ key = key.gsub(k, "")
173
+ predicate = p
174
+ break
175
+ end
176
+ end
177
+
178
+ # Build value
179
+ if value.is_a?(Array)
180
+ value = "(#{value.map { |v| v.is_a?(String) ? "'#{v}'" : v }.join(",")})"
181
+ predicate = "in" if predicate == "eq"
182
+ elsif value.is_a?(String) and !["NULL", "NOT NULL"].include?(value)
183
+ value = "'#{value}'"
184
+ elsif value.nil?
185
+ predicate = "IS" if predicate == "eq"
186
+ value = "NULL"
187
+ end
188
+
189
+ "#{key.camelize(:lower)} #{predicate} #{value}"
190
+ end
191
+
192
+ def build_params
193
+ params = {}
194
+ params.merge!({ :filter => build_filter }) if criteria[:conditions]
195
+ # Lockstep Platform API does not support a page size of 1
196
+ params.merge!({ :pageSize => (criteria[:limit] == 1 ? 2 : criteria[:limit]) }) if criteria[:limit]
197
+ params.merge!({ :pageNumber => criteria[:page_number] }) if criteria[:page_number]
198
+ params.merge!({ :include => criteria[:include].join(",") }) if criteria[:include]
199
+ params.merge!({ :order => criteria[:order].join(",") }) if criteria[:order]
200
+ params.merge!({ :pageSize => 2 }) if criteria[:count]
201
+ params.reject! { |k, v| v.blank? }
202
+ params
203
+ end
204
+
205
+ def execute
206
+ return [] if criteria[:none]
207
+ # This code automatically adds all related objects
208
+ #
209
+ # if @klass.has_many_relations
210
+ # relations = @klass.has_many_relations.select {|k, val| val[:included]}.keys.map { |relation| relation.to_s }
211
+ # include_object(*relations)
212
+ # end
213
+
214
+ params = build_params
215
+
216
+ return chunk_results(params) if criteria[:chunk]
217
+
218
+ get_results(params)
219
+ end
220
+
221
+ def get_results(params = {})
222
+ resp = @klass.resource.get(@klass.query_path, :params => params)
223
+
224
+ return [] if %w(404).include?(resp.code.to_s)
225
+ # TODO handle non 200 response code. Throwing an exception for now
226
+ raise StandardError.new("#{resp.code} error while fetching: #{resp.body}") unless %w(201 200).include?(resp.code.to_s)
227
+
228
+ if criteria[:count]
229
+ results = JSON.parse(resp.body)["totalCount"]
230
+ return results.to_i
231
+ else
232
+ results = JSON.parse(resp.body)["records"]
233
+ results = results[0..(criteria[:limit] - 1)] if criteria[:limit]
234
+ get_relation_objects results.map { |r|
235
+ # Convert camelcase to snake-case
236
+ r = r.transform_keys { |key| key.underscore }
237
+ @klass.model_name.to_s.constantize.new(r, false)
238
+ }
239
+ end
240
+ end
241
+
242
+ def get_relation_objects(objects)
243
+ return objects if criteria[:include].blank?
244
+
245
+ included_objects = criteria[:include].map { |item| item.to_s.downcase }
246
+
247
+ if @klass.has_many_relations
248
+ objects.each do |item|
249
+ @klass.has_many_relations.each do |relation, relation_config|
250
+ next unless included_objects.include?(relation.to_s.downcase)
251
+
252
+ if !item.attributes.has_key?(relation.to_s)
253
+ item.attributes[relation.to_s] = Lockstep::RelationArray.new(@klass, [], relation, relation_config[:class_name])
254
+ elsif !item.attributes[relation].is_a?(Lockstep::RelationArray)
255
+ item.attributes[relation.to_s] = Lockstep::RelationArray.new(@klass, item.attributes[relation], relation, relation_config[:class_name])
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ objects.each do |item|
262
+ item.attributes.each do |key, value|
263
+ if value.is_a?(Array)
264
+ relation = @klass.has_many_relations[key]
265
+ next if relation.blank? or !included_objects.include?(key.to_s.downcase)
266
+
267
+ value.each do |relation_hash|
268
+ relation_obj = turn_relation_hash_into_object(relation, relation_hash)
269
+ value[value.index(relation_hash)] = relation_obj
270
+ end
271
+ elsif value.is_a?(Hash)
272
+ relation = @klass.belongs_to_relations[key]
273
+ next if relation.blank?
274
+
275
+ relation_hash = value
276
+ relation_obj = turn_relation_hash_into_object(relation, relation_hash)
277
+ item.attributes[key] = relation_obj
278
+ end
279
+ end
280
+ end
281
+ objects
282
+ end
283
+
284
+ def chunk_results(params = {})
285
+ page = criteria[:page] || 0
286
+ limit = criteria[:limit] || 0
287
+ # completed = false
288
+ records = []
289
+ loop do
290
+ params[:pageNumber] = page
291
+ params[:pageSize] = limit > 0 ? [limit - records.size, criteria[:chunk]].min : criteria[:chunk]
292
+ break if params[:pageSize] == 0
293
+
294
+ results = get_results(params)
295
+ break unless results.present?
296
+
297
+ records += results
298
+ break if limit > 0 and records.size >= limit
299
+
300
+ page += 1
301
+ end
302
+
303
+ records
304
+ end
305
+
306
+ def first
307
+ limit(1)
308
+ execute.first
309
+ end
310
+
311
+ def all
312
+ execute
313
+ end
314
+
315
+ def count
316
+ criteria[:count] = true
317
+ execute
318
+ end
319
+
320
+ # Find a Lockstep::ApiRecord object by ID
321
+ #
322
+ # @param [String] id the ID of the Parse object you want to find.
323
+ # @return [Lockstep::ApiRecord] an object that subclasses Lockstep::ApiRecord.
324
+ def find(id)
325
+ raise Lockstep::Exceptions::RecordNotFound, "Couldn't find #{name} without an ID" if id.blank?
326
+ record = where(@klass.id_ref => id).first
327
+ raise Lockstep::Exceptions::RecordNotFound, "Couldn't find #{name} with id: #{id}" if record.blank?
328
+ record
329
+ end
330
+
331
+ def method_missing(meth, *args, &block)
332
+ method_name = method_name.to_s
333
+ if method_name.start_with?("find_by_")
334
+ attrib = method_name.gsub(/^find_by_/, "")
335
+ finder_name = "find_all_by_#{attrib}"
336
+
337
+ define_singleton_method(finder_name) do |target_value|
338
+ where({ attrib.to_sym => target_value }).first
339
+ end
340
+
341
+ send(finder_name, args[0])
342
+ elsif method_name.start_with?("find_all_by_")
343
+ attrib = method_name.gsub(/^find_all_by_/, "")
344
+ finder_name = "find_all_by_#{attrib}"
345
+
346
+ define_singleton_method(finder_name) do |target_value|
347
+ where({ attrib.to_sym => target_value }).all
348
+ end
349
+
350
+ send(finder_name, args[0])
351
+ end
352
+
353
+ if @klass.scopes[meth].present?
354
+ instance_exec *args, &@klass.scopes[meth]
355
+ elsif Array.method_defined?(meth)
356
+ all.send(meth, *args, &block)
357
+ else
358
+ super
359
+ end
360
+ end
361
+
362
+ def respond_to?(meth)
363
+ if Array.method_defined?(meth)
364
+ true
365
+ else
366
+ super
367
+ end
368
+ end
369
+
370
+ def unscoped
371
+ Lockstep::Query.new(@klass)
372
+ end
373
+
374
+ private
375
+
376
+ def turn_relation_hash_into_object(relation, hash)
377
+ return nil if hash == nil
378
+ relation_klass = relation[:class_name].to_s.constantize
379
+ relation_object = relation_klass.new
380
+ hash.each do |key, value|
381
+ class_name_in_a_hash = false
382
+ if value.is_a?(Array)
383
+ value.each do |item|
384
+ if item and item.is_a?(Hash)
385
+ class_name_in_a_hash = true if relation_klass.has_many_relations[key].present?
386
+ break
387
+ end
388
+ end
389
+ end
390
+
391
+ if value.is_a?(Array) and class_name_in_a_hash
392
+ value.each do |object_in_array|
393
+ fresh_object = turn_relation_hash_into_object(relation_klass.has_many_relations[key][:class_name], object_in_array)
394
+ value[value.index(object_in_array)] = fresh_object
395
+ end
396
+ # Convert key from camelcase to snake-case
397
+ relation_object.attributes[key.underscore] = value
398
+ elsif value.is_a?(Hash) and relation_klass.belongs_to_relations[key].present?
399
+ # Convert key from camelcase to snake-case
400
+ relation_object.attributes[key.underscore] = turn_relation_hash_into_object(relation_klass.belongs_to_relations[key][:class_name], value)
401
+ else
402
+ # Convert key from camelcase to snake-case
403
+ relation_object.attributes[key.underscore] = value if key.to_s != "__type" and key.to_s != "className"
404
+ end
405
+ end
406
+
407
+ relation_object
408
+ end
409
+ end