fmrest 0.12.0 → 0.15.1

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 +3 -0
  3. data/CHANGELOG.md +31 -0
  4. data/README.md +252 -851
  5. metadata +71 -108
  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,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- module Model
6
- module Auth
7
- extend ::ActiveSupport::Concern
8
-
9
- class_methods do
10
- # Logs out the database session for this model (and other models
11
- # using the same credentials).
12
- #
13
- # @raise [FmRest::V1::TokenSession::NoSessionTokenSet] if no session
14
- # token was set (and no request is sent).
15
- def logout!
16
- connection.delete(FmRest::V1.session_path("dummy-token"))
17
- end
18
-
19
- # Logs out the database session for this model (and other models
20
- # using the same credentials). Unlike `logout!`, no exception is
21
- # raised in case of missing session token.
22
- #
23
- # @return [Boolean] Whether the logout request was sent (it's only
24
- # sent if a session token was previously set)
25
- def logout
26
- logout!
27
- true
28
- rescue FmRest::V1::TokenSession::NoSessionTokenSet
29
- false
30
- end
31
-
32
- def request_auth_token
33
- FmRest::V1.request_auth_token(FmRest::V1.auth_connection(fmrest_config))
34
- end
35
-
36
- def request_auth_token!
37
- FmRest::V1.request_auth_token!(FmRest::V1.auth_connection(fmrest_config))
38
- end
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,163 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- module Model
6
- # This module provides methods for configuring the Farday connection for
7
- # the model, as well as setting up the connection itself.
8
- #
9
- module Connection
10
- extend ActiveSupport::Concern
11
-
12
- included do
13
- class_attribute :faraday_block, instance_accessor: false, instance_predicate: false
14
- class << self; private :faraday_block, :faraday_block=; end
15
-
16
- # FM Data API expects PATCH for updates (Spyke uses PUT by default)
17
- self.callback_methods = { create: :post, update: :patch }.freeze
18
- end
19
-
20
- class_methods do
21
- def fmrest_config
22
- if fmrest_config_overlay
23
- return FmRest.default_connection_settings.merge(fmrest_config_overlay, skip_validation: true)
24
- end
25
-
26
- FmRest.default_connection_settings
27
- end
28
-
29
- # Sets the FileMaker connection settings for the model.
30
- #
31
- # Behaves similar to ActiveSupport's `class_attribute`, so it can be
32
- # inherited and safely overwritten in subclasses.
33
- #
34
- # @param settings [Hash] The settings hash
35
- #
36
- def fmrest_config=(settings)
37
- settings = ConnectionSettings.new(settings, skip_validation: true)
38
-
39
- singleton_class.redefine_method(:fmrest_config) do
40
- overlay = fmrest_config_overlay
41
- return settings.merge(overlay, skip_validation: true) if overlay
42
- settings
43
- end
44
- end
45
-
46
- # Allows overriding some connection settings in a thread-local
47
- # manner. Useful in the use case where you want to connect to the
48
- # same database using different accounts (e.g. credentials provided
49
- # by users in a web app context).
50
- #
51
- # @param (see #fmrest_config=)
52
- #
53
- def fmrest_config_overlay=(settings)
54
- Thread.current[fmrest_config_overlay_key] = settings
55
- end
56
-
57
- # @return [FmRest::ConnectionSettings] the connection settings
58
- # overlay if any is in use
59
- #
60
- def fmrest_config_overlay
61
- Thread.current[fmrest_config_overlay_key] || begin
62
- superclass.fmrest_config_overlay
63
- rescue NoMethodError
64
- nil
65
- end
66
- end
67
-
68
- # Clears the connection settings overlay.
69
- #
70
- def clear_fmrest_config_overlay
71
- Thread.current[fmrest_config_overlay_key] = nil
72
- end
73
-
74
- # Runs a block of code in the context of the given connection
75
- # settings without affecting the connection settings outside said
76
- # block.
77
- #
78
- # @param (see #fmrest_config=)
79
- #
80
- # @example
81
- # Honeybee.with_overlay(username: "...", password: "...") do
82
- # Honeybee.query(...)
83
- # end
84
- #
85
- def with_overlay(settings, &block)
86
- Fiber.new do
87
- begin
88
- self.fmrest_config_overlay = settings
89
- yield
90
- ensure
91
- self.clear_fmrest_config_overlay
92
- end
93
- end.resume
94
- end
95
-
96
- # Spyke override -- Defaults to `fmrest_connection`
97
- #
98
- def connection
99
- super || fmrest_connection
100
- end
101
-
102
- # Sets a block for injecting custom middleware into the Faraday
103
- # connection.
104
- #
105
- # @example
106
- # class MyModel < FmRest::Spyke::Base
107
- # faraday do |conn|
108
- # # Set up a custom logger for the model
109
- # conn.response :logger, MyApp.logger, bodies: true
110
- # end
111
- # end
112
- #
113
- def faraday(&block)
114
- self.faraday_block = block
115
- end
116
-
117
- private
118
-
119
- def fmrest_connection
120
- memoize = false
121
-
122
- # Don't memoize the connection if there's an overlay, since
123
- # overlays are thread-local and so should be the connection
124
- unless fmrest_config_overlay
125
- return @fmrest_connection if @fmrest_connection
126
- memoize = true
127
- end
128
-
129
- config = ConnectionSettings.wrap(fmrest_config)
130
-
131
- connection =
132
- FmRest::V1.build_connection(config) do |conn|
133
- faraday_block.call(conn) if faraday_block
134
-
135
- # Pass the class to SpykeFormatter's initializer so it can have
136
- # access to extra context defined in the model, e.g. a portal
137
- # where name of the portal and the attributes prefix don't match
138
- # and need to be specified as options to `portal`
139
- conn.use FmRest::Spyke::SpykeFormatter, self
140
-
141
- conn.use FmRest::V1::TypeCoercer, config
142
-
143
- # FmRest::Spyke::JsonParse expects symbol keys
144
- conn.response :json, parser_options: { symbolize_names: true }
145
- end
146
-
147
- @fmrest_connection = connection if memoize
148
-
149
- connection
150
- end
151
-
152
- def fmrest_config_overlay_key
153
- :"#{object_id}.fmrest_config_overlay"
154
- end
155
- end
156
-
157
- def fmrest_config
158
- self.class.fmrest_config
159
- end
160
- end
161
- end
162
- end
163
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/container_field"
4
-
5
- module FmRest
6
- module Spyke
7
- module Model
8
- # This module adds support for container fields.
9
- #
10
- module ContainerFields
11
- extend ::ActiveSupport::Concern
12
-
13
- class_methods do
14
- # Defines a container field on the model.
15
- #
16
- # @param name [Symbol] the name of the container field
17
- #
18
- # @option options [String] :field_name (nil) the name of the container
19
- # field in the FileMaker layout (only needed if it doesn't match
20
- # the name given)
21
- #
22
- # @example
23
- # class Honeybee < FmRest::Spyke::Base
24
- # container :photo, field_name: "Beehive Photo ID"
25
- # end
26
- #
27
- def container(name, options = {})
28
- field_name = options[:field_name] || name
29
-
30
- define_method(name) do
31
- @container_fields ||= {}
32
- @container_fields[name.to_sym] ||= ContainerField.new(self, field_name)
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
39
- end
40
-
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- module Model
6
- module GlobalFields
7
- extend ::ActiveSupport::Concern
8
-
9
- FULLY_QUALIFIED_FIELD_NAME_MATCHER = /\A[^:]+::[^:]+\Z/.freeze
10
-
11
- class_methods do
12
- def set_globals(values_hash)
13
- connection.patch(FmRest::V1.globals_path, {
14
- globalFields: normalize_globals_hash(values_hash)
15
- })
16
- end
17
-
18
- private
19
-
20
- def normalize_globals_hash(hash)
21
- hash.each_with_object({}) do |(k, v), normalized|
22
- if v.kind_of?(Hash)
23
- v.each do |k2, v2|
24
- normalized["#{k}::#{k2}"] = v2
25
- end
26
- next
27
- end
28
-
29
- unless FULLY_QUALIFIED_FIELD_NAME_MATCHER === k.to_s
30
- raise ArgumentError, "global fields must be given in fully qualified format (table name::field name)"
31
- end
32
-
33
- normalized[k] = v
34
- end
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- module Model
6
- module Http
7
- extend ::ActiveSupport::Concern
8
-
9
- class_methods do
10
-
11
- # Override Spyke's request method to keep a thread-local copy of the
12
- # last request's metadata, so that we can access things like script
13
- # execution results after a save, etc.
14
-
15
-
16
- # Spyke override -- Keeps metadata in thread-local class variable.
17
- #
18
- def request(*args)
19
- super.tap do |r|
20
- Thread.current[last_request_metadata_key] = r.metadata
21
- end
22
- end
23
-
24
- def last_request_metadata(key: last_request_metadata_key)
25
- Thread.current[key]
26
- end
27
-
28
- private
29
-
30
- def last_request_metadata_key
31
- "#{to_s}.last_request_metadata"
32
- end
33
- end
34
- end
35
-
36
- # Spyke override -- Uses `__record_id` for building the record URI.
37
- #
38
- def uri
39
- ::Spyke::Path.new(@uri_template, fmrest_uri_attributes) if @uri_template
40
- end
41
-
42
- private
43
-
44
- # Spyke override (private) -- Use `__record_id` instead of `id`
45
- #
46
- def resolve_path_from_action(action)
47
- case action
48
- when Symbol then uri.join(action)
49
- when String then ::Spyke::Path.new(action, fmrest_uri_attributes)
50
- else uri
51
- end
52
- end
53
-
54
- def fmrest_uri_attributes
55
- if persisted?
56
- { __record_id: __record_id }
57
- else
58
- # NOTE: it seems silly to be calling attributes.slice(:__record_id)
59
- # when the record is supposed to not have a record_id set (since
60
- # persisted? is false here), but it makes sense in the context of how
61
- # Spyke works:
62
- #
63
- # When calling Model.find(id), Spyke will internally create a scope
64
- # with .where(primary_key => id) and call .find_one on it. Then,
65
- # somewhere down the line Spyke creates a new empty instance of the
66
- # current model class to get its .uri property (the one we're
67
- # partially building through this method and which contains these URI
68
- # attributes). When initializing a record Spyke first forcefully
69
- # assigns the .where()-set attributes from the current scope onto
70
- # that instance's attributes hash, which then leads us right here,
71
- # where we might have __record_id assigned as a scope attribute:
72
- attributes.slice(:__record_id)
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,256 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/relation"
4
- require "fmrest/spyke/validation_error"
5
-
6
- module FmRest
7
- module Spyke
8
- module Model
9
- # This module adds and extends various ORM features in Spyke models,
10
- # including custom query methods, remote script execution and
11
- # exception-raising persistence methods.
12
- #
13
- module Orm
14
- extend ::ActiveSupport::Concern
15
-
16
- included do
17
- # Allow overriding FM's default limit (by default it's 100)
18
- class_attribute :default_limit, instance_accessor: false, instance_predicate: false
19
-
20
- class_attribute :default_sort, instance_accessor: false, instance_predicate: false
21
-
22
- # Whether to raise an FmRest::APIError::NoMatchingRecordsError when a
23
- # _find request has no results
24
- class_attribute :raise_on_no_matching_records, instance_accessor: false, instance_predicate: false
25
- end
26
-
27
- class_methods do
28
- # Methods delegated to `FmRest::Spyke::Relation`
29
- delegate :limit, :offset, :sort, :order, :query, :omit, :portal,
30
- :portals, :includes, :with_all_portals, :without_portals,
31
- :script, :find_one, :first, :any, :find_some,
32
- :find_in_batches, :find_each, to: :all
33
-
34
- # Spyke override -- Use FmRest's Relation instead of Spyke's vanilla
35
- # one
36
- #
37
- def all
38
- current_scope || Relation.new(self, uri: uri)
39
- end
40
-
41
- # Spyke override -- allows properly setting limit, offset and other
42
- # options, as well as using the appropriate HTTP method/URL depending
43
- # on whether there's a query present in the current scope.
44
- #
45
- # @example
46
- # Person.query(first_name: "Stefan").fetch # -> POST .../_find
47
- #
48
- def fetch
49
- if current_scope.has_query?
50
- scope = extend_scope_with_fm_params(current_scope, prefixed: false)
51
- scope = scope.where(query: scope.query_params)
52
- scope = scope.with(FmRest::V1::find_path(layout))
53
- else
54
- scope = extend_scope_with_fm_params(current_scope, prefixed: true)
55
- end
56
-
57
- previous, self.current_scope = current_scope, scope
58
-
59
- # The DAPI returns a 401 "No records match the request" error when
60
- # nothing matches a _find request, so we need to catch it in order
61
- # to provide sane behavior (i.e. return an empty resultset)
62
- begin
63
- current_scope.has_query? ? scoped_request(:post) : super
64
- rescue FmRest::APIError::NoMatchingRecordsError => e
65
- raise e if raise_on_no_matching_records
66
- ::Spyke::Result.new({})
67
- end
68
- ensure
69
- self.current_scope = previous
70
- end
71
-
72
- # Exception-raising version of `#create`
73
- #
74
- # @param attributes [Hash] the attributes to initialize the
75
- # record with
76
- #
77
- def create!(attributes = {})
78
- new(attributes).tap(&:save!)
79
- end
80
-
81
- # Requests execution of a FileMaker script.
82
- #
83
- # @param script_name [String] the name of the FileMaker script to
84
- # execute
85
- # @param param [String] an optional paramater for the script
86
- #
87
- def execute_script(script_name, param: nil)
88
- params = {}
89
- params = {"script.param" => param} unless param.nil?
90
- request(:get, FmRest::V1::script_path(layout, script_name), params)
91
- end
92
-
93
- private
94
-
95
- def extend_scope_with_fm_params(scope, prefixed: false)
96
- prefix = prefixed ? "_" : nil
97
-
98
- where_options = {}
99
-
100
- where_options["#{prefix}limit"] = scope.limit_value if scope.limit_value
101
- where_options["#{prefix}offset"] = scope.offset_value if scope.offset_value
102
-
103
- if scope.sort_params.present?
104
- where_options["#{prefix}sort"] =
105
- prefixed ? scope.sort_params.to_json : scope.sort_params
106
- end
107
-
108
- unless scope.included_portals.nil?
109
- where_options["portal"] =
110
- prefixed ? scope.included_portals.to_json : scope.included_portals
111
- end
112
-
113
- if scope.portal_params.present?
114
- scope.portal_params.each do |portal_param, value|
115
- where_options["#{prefix}#{portal_param}"] = value
116
- end
117
- end
118
-
119
- if scope.script_params.present?
120
- where_options.merge!(scope.script_params)
121
- end
122
-
123
- scope.where(where_options)
124
- end
125
- end
126
-
127
- # Spyke override -- Adds a number of features to original `#save`:
128
- #
129
- # * Validations
130
- # * Data API scripts execution
131
- # * Refresh of dirty attributes
132
- #
133
- # @option options [String] :script the name of a FileMaker script to execute
134
- # upon saving
135
- # @option options [Boolean] :raise_validation_errors whether to raise an
136
- # exception if validations fail
137
- #
138
- # @return [true] if saved successfully
139
- # @return [false] if validations or persistence failed
140
- #
141
- def save(options = {})
142
- callback = persisted? ? :update : :create
143
-
144
- return false unless perform_save_validations(callback, options)
145
- return false unless perform_save_persistence(callback, options)
146
-
147
- true
148
- end
149
-
150
- # Exception-raising version of `#save`.
151
- #
152
- # @option (see #save)
153
- #
154
- # @return [true] if saved successfully
155
- #
156
- # @raise if validations or presistence failed
157
- #
158
- def save!(options = {})
159
- save(options.merge(raise_validation_errors: true))
160
- end
161
-
162
- # Exception-raising version of `#update`.
163
- #
164
- # @param new_attributes [Hash] a hash of record attributes to update
165
- # the record with
166
- #
167
- # @option (see #save)
168
- #
169
- def update!(new_attributes, options = {})
170
- self.attributes = new_attributes
171
- save!(options)
172
- end
173
-
174
- # Spyke override -- Adds support for Data API script execution.
175
- #
176
- # @option options [String] :script the name of a FileMaker script to execute
177
- # upon deletion
178
- #
179
- def destroy(options = {})
180
- # For whatever reason the Data API wants the script params as query
181
- # string params for DELETE requests, making this more complicated
182
- # than it should be
183
- script_query_string = if options.has_key?(:script)
184
- "?" + Faraday::Utils.build_query(FmRest::V1.convert_script_params(options[:script]))
185
- else
186
- ""
187
- end
188
-
189
- self.attributes = delete(uri.to_s + script_query_string)
190
- end
191
-
192
- # (see #destroy)
193
- #
194
- # @option (see #destroy)
195
- #
196
- def reload(options = {})
197
- scope = self.class
198
- scope = scope.script(options[:script]) if options.has_key?(:script)
199
- reloaded = scope.find(__record_id)
200
- self.attributes = reloaded.attributes
201
- self.__mod_id = reloaded.mod_id
202
- end
203
-
204
- # ActiveModel 5+ implements this method, so we only need it if we're in
205
- # the older AM4
206
- if ActiveModel::VERSION::MAJOR == 4
207
- def validate!(context = nil)
208
- valid?(context) || raise_validation_error
209
- end
210
- end
211
-
212
- private
213
-
214
- def perform_save_validations(context, options)
215
- return true if options[:validate] == false
216
- options[:raise_validation_errors] ? validate!(context) : validate(context)
217
- end
218
-
219
- def perform_save_persistence(callback, options)
220
- run_callbacks :save do
221
- run_callbacks(callback) do
222
-
223
- begin
224
- send self.class.method_for(callback), build_params_for_save(options)
225
-
226
- rescue APIError::ValidationError => e
227
- if options[:raise_validation_errors]
228
- raise e
229
- else
230
- return false
231
- end
232
- end
233
-
234
- end
235
- end
236
-
237
- true
238
- end
239
-
240
- def build_params_for_save(options)
241
- to_params.tap do |params|
242
- if options.has_key?(:script)
243
- params.merge!(FmRest::V1.convert_script_params(options[:script]))
244
- end
245
- end
246
- end
247
-
248
- # Overwrite ActiveModel's raise_validation_error to use our own class
249
- #
250
- def raise_validation_error # :doc:
251
- raise(ValidationError.new(self))
252
- end
253
- end
254
- end
255
- end
256
- end