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.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/CHANGELOG.md +32 -0
- data/README.md +228 -844
- metadata +71 -101
- data/.github/workflows/ci.yml +0 -33
- data/.gitignore +0 -26
- data/.rspec +0 -3
- data/.travis.yml +0 -5
- data/Gemfile +0 -3
- data/Rakefile +0 -6
- data/fmrest.gemspec +0 -38
- data/lib/fmrest.rb +0 -34
- data/lib/fmrest/connection_settings.rb +0 -124
- data/lib/fmrest/errors.rb +0 -30
- data/lib/fmrest/spyke.rb +0 -21
- data/lib/fmrest/spyke/base.rb +0 -23
- data/lib/fmrest/spyke/container_field.rb +0 -59
- data/lib/fmrest/spyke/model.rb +0 -36
- data/lib/fmrest/spyke/model/associations.rb +0 -82
- data/lib/fmrest/spyke/model/attributes.rb +0 -171
- data/lib/fmrest/spyke/model/auth.rb +0 -43
- data/lib/fmrest/spyke/model/connection.rb +0 -135
- data/lib/fmrest/spyke/model/container_fields.rb +0 -25
- data/lib/fmrest/spyke/model/global_fields.rb +0 -40
- data/lib/fmrest/spyke/model/http.rb +0 -37
- data/lib/fmrest/spyke/model/orm.rb +0 -212
- data/lib/fmrest/spyke/model/serialization.rb +0 -91
- data/lib/fmrest/spyke/model/uri.rb +0 -30
- data/lib/fmrest/spyke/portal.rb +0 -55
- data/lib/fmrest/spyke/relation.rb +0 -359
- data/lib/fmrest/spyke/spyke_formatter.rb +0 -273
- data/lib/fmrest/spyke/validation_error.rb +0 -25
- data/lib/fmrest/string_date.rb +0 -220
- data/lib/fmrest/token_store.rb +0 -12
- data/lib/fmrest/token_store/active_record.rb +0 -74
- data/lib/fmrest/token_store/base.rb +0 -25
- data/lib/fmrest/token_store/memory.rb +0 -26
- data/lib/fmrest/token_store/moneta.rb +0 -41
- data/lib/fmrest/token_store/redis.rb +0 -45
- data/lib/fmrest/v1.rb +0 -23
- data/lib/fmrest/v1/auth.rb +0 -30
- data/lib/fmrest/v1/connection.rb +0 -115
- data/lib/fmrest/v1/container_fields.rb +0 -114
- data/lib/fmrest/v1/dates.rb +0 -81
- data/lib/fmrest/v1/paths.rb +0 -47
- data/lib/fmrest/v1/raise_errors.rb +0 -59
- data/lib/fmrest/v1/token_session.rb +0 -134
- data/lib/fmrest/v1/token_store/active_record.rb +0 -13
- data/lib/fmrest/v1/token_store/memory.rb +0 -13
- data/lib/fmrest/v1/type_coercer.rb +0 -192
- data/lib/fmrest/v1/utils.rb +0 -94
- 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
|