fmrest 0.11.0 → 0.14.0

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +32 -0
  4. data/README.md +228 -844
  5. metadata +71 -101
  6. data/.github/workflows/ci.yml +0 -33
  7. data/.gitignore +0 -26
  8. data/.rspec +0 -3
  9. data/.travis.yml +0 -5
  10. data/Gemfile +0 -3
  11. data/Rakefile +0 -6
  12. data/fmrest.gemspec +0 -38
  13. data/lib/fmrest.rb +0 -34
  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 -23
  18. data/lib/fmrest/spyke/container_field.rb +0 -59
  19. data/lib/fmrest/spyke/model.rb +0 -36
  20. data/lib/fmrest/spyke/model/associations.rb +0 -82
  21. data/lib/fmrest/spyke/model/attributes.rb +0 -171
  22. data/lib/fmrest/spyke/model/auth.rb +0 -43
  23. data/lib/fmrest/spyke/model/connection.rb +0 -135
  24. data/lib/fmrest/spyke/model/container_fields.rb +0 -25
  25. data/lib/fmrest/spyke/model/global_fields.rb +0 -40
  26. data/lib/fmrest/spyke/model/http.rb +0 -37
  27. data/lib/fmrest/spyke/model/orm.rb +0 -212
  28. data/lib/fmrest/spyke/model/serialization.rb +0 -91
  29. data/lib/fmrest/spyke/model/uri.rb +0 -30
  30. data/lib/fmrest/spyke/portal.rb +0 -55
  31. data/lib/fmrest/spyke/relation.rb +0 -359
  32. data/lib/fmrest/spyke/spyke_formatter.rb +0 -273
  33. data/lib/fmrest/spyke/validation_error.rb +0 -25
  34. data/lib/fmrest/string_date.rb +0 -220
  35. data/lib/fmrest/token_store.rb +0 -12
  36. data/lib/fmrest/token_store/active_record.rb +0 -74
  37. data/lib/fmrest/token_store/base.rb +0 -25
  38. data/lib/fmrest/token_store/memory.rb +0 -26
  39. data/lib/fmrest/token_store/moneta.rb +0 -41
  40. data/lib/fmrest/token_store/redis.rb +0 -45
  41. data/lib/fmrest/v1.rb +0 -23
  42. data/lib/fmrest/v1/auth.rb +0 -30
  43. data/lib/fmrest/v1/connection.rb +0 -115
  44. data/lib/fmrest/v1/container_fields.rb +0 -114
  45. data/lib/fmrest/v1/dates.rb +0 -81
  46. data/lib/fmrest/v1/paths.rb +0 -47
  47. data/lib/fmrest/v1/raise_errors.rb +0 -59
  48. data/lib/fmrest/v1/token_session.rb +0 -134
  49. data/lib/fmrest/v1/token_store/active_record.rb +0 -13
  50. data/lib/fmrest/v1/token_store/memory.rb +0 -13
  51. data/lib/fmrest/v1/type_coercer.rb +0 -192
  52. data/lib/fmrest/v1/utils.rb +0 -94
  53. 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,135 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- module Model
6
- module Connection
7
- extend ActiveSupport::Concern
8
-
9
- included do
10
- class_attribute :faraday_block, instance_accessor: false, instance_predicate: false
11
- class << self; private :faraday_block, :faraday_block=; end
12
-
13
- # FM Data API expects PATCH for updates (Spyke's default is PUT)
14
- self.callback_methods = { create: :post, update: :patch }.freeze
15
- end
16
-
17
- class_methods do
18
- def fmrest_config
19
- if fmrest_config_overlay
20
- return FmRest.default_connection_settings.merge(fmrest_config_overlay, skip_validation: true)
21
- end
22
-
23
- FmRest.default_connection_settings
24
- end
25
-
26
- # Behaves similar to ActiveSupport's class_attribute, redefining the
27
- # reader method so it can be inherited and overwritten in subclasses
28
- #
29
- def fmrest_config=(settings)
30
- settings = ConnectionSettings.new(settings, skip_validation: true)
31
-
32
- redefine_singleton_method(:fmrest_config) do
33
- overlay = fmrest_config_overlay
34
- return settings.merge(overlay, skip_validation: true) if overlay
35
- settings
36
- end
37
- end
38
-
39
- # Allows overwriting some connection settings in a thread-local
40
- # manner. Useful in the use case where you want to connect to the
41
- # same database using different accounts (e.g. credentials provided
42
- # by users in a web app context)
43
- #
44
- def fmrest_config_overlay=(settings)
45
- Thread.current[fmrest_config_overlay_key] = settings
46
- end
47
-
48
- def fmrest_config_overlay
49
- Thread.current[fmrest_config_overlay_key] || begin
50
- superclass.fmrest_config_overlay
51
- rescue NoMethodError
52
- nil
53
- end
54
- end
55
-
56
- def clear_fmrest_config_overlay
57
- Thread.current[fmrest_config_overlay_key] = nil
58
- end
59
-
60
- def with_overlay(settings, &block)
61
- Fiber.new do
62
- begin
63
- self.fmrest_config_overlay = settings
64
- yield
65
- ensure
66
- self.clear_fmrest_config_overlay
67
- end
68
- end.resume
69
- end
70
-
71
- def connection
72
- super || fmrest_connection
73
- end
74
-
75
- # Sets a block for injecting custom middleware into the Faraday
76
- # connection. Example usage:
77
- #
78
- # class MyModel < FmRest::Spyke::Base
79
- # faraday do |conn|
80
- # # Set up a custom logger for the model
81
- # conn.response :logger, MyApp.logger, bodies: true
82
- # end
83
- # end
84
- #
85
- def faraday(&block)
86
- self.faraday_block = block
87
- end
88
-
89
- private
90
-
91
- def fmrest_connection
92
- memoize = false
93
-
94
- # Don't memoize the connection if there's an overlay, since
95
- # overlays are thread-local and so should be the connection
96
- unless fmrest_config_overlay
97
- return @fmrest_connection if @fmrest_connection
98
- memoize = true
99
- end
100
-
101
- config = ConnectionSettings.wrap(fmrest_config)
102
-
103
- connection =
104
- FmRest::V1.build_connection(config) do |conn|
105
- faraday_block.call(conn) if faraday_block
106
-
107
- # Pass the class to SpykeFormatter's initializer so it can have
108
- # access to extra context defined in the model, e.g. a portal
109
- # where name of the portal and the attributes prefix don't match
110
- # and need to be specified as options to `portal`
111
- conn.use FmRest::Spyke::SpykeFormatter, self
112
-
113
- conn.use FmRest::V1::TypeCoercer, config
114
-
115
- # FmRest::Spyke::JsonParse expects symbol keys
116
- conn.response :json, parser_options: { symbolize_names: true }
117
- end
118
-
119
- @fmrest_connection = connection if memoize
120
-
121
- connection
122
- end
123
-
124
- def fmrest_config_overlay_key
125
- :"#{object_id}.fmrest_config_overlay"
126
- end
127
- end
128
-
129
- def fmrest_config
130
- self.class.fmrest_config
131
- end
132
- end
133
- end
134
- end
135
- end
@@ -1,25 +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
- module ContainerFields
9
- extend ::ActiveSupport::Concern
10
-
11
- class_methods do
12
- def container(name, options = {})
13
- field_name = options[:field_name] || name
14
-
15
- define_method(name) do
16
- @container_fields ||= {}
17
- @container_fields[name.to_sym] ||= ContainerField.new(self, field_name)
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
@@ -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,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/relation"
4
-
5
- module FmRest
6
- module Spyke
7
- module Model
8
- module Http
9
- extend ::ActiveSupport::Concern
10
-
11
- class_methods do
12
-
13
- # Override Spyke's request method to keep a thread-local copy of the
14
- # last request's metadata, so that we can access things like script
15
- # execution results after a save, etc.
16
-
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
- end
36
- end
37
- end
@@ -1,212 +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
- module Orm
10
- extend ::ActiveSupport::Concern
11
-
12
- included do
13
- # Allow overriding FM's default limit (by default it's 100)
14
- class_attribute :default_limit, instance_accessor: false, instance_predicate: false
15
-
16
- class_attribute :default_sort, instance_accessor: false, instance_predicate: false
17
-
18
- # Whether to raise an FmRest::APIError::NoMatchingRecordsError when a
19
- # _find request has no results
20
- class_attribute :raise_on_no_matching_records, instance_accessor: false, instance_predicate: false
21
- end
22
-
23
- class_methods do
24
- # Methods delegated to FmRest::Spyke::Relation
25
- delegate :limit, :offset, :sort, :order, :query, :omit, :portal,
26
- :portals, :includes, :with_all_portals, :without_portals,
27
- :script, :find_one, :first, :any, :find_some,
28
- :find_in_batches, :find_each, to: :all
29
-
30
- def all
31
- # Use FmRest's Relation instead of Spyke's vanilla one
32
- current_scope || Relation.new(self, uri: uri)
33
- end
34
-
35
- # Extended fetch to allow properly setting limit, offset and other
36
- # options, as well as using the appropriate HTTP method/URL depending
37
- # on whether there's a query present in the current scope, e.g.:
38
- #
39
- # Person.query(first_name: "Stefan").fetch # POST .../_find
40
- #
41
- def fetch
42
- if current_scope.has_query?
43
- scope = extend_scope_with_fm_params(current_scope, prefixed: false)
44
- scope = scope.where(query: scope.query_params)
45
- scope = scope.with(FmRest::V1::find_path(layout))
46
- else
47
- scope = extend_scope_with_fm_params(current_scope, prefixed: true)
48
- end
49
-
50
- previous, self.current_scope = current_scope, scope
51
-
52
- # The DAPI returns a 401 "No records match the request" error when
53
- # nothing matches a _find request, so we need to catch it in order
54
- # to provide sane behavior (i.e. return an empty resultset)
55
- begin
56
- current_scope.has_query? ? scoped_request(:post) : super
57
- rescue FmRest::APIError::NoMatchingRecordsError => e
58
- raise e if raise_on_no_matching_records
59
- ::Spyke::Result.new({})
60
- end
61
- ensure
62
- self.current_scope = previous
63
- end
64
-
65
- # API-error-raising version of #create
66
- #
67
- def create!(attributes = {})
68
- new(attributes).tap(&:save!)
69
- end
70
-
71
- def execute_script(script_name, param: nil)
72
- params = {}
73
- params = {"script.param" => param} unless param.nil?
74
- request(:get, FmRest::V1::script_path(layout, script_name), params)
75
- end
76
-
77
- private
78
-
79
- def extend_scope_with_fm_params(scope, prefixed: false)
80
- prefix = prefixed ? "_" : nil
81
-
82
- where_options = {}
83
-
84
- where_options["#{prefix}limit"] = scope.limit_value if scope.limit_value
85
- where_options["#{prefix}offset"] = scope.offset_value if scope.offset_value
86
-
87
- if scope.sort_params.present?
88
- where_options["#{prefix}sort"] =
89
- prefixed ? scope.sort_params.to_json : scope.sort_params
90
- end
91
-
92
- unless scope.included_portals.nil?
93
- where_options["portal"] =
94
- prefixed ? scope.included_portals.to_json : scope.included_portals
95
- end
96
-
97
- if scope.portal_params.present?
98
- scope.portal_params.each do |portal_param, value|
99
- where_options["#{prefix}#{portal_param}"] = value
100
- end
101
- end
102
-
103
- if scope.script_params.present?
104
- where_options.merge!(scope.script_params)
105
- end
106
-
107
- scope.where(where_options)
108
- end
109
- end
110
-
111
- # Overwrite Spyke's save to provide a number of features:
112
- #
113
- # * Validations
114
- # * Data API scripts execution
115
- # * Refresh of dirty attributes
116
- #
117
- def save(options = {})
118
- callback = persisted? ? :update : :create
119
-
120
- return false unless perform_save_validations(callback, options)
121
- return false unless perform_save_persistence(callback, options)
122
-
123
- true
124
- end
125
-
126
- def save!(options = {})
127
- save(options.merge(raise_validation_errors: true))
128
- end
129
-
130
- # Overwrite Spyke's destroy to provide Data API script execution
131
- #
132
- def destroy(options = {})
133
- # For whatever reason the Data API wants the script params as query
134
- # string params for DELETE requests, making this more complicated
135
- # than it should be
136
- script_query_string = if options.has_key?(:script)
137
- "?" + Faraday::Utils.build_query(FmRest::V1.convert_script_params(options[:script]))
138
- else
139
- ""
140
- end
141
-
142
- self.attributes = delete(uri.to_s + script_query_string)
143
- end
144
-
145
- # API-error-raising version of #update
146
- #
147
- def update!(new_attributes, options = {})
148
- self.attributes = new_attributes
149
- save!(options)
150
- end
151
-
152
- def reload(options = {})
153
- scope = self.class
154
- scope = scope.script(options[:script]) if options.has_key?(:script)
155
- reloaded = scope.find(id)
156
- self.attributes = reloaded.attributes
157
- self.mod_id = reloaded.mod_id
158
- end
159
-
160
- # ActiveModel 5+ implements this method, so we only needed if we're in
161
- # the older AM4
162
- if ActiveModel::VERSION::MAJOR == 4
163
- def validate!(context = nil)
164
- valid?(context) || raise_validation_error
165
- end
166
- end
167
-
168
- private
169
-
170
- def perform_save_validations(context, options)
171
- return true if options[:validate] == false
172
- options[:raise_validation_errors] ? validate!(context) : validate(context)
173
- end
174
-
175
- def perform_save_persistence(callback, options)
176
- run_callbacks :save do
177
- run_callbacks(callback) do
178
-
179
- begin
180
- send self.class.method_for(callback), build_params_for_save(options)
181
-
182
- rescue APIError::ValidationError => e
183
- if options[:raise_validation_errors]
184
- raise e
185
- else
186
- return false
187
- end
188
- end
189
-
190
- end
191
- end
192
-
193
- true
194
- end
195
-
196
- def build_params_for_save(options)
197
- to_params.tap do |params|
198
- if options.has_key?(:script)
199
- params.merge!(FmRest::V1.convert_script_params(options[:script]))
200
- end
201
- end
202
- end
203
-
204
- # Overwrite ActiveModel's raise_validation_error to use our own class
205
- #
206
- def raise_validation_error # :doc:
207
- raise(ValidationError.new(self))
208
- end
209
- end
210
- end
211
- end
212
- end