fmrest 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +11 -0
  4. data/README.md +175 -809
  5. metadata +61 -109
  6. data/.gitignore +0 -26
  7. data/.rspec +0 -3
  8. data/.travis.yml +0 -5
  9. data/Gemfile +0 -3
  10. data/Rakefile +0 -6
  11. data/UPGRADING +0 -15
  12. data/fmrest.gemspec +0 -49
  13. data/lib/fmrest.rb +0 -36
  14. data/lib/fmrest/connection_settings.rb +0 -124
  15. data/lib/fmrest/errors.rb +0 -30
  16. data/lib/fmrest/spyke.rb +0 -21
  17. data/lib/fmrest/spyke/base.rb +0 -9
  18. data/lib/fmrest/spyke/container_field.rb +0 -59
  19. data/lib/fmrest/spyke/model.rb +0 -33
  20. data/lib/fmrest/spyke/model/associations.rb +0 -86
  21. data/lib/fmrest/spyke/model/attributes.rb +0 -163
  22. data/lib/fmrest/spyke/model/auth.rb +0 -43
  23. data/lib/fmrest/spyke/model/connection.rb +0 -163
  24. data/lib/fmrest/spyke/model/container_fields.rb +0 -40
  25. data/lib/fmrest/spyke/model/global_fields.rb +0 -40
  26. data/lib/fmrest/spyke/model/http.rb +0 -77
  27. data/lib/fmrest/spyke/model/orm.rb +0 -256
  28. data/lib/fmrest/spyke/model/record_id.rb +0 -78
  29. data/lib/fmrest/spyke/model/serialization.rb +0 -99
  30. data/lib/fmrest/spyke/model/uri.rb +0 -29
  31. data/lib/fmrest/spyke/portal.rb +0 -55
  32. data/lib/fmrest/spyke/relation.rb +0 -359
  33. data/lib/fmrest/spyke/spyke_formatter.rb +0 -273
  34. data/lib/fmrest/spyke/validation_error.rb +0 -25
  35. data/lib/fmrest/string_date.rb +0 -220
  36. data/lib/fmrest/token_store.rb +0 -12
  37. data/lib/fmrest/token_store/active_record.rb +0 -74
  38. data/lib/fmrest/token_store/base.rb +0 -25
  39. data/lib/fmrest/token_store/memory.rb +0 -26
  40. data/lib/fmrest/token_store/moneta.rb +0 -41
  41. data/lib/fmrest/token_store/null.rb +0 -20
  42. data/lib/fmrest/token_store/redis.rb +0 -45
  43. data/lib/fmrest/v1.rb +0 -23
  44. data/lib/fmrest/v1/auth.rb +0 -30
  45. data/lib/fmrest/v1/connection.rb +0 -115
  46. data/lib/fmrest/v1/container_fields.rb +0 -114
  47. data/lib/fmrest/v1/dates.rb +0 -81
  48. data/lib/fmrest/v1/paths.rb +0 -47
  49. data/lib/fmrest/v1/raise_errors.rb +0 -57
  50. data/lib/fmrest/v1/token_session.rb +0 -133
  51. data/lib/fmrest/v1/token_store/active_record.rb +0 -13
  52. data/lib/fmrest/v1/token_store/memory.rb +0 -13
  53. data/lib/fmrest/v1/type_coercer.rb +0 -192
  54. data/lib/fmrest/v1/utils.rb +0 -94
  55. data/lib/fmrest/version.rb +0 -5
@@ -1,273 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "ostruct"
5
-
6
- module FmRest
7
- module Spyke
8
- # Metadata class to be passed to Spyke::Collection#metadata
9
- class Metadata < Struct.new(:messages, :script, :data_info)
10
- alias_method :scripts, :script
11
- end
12
-
13
- class DataInfo < OpenStruct
14
- def total_record_count; totalRecordCount; end
15
- def found_count; foundCount; end
16
- def returned_count; returnedCount; end
17
- end
18
-
19
- # Response Faraday middleware for converting FM API's response JSON into
20
- # Spyke's expected format
21
- class SpykeFormatter < ::Faraday::Response::Middleware
22
- SINGLE_RECORD_RE = %r(/records/\d+\z).freeze
23
- MULTIPLE_RECORDS_RE = %r(/records\z).freeze
24
- CONTAINER_RE = %r(/records/\d+/containers/[^/]+/\d+\z).freeze
25
- FIND_RECORDS_RE = %r(/_find\b).freeze
26
- SCRIPT_REQUEST_RE = %r(/script/[^/]+\z).freeze
27
-
28
- VALIDATION_ERROR_RANGE = 500..599
29
-
30
- # @param app [#call]
31
- # @param model [Class<FmRest::Spyke::Base>]
32
- def initialize(app, model)
33
- super(app)
34
- @model = model
35
- end
36
-
37
- # @param env [Faraday::Env]
38
- def on_complete(env)
39
- return unless env.body.is_a?(Hash)
40
-
41
- json = env.body
42
-
43
- case
44
- when single_record_request?(env)
45
- env.body = prepare_single_record(json)
46
- when multiple_records_request?(env), find_request?(env)
47
- env.body = prepare_collection(json)
48
- when create_request?(env), update_request?(env), delete_request?(env), container_upload_request?(env)
49
- env.body = prepare_save_response(json)
50
- when execute_script_request?(env)
51
- env.body = build_base_hash(json)
52
- else
53
- # Attempt to parse unknown requests too
54
- env.body = build_base_hash(json)
55
- end
56
- end
57
-
58
- private
59
-
60
- # @param json [Hash]
61
- # @return [Hash] the response in Spyke format
62
- def prepare_save_response(json)
63
- response = json[:response]
64
-
65
- data = {}
66
- data[:__mod_id] = response[:modId] if response[:modId]
67
- data[:__record_id] = response[:recordId].to_i if response[:recordId]
68
-
69
- build_base_hash(json, true).merge!(data: data)
70
- end
71
-
72
- # (see #prepare_save_response)
73
- def prepare_single_record(json)
74
- data =
75
- json[:response][:data] &&
76
- prepare_record_data(json[:response][:data].first)
77
-
78
- build_base_hash(json).merge!(data: data)
79
- end
80
-
81
- # (see #prepare_save_response)
82
- def prepare_collection(json)
83
- data =
84
- json[:response][:data] &&
85
- json[:response][:data].map { |record_data| prepare_record_data(record_data) }
86
-
87
- build_base_hash(json).merge!(data: data)
88
- end
89
-
90
- # @param json [Hash]
91
- # @param include_errors [Boolean]
92
- # @return [FmRest::Spyke::Metadata] the skeleton structure for a
93
- # Spyke-formatted response
94
- def build_base_hash(json, include_errors = false)
95
- {
96
- metadata: Metadata.new(
97
- prepare_messages(json),
98
- prepare_script_results(json),
99
- prepare_data_info(json)
100
- ).freeze,
101
- errors: include_errors ? prepare_errors(json) : {}
102
- }
103
- end
104
-
105
- # @param json [Hash]
106
- # @return [Array<OpenStruct>] the skeleton structure for a
107
- # Spyke-formatted response
108
- def prepare_messages(json)
109
- return [] unless json[:messages]
110
- json[:messages].map { |m| OpenStruct.new(m).freeze }.freeze
111
- end
112
-
113
- # @param json [Hash]
114
- # @return [OpenStruct] the script(s) execution results for Spyke metadata
115
- # format
116
- def prepare_script_results(json)
117
- results = {}
118
-
119
- [:prerequest, :presort].each do |s|
120
- if json[:response][:"scriptError.#{s}"]
121
- results[s] = OpenStruct.new(
122
- result: json[:response][:"scriptResult.#{s}"],
123
- error: json[:response][:"scriptError.#{s}"]
124
- ).freeze
125
- end
126
- end
127
-
128
- if json[:response][:scriptError]
129
- results[:after] = OpenStruct.new(
130
- result: json[:response][:scriptResult],
131
- error: json[:response][:scriptError]
132
- ).freeze
133
- end
134
-
135
- results.present? ? OpenStruct.new(results).freeze : nil
136
- end
137
-
138
- # @param json [Hash]
139
- # @return [OpenStruct] the script(s) execution results for
140
- # Spyke metadata format
141
- def prepare_data_info(json)
142
- data_info = json[:response] && json[:response][:dataInfo]
143
-
144
- return nil unless data_info.present?
145
-
146
- DataInfo.new(data_info).freeze
147
- end
148
-
149
- # @param json [Hash]
150
- # @return [Hash] the errors hash in Spyke format
151
- def prepare_errors(json)
152
- # Code 0 means "No Error"
153
- # https://fmhelp.filemaker.com/help/17/fmp/en/index.html#page/FMP_Help/error-codes.html
154
- return {} if json[:messages][0][:code].to_i == 0
155
-
156
- json[:messages].each_with_object(base: []) do |message, hash|
157
- # Only include validation errors
158
- next unless VALIDATION_ERROR_RANGE.include?(message[:code].to_i)
159
-
160
- hash[:base] << "#{message[:message]} (#{message[:code]})"
161
- end
162
- end
163
-
164
- # `json_data` is expected in this format:
165
- #
166
- # {
167
- # "fieldData": {
168
- # "fieldName1" : "fieldValue1",
169
- # "fieldName2" : "fieldValue2",
170
- # ...
171
- # },
172
- # "portalData": {
173
- # "portal1" : [
174
- # { <portalRecord1> },
175
- # { <portalRecord2> },
176
- # ...
177
- # ],
178
- # "portal2" : [
179
- # { <portalRecord1> },
180
- # { <portalRecord2> },
181
- # ...
182
- # ]
183
- # },
184
- # "modId": <Id_for_last_modification>,
185
- # "recordId": <Unique_internal_ID_for_this_record>
186
- # }
187
- #
188
- # @param json_data [Hash]
189
- # @return [Hash] the record data in Spyke format
190
- def prepare_record_data(json_data)
191
- out = { __record_id: json_data[:recordId].to_i, __mod_id: json_data[:modId] }
192
- out.merge!(json_data[:fieldData])
193
- out.merge!(prepare_portal_data(json_data[:portalData])) if json_data[:portalData]
194
- out
195
- end
196
-
197
- # Extracts `recordId` and strips the `"PortalName::"` field prefix for each
198
- # portal
199
- #
200
- # Sample `json_portal_data`:
201
- #
202
- # "portalData": {
203
- # "Orders":[
204
- # { "Orders::DeliveryDate": "3/7/2017", "recordId": "23" }
205
- # ]
206
- # }
207
- #
208
- # @param json_portal_data [Hash]
209
- # @return [Hash] the portal data in Spyke format
210
- def prepare_portal_data(json_portal_data)
211
- json_portal_data.each_with_object({}) do |(portal_name, portal_records), out|
212
- portal_options = @model.portal_options[portal_name.to_s] || {}
213
-
214
- out[portal_name] =
215
- portal_records.map do |portal_fields|
216
- attributes = { __record_id: portal_fields[:recordId].to_i }
217
- attributes[:__mod_id] = portal_fields[:modId] if portal_fields[:modId]
218
-
219
- prefix = portal_options[:attribute_prefix] || portal_name
220
- prefix_matcher = /\A#{prefix}::/
221
-
222
- portal_fields.each do |k, v|
223
- next if :recordId == k || :modId == k
224
- attributes[k.to_s.gsub(prefix_matcher, "").to_sym] = v
225
- end
226
-
227
- attributes
228
- end
229
- end
230
- end
231
-
232
- # @param env [Faraday::Env]
233
- # @return [Boolean]
234
- def single_record_request?(env)
235
- env.method == :get && env.url.path.match(SINGLE_RECORD_RE)
236
- end
237
-
238
- # (see #single_record_request?)
239
- def multiple_records_request?(env)
240
- env.method == :get && env.url.path.match(MULTIPLE_RECORDS_RE)
241
- end
242
-
243
- # (see #single_record_request?)
244
- def find_request?(env)
245
- env.method == :post && env.url.path.match(FIND_RECORDS_RE)
246
- end
247
-
248
- # (see #single_record_request?)
249
- def update_request?(env)
250
- env.method == :patch && env.url.path.match(SINGLE_RECORD_RE)
251
- end
252
-
253
- # (see #single_record_request?)
254
- def create_request?(env)
255
- env.method == :post && env.url.path.match(MULTIPLE_RECORDS_RE)
256
- end
257
-
258
- # (see #single_record_request?)
259
- def container_upload_request?(env)
260
- env.method == :post && env.url.path.match(CONTAINER_RE)
261
- end
262
-
263
- # (see #single_record_request?)
264
- def delete_request?(env)
265
- env.method == :delete && env.url.path.match(SINGLE_RECORD_RE)
266
- end
267
-
268
- def execute_script_request?(env)
269
- env.method == :get && env.url.path.match(SCRIPT_REQUEST_RE)
270
- end
271
- end
272
- end
273
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- # ActiveModel 4 doesn't include a ValidationError class, which we want to
6
- # raise when model.validate! fails.
7
- #
8
- # In order to break the least amount of code that uses AM5+, while still
9
- # supporting AM4 we use this proxy class that inherits from
10
- # AM::ValidationError if it's there, or reimplements it otherwise
11
- if defined?(::ActiveModel::ValidationError)
12
- class ValidationError < ::ActiveModel::ValidationError; end
13
- else
14
- class ValidationError < StandardError
15
- attr_reader :model
16
-
17
- def initialize(model)
18
- @model = model
19
- errors = @model.errors.full_messages.join(", ")
20
- super("Invalid model: #{errors}")
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,220 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "date"
4
-
5
- module FmRest
6
- # Gotchas:
7
- #
8
- # 1.
9
- #
10
- # Date === <StringDate instance> # => false
11
- #
12
- # The above can affect case conditions, as trying to match a StringDate
13
- # with:
14
- #
15
- # case obj
16
- # when Date
17
- # ...
18
- #
19
- # ...will not work.
20
- #
21
- # Instead one must specify the FmRest::StringDate class:
22
- #
23
- # case obj
24
- # when Date, FmRest::StringDate
25
- # ...
26
- #
27
- # 2.
28
- #
29
- # StringDate#eql? only matches other strings, not dates.
30
- #
31
- # This could affect hash indexing when a StringDate is used as a key.
32
- #
33
- # TODO: Verify the above
34
- #
35
- # 3.
36
- #
37
- # StringDate#succ and StringDate#next return a String, despite Date#succ
38
- # and Date#next also existing.
39
- #
40
- # Workaround: Use StringDate#next_day or strdate + 1
41
- #
42
- # 4.
43
- #
44
- # StringDate#to_s returns the original string, not the Date string
45
- # representation.
46
- #
47
- # Workaround: Use strdate.to_date.to_s
48
- #
49
- # 5.
50
- #
51
- # StringDate#hash returns the hash for the string (important when using
52
- # a StringDate as a hash key)
53
- #
54
- # 6.
55
- #
56
- # StringDate#as_json returns the string
57
- #
58
- # Workaround: Use strdate.to_date.as_json
59
- #
60
- # 7.
61
- #
62
- # Equality with Date is not reciprocal:
63
- #
64
- # str_date == date #=> true
65
- # date == str_date #=> false
66
- #
67
- # NOTE: Potential workaround: Inherit StringDate from Date instead of String
68
- #
69
- # 8.
70
- #
71
- # Calling string transforming methods (e.g. .upcase) returns a StringDate
72
- # instead of a String.
73
- #
74
- # NOTE: Potential workaround: Inherit StringDate from Date instead of String
75
- #
76
- class StringDate < String
77
- DELEGATE_CLASS = ::Date
78
-
79
- class InvalidDate < ArgumentError; end
80
-
81
- class << self
82
- def strptime(str, date_format, *_)
83
- begin
84
- date = self::DELEGATE_CLASS.strptime(str, date_format)
85
- rescue ArgumentError
86
- raise InvalidDate
87
- end
88
-
89
- new(str, date)
90
- end
91
- end
92
-
93
- def initialize(str, date, **str_args)
94
- raise ArgumentError, "str must be of class String" unless str.is_a?(String)
95
- raise ArgumentError, "date must be of class #{self.class::DELEGATE_CLASS.name}" unless date.is_a?(self.class::DELEGATE_CLASS)
96
-
97
- super(str, **str_args)
98
-
99
- @delegate = date
100
-
101
- freeze
102
- end
103
-
104
- def is_a?(klass)
105
- klass == ::Date || super
106
- end
107
- alias_method :kind_of?, :is_a?
108
-
109
- def to_date
110
- @delegate
111
- end
112
-
113
- def to_datetime
114
- @delegate.to_datetime
115
- end
116
-
117
- def to_time
118
- @delegate.to_time
119
- end
120
-
121
- # ActiveSupport method
122
- def in_time_zone(*_)
123
- @delegate.in_time_zone(*_)
124
- end
125
-
126
- def inspect
127
- "#<#{self.class.name} #{@delegate.inspect} - #{super}>"
128
- end
129
-
130
- def <=>(oth)
131
- return @delegate <=> oth if oth.is_a?(::Date) || oth.is_a?(Numeric)
132
- super
133
- end
134
-
135
- def +(val)
136
- return @delegate + val if val.kind_of?(Numeric)
137
- super
138
- end
139
-
140
- def <<(val)
141
- return @delegate << val if val.kind_of?(Numeric)
142
- super
143
- end
144
-
145
- def ==(oth)
146
- return @delegate == oth if oth.kind_of?(::Date) || oth.kind_of?(Numeric)
147
- super
148
- end
149
- alias_method :===, :==
150
-
151
- def upto(oth, &blk)
152
- return @delegate.upto(oth, &blk) if oth.kind_of?(::Date) || oth.kind_of?(Numeric)
153
- super
154
- end
155
-
156
- def between?(a, b)
157
- return @delegate.between?(a, b) if [a, b].any? {|o| o.is_a?(::Date) || o.is_a?(Numeric) }
158
- super
159
- end
160
-
161
- private
162
-
163
- def respond_to_missing?(name, include_private = false)
164
- @delegate.respond_to?(name, include_private)
165
- end
166
-
167
- def method_missing(method, *args, &block)
168
- @delegate.send(method, *args, &block)
169
- end
170
- end
171
-
172
- class StringDateTime < StringDate
173
- DELEGATE_CLASS = ::DateTime
174
-
175
- def is_a?(klass)
176
- klass == ::DateTime || super
177
- end
178
- alias_method :kind_of?, :is_a?
179
-
180
- def to_date
181
- @delegate.to_date
182
- end
183
-
184
- def to_datetime
185
- @delegate
186
- end
187
- end
188
-
189
- module StringDateAwareness
190
- def _parse(v, *_)
191
- if v.is_a?(StringDateTime)
192
- return { year: v.year, mon: v.month, mday: v.mday, hour: v.hour, min: v.min, sec: v.sec, sec_fraction: v.sec_fraction, offset: v.offset }
193
- end
194
- if v.is_a?(StringDate)
195
- return { year: v.year, mon: v.month, mday: v.mday }
196
- end
197
- super
198
- end
199
-
200
- def parse(v, *_)
201
- if v.is_a?(StringDate)
202
- return self == ::DateTime ? v.to_datetime : v.to_date
203
- end
204
- super
205
- end
206
-
207
- # Overriding case equality method so that it returns true for
208
- # `FmRest::StringDate` instances
209
- #
210
- # Calls superclass method
211
- #
212
- def ===(other)
213
- super || other.is_a?(StringDate)
214
- end
215
-
216
- def self.enable(classes: [Date, DateTime])
217
- classes.each { |klass| klass.singleton_class.prepend(self) }
218
- end
219
- end
220
- end