fmrest 0.12.0 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +3 -0
- data/CHANGELOG.md +31 -0
- data/README.md +252 -851
- metadata +71 -108
- data/.gitignore +0 -26
- data/.rspec +0 -3
- data/.travis.yml +0 -5
- data/Gemfile +0 -3
- data/Rakefile +0 -6
- data/UPGRADING +0 -15
- data/fmrest.gemspec +0 -49
- data/lib/fmrest.rb +0 -36
- 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 -9
- data/lib/fmrest/spyke/container_field.rb +0 -59
- data/lib/fmrest/spyke/model.rb +0 -33
- data/lib/fmrest/spyke/model/associations.rb +0 -86
- data/lib/fmrest/spyke/model/attributes.rb +0 -163
- data/lib/fmrest/spyke/model/auth.rb +0 -43
- data/lib/fmrest/spyke/model/connection.rb +0 -163
- data/lib/fmrest/spyke/model/container_fields.rb +0 -40
- data/lib/fmrest/spyke/model/global_fields.rb +0 -40
- data/lib/fmrest/spyke/model/http.rb +0 -77
- data/lib/fmrest/spyke/model/orm.rb +0 -256
- data/lib/fmrest/spyke/model/record_id.rb +0 -78
- data/lib/fmrest/spyke/model/serialization.rb +0 -99
- data/lib/fmrest/spyke/model/uri.rb +0 -29
- 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/null.rb +0 -20
- 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 -57
- data/lib/fmrest/v1/token_session.rb +0 -133
- 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,163 +0,0 @@
|
|
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
|
@@ -1,40 +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
|
-
# 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
|
-
|
@@ -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,77 +0,0 @@
|
|
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
|
@@ -1,256 +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
|
-
# 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
|