fmrest-spyke 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 +7 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +127 -0
- data/LICENSE.txt +21 -0
- data/README.md +523 -0
- data/lib/fmrest-spyke.rb +3 -0
- data/lib/fmrest/spyke.rb +21 -0
- data/lib/fmrest/spyke/base.rb +9 -0
- data/lib/fmrest/spyke/container_field.rb +59 -0
- data/lib/fmrest/spyke/model.rb +33 -0
- data/lib/fmrest/spyke/model/associations.rb +166 -0
- data/lib/fmrest/spyke/model/attributes.rb +159 -0
- data/lib/fmrest/spyke/model/auth.rb +43 -0
- data/lib/fmrest/spyke/model/connection.rb +163 -0
- data/lib/fmrest/spyke/model/container_fields.rb +40 -0
- data/lib/fmrest/spyke/model/global_fields.rb +40 -0
- data/lib/fmrest/spyke/model/http.rb +77 -0
- data/lib/fmrest/spyke/model/orm.rb +256 -0
- data/lib/fmrest/spyke/model/record_id.rb +96 -0
- data/lib/fmrest/spyke/model/serialization.rb +115 -0
- data/lib/fmrest/spyke/model/uri.rb +29 -0
- data/lib/fmrest/spyke/portal.rb +65 -0
- data/lib/fmrest/spyke/relation.rb +359 -0
- data/lib/fmrest/spyke/spyke_formatter.rb +274 -0
- data/lib/fmrest/spyke/validation_error.rb +25 -0
- metadata +96 -0
@@ -0,0 +1,43 @@
|
|
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
|
@@ -0,0 +1,163 @@
|
|
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
|
@@ -0,0 +1,40 @@
|
|
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
|
+
|
@@ -0,0 +1,40 @@
|
|
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
|
@@ -0,0 +1,77 @@
|
|
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
|
@@ -0,0 +1,256 @@
|
|
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
|