fmrest 0.10.0 → 0.13.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 +38 -0
- data/README.md +194 -763
- metadata +70 -97
- 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 -29
- data/lib/fmrest/errors.rb +0 -28
- 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 -35
- data/lib/fmrest/spyke/model/connection.rb +0 -74
- 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 -6
- 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 -21
- data/lib/fmrest/v1/connection.rb +0 -89
- 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 -57
- data/lib/fmrest/v1/token_session.rb +0 -142
- 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 -95
- data/lib/fmrest/version.rb +0 -5
@@ -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
|
@@ -1,91 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module FmRest
|
4
|
-
module Spyke
|
5
|
-
module Model
|
6
|
-
module Serialization
|
7
|
-
FM_DATE_FORMAT = "%m/%d/%Y"
|
8
|
-
FM_DATETIME_FORMAT = "#{FM_DATE_FORMAT} %H:%M:%S"
|
9
|
-
|
10
|
-
# Override Spyke's to_params to return FM Data API's expected JSON
|
11
|
-
# format, and including only modified fields
|
12
|
-
#
|
13
|
-
def to_params
|
14
|
-
params = {
|
15
|
-
fieldData: serialize_values!(changed_params_not_embedded_in_url)
|
16
|
-
}
|
17
|
-
|
18
|
-
params[:modId] = mod_id if mod_id
|
19
|
-
|
20
|
-
portal_data = serialize_portals
|
21
|
-
params[:portalData] = portal_data unless portal_data.empty?
|
22
|
-
|
23
|
-
params
|
24
|
-
end
|
25
|
-
|
26
|
-
protected
|
27
|
-
|
28
|
-
def serialize_for_portal(portal)
|
29
|
-
params =
|
30
|
-
changed_params.except(:id).transform_keys do |key|
|
31
|
-
"#{portal.attribute_prefix}::#{key}"
|
32
|
-
end
|
33
|
-
|
34
|
-
params[:recordId] = id if id
|
35
|
-
params[:modId] = mod_id if mod_id
|
36
|
-
|
37
|
-
serialize_values!(params)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def serialize_portals
|
43
|
-
portal_data = {}
|
44
|
-
|
45
|
-
portals.each do |portal|
|
46
|
-
portal.each do |portal_record|
|
47
|
-
next unless portal_record.changed?
|
48
|
-
portal_params = portal_data[portal.portal_key] ||= []
|
49
|
-
portal_params << portal_record.serialize_for_portal(portal)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
portal_data
|
54
|
-
end
|
55
|
-
|
56
|
-
def changed_params_not_embedded_in_url
|
57
|
-
params_not_embedded_in_url.slice(*mapped_changed)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Modifies the given hash in-place encoding non-string values (e.g.
|
61
|
-
# dates) to their string representation when appropriate.
|
62
|
-
#
|
63
|
-
def serialize_values!(params)
|
64
|
-
params.transform_values! do |value|
|
65
|
-
case value
|
66
|
-
when DateTime, Time, FmRest::StringDateTime
|
67
|
-
convert_datetime_timezone(value.to_datetime).strftime(FM_DATETIME_FORMAT)
|
68
|
-
when Date, FmRest::StringDate
|
69
|
-
value.strftime(FM_DATE_FORMAT)
|
70
|
-
else
|
71
|
-
value
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
params
|
76
|
-
end
|
77
|
-
|
78
|
-
def convert_datetime_timezone(dt)
|
79
|
-
case fmrest_config.fetch(:timezone, nil)
|
80
|
-
when :utc, "utc"
|
81
|
-
dt.new_offset(0)
|
82
|
-
when :local, "local"
|
83
|
-
dt.new_offset(FmRest::V1.local_offset_for_datetime(dt))
|
84
|
-
when nil
|
85
|
-
dt
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module FmRest
|
4
|
-
module Spyke
|
5
|
-
module Model
|
6
|
-
module Uri
|
7
|
-
extend ::ActiveSupport::Concern
|
8
|
-
|
9
|
-
class_methods do
|
10
|
-
# Accessor for FM layout (helps with building the URI)
|
11
|
-
#
|
12
|
-
def layout(layout = nil)
|
13
|
-
@layout = layout if layout
|
14
|
-
@layout ||= model_name.name
|
15
|
-
end
|
16
|
-
|
17
|
-
# Extend uri acccessor to default to FM Data schema
|
18
|
-
#
|
19
|
-
def uri(uri_template = nil)
|
20
|
-
if @uri.nil? && uri_template.nil?
|
21
|
-
return FmRest::V1.record_path(layout) + "(/:id)"
|
22
|
-
end
|
23
|
-
|
24
|
-
super
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
data/lib/fmrest/spyke/portal.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module FmRest
|
4
|
-
module Spyke
|
5
|
-
# Extend Spyke's HasMany association with custom options
|
6
|
-
#
|
7
|
-
class Portal < ::Spyke::Associations::HasMany
|
8
|
-
def initialize(*args)
|
9
|
-
super
|
10
|
-
|
11
|
-
# Portals are always embedded, so no special URI
|
12
|
-
@options[:uri] = ""
|
13
|
-
end
|
14
|
-
|
15
|
-
def portal_key
|
16
|
-
return @options[:portal_key] if @options[:portal_key]
|
17
|
-
name
|
18
|
-
end
|
19
|
-
|
20
|
-
def attribute_prefix
|
21
|
-
@options[:attribute_prefix] || portal_key
|
22
|
-
end
|
23
|
-
|
24
|
-
def parent_changes_applied
|
25
|
-
each do |record|
|
26
|
-
record.changes_applied
|
27
|
-
# Saving portal data doesn't provide new modIds for the
|
28
|
-
# portal records, so we clear them instead. We can still save
|
29
|
-
# portal data without a mod_id (it's optional in FM Data API)
|
30
|
-
record.mod_id = nil
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
# Spyke::Associations::HasMany#initialize calls primary_key to build the
|
37
|
-
# default URI, which causes a NameError, so this is here just to prevent
|
38
|
-
# that. We don't care what it returns as we override the URI with nil
|
39
|
-
# anyway
|
40
|
-
def primary_key; end
|
41
|
-
|
42
|
-
# Make sure the association doesn't try to fetch records through URI
|
43
|
-
def uri; nil; end
|
44
|
-
|
45
|
-
def embedded_data
|
46
|
-
parent.attributes[portal_key]
|
47
|
-
end
|
48
|
-
|
49
|
-
def add_to_parent(record)
|
50
|
-
find_some << record
|
51
|
-
record
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|