fmrest 0.11.0 → 0.14.0

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