lockstep_rails 0.3.22

Sign up to get free protection for your applications and to get access to all the features.
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