fmrest 0.11.1 → 0.12.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/CHANGELOG.md +6 -0
- data/UPGRADING +15 -0
- data/fmrest.gemspec +14 -3
- data/lib/fmrest/spyke/base.rb +0 -14
- data/lib/fmrest/spyke/container_field.rb +2 -2
- data/lib/fmrest/spyke/model.rb +3 -6
- data/lib/fmrest/spyke/model/associations.rb +15 -11
- data/lib/fmrest/spyke/model/attributes.rb +21 -29
- data/lib/fmrest/spyke/model/connection.rb +40 -12
- data/lib/fmrest/spyke/model/container_fields.rb +15 -0
- data/lib/fmrest/spyke/model/http.rb +42 -2
- data/lib/fmrest/spyke/model/orm.rb +61 -17
- data/lib/fmrest/spyke/model/record_id.rb +78 -0
- data/lib/fmrest/spyke/model/serialization.rb +2 -2
- data/lib/fmrest/spyke/model/uri.rb +3 -4
- data/lib/fmrest/spyke/spyke_formatter.rb +5 -5
- data/lib/fmrest/token_store/null.rb +20 -0
- data/lib/fmrest/version.rb +1 -1
- metadata +28 -10
- data/.github/workflows/ci.yml +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 725c8356fc5933f0484f7d04a4458ecc85c638838c8d9869d039a6e09b0b15f6
|
4
|
+
data.tar.gz: 2a8047594d93d13b0f6d9981159c16b1afdcaa19713c054391fc08ca1fa68633
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a41a3e747bf6ccfe0de7e5541b0db35d30d9723b7a0eeb895fbe9e21f4c2c7e6f08ccd3012c1e6f4edaa5403966e2a7563b74490327c721990fbdd4ab79608a
|
7
|
+
data.tar.gz: 16fb2868c102905a58632b1183581dcf4617d4e6c9f7529abad12916b78b3c692d21a469eaf7b23101d53ff55e8c9ccbfc1167556cc09855516647ce00565ea8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## Changelog
|
2
2
|
|
3
|
+
### 0.12.0
|
4
|
+
|
5
|
+
* Rename `FmRest::Spyke::Base#id=` to `FmRest::Spyke::Base#__record_id=` to
|
6
|
+
prevent clobbering of FileMaker layout-defined fields
|
7
|
+
* Better yard documentation
|
8
|
+
|
3
9
|
### 0.11.1
|
4
10
|
|
5
11
|
* Fix a couple crashes due to missing constants
|
data/UPGRADING
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
=======================================
|
2
|
+
Notes on upgrading from fmrest < 0.12
|
3
|
+
=======================================
|
4
|
+
|
5
|
+
There's a small breaking change in fmrest 0.12 that will most likely not affect
|
6
|
+
you, but you may want to be aware of:
|
7
|
+
|
8
|
+
Previous to this version the record ID on an FmRest::Spyke::Base instance could
|
9
|
+
be set with `id=`. This caused problems in cases where a FileMaker layout had a
|
10
|
+
field named `id`, so `id=` got renamed to `__record_id=`. Setting the record ID
|
11
|
+
by hand however isn't something useful or that should be done at all, so it's
|
12
|
+
very unlikely that this change will affect your existing code at all.
|
13
|
+
|
14
|
+
Thanks for using fmrest-ruby!
|
15
|
+
|
data/fmrest.gemspec
CHANGED
@@ -14,23 +14,34 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
-
f.match(%r{^(test|spec|features|bin)/})
|
17
|
+
f.match(%r{^(\.github|test|spec|features|bin)/})
|
18
18
|
end
|
19
19
|
spec.bindir = "exe"
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
+
if File.exist?('UPGRADING')
|
24
|
+
spec.post_install_message = File.read("UPGRADING")
|
25
|
+
end
|
26
|
+
|
23
27
|
spec.add_dependency 'faraday', '>= 0.9.0', '< 2.0'
|
24
28
|
spec.add_dependency 'faraday_middleware', '>= 0.9.1', '< 2.0'
|
25
29
|
|
26
30
|
spec.add_development_dependency "bundler"
|
27
31
|
spec.add_development_dependency "rake"
|
28
32
|
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
-
spec.add_development_dependency "spyke"
|
33
|
+
spec.add_development_dependency "spyke", ">= 5.3.3"
|
30
34
|
spec.add_development_dependency "webmock"
|
31
35
|
spec.add_development_dependency "pry-byebug"
|
32
36
|
spec.add_development_dependency "activerecord", ENV["ACTIVE_RECORD_VERSION"]
|
33
|
-
|
37
|
+
|
38
|
+
sqlite3_version = if (4.2..5.2).include?(ENV["ACTIVE_RECORD_VERSION"].to_s.gsub(/[^\d.]/, "").to_f)
|
39
|
+
"~> 1.3.0"
|
40
|
+
else
|
41
|
+
"~> 1.4.0"
|
42
|
+
end
|
43
|
+
|
44
|
+
spec.add_development_dependency "sqlite3", sqlite3_version
|
34
45
|
spec.add_development_dependency "mock_redis"
|
35
46
|
spec.add_development_dependency "moneta"
|
36
47
|
spec.add_development_dependency "yard"
|
data/lib/fmrest/spyke/base.rb
CHANGED
@@ -5,19 +5,5 @@ module FmRest
|
|
5
5
|
class Base < ::Spyke::Base
|
6
6
|
include FmRest::Spyke::Model
|
7
7
|
end
|
8
|
-
|
9
|
-
class << self
|
10
|
-
def Base(config = nil)
|
11
|
-
warn "[DEPRECATION] Inheriting from `FmRest::Spyke::Base(config)` is deprecated and will be removed, inherit from `FmRest::Spyke::Base` (without arguments) and use `fmrest_config=` instead"
|
12
|
-
|
13
|
-
if config
|
14
|
-
return Class.new(::FmRest::Spyke::Base) do
|
15
|
-
self.fmrest_config = config
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
::FmRest::Spyke::Base
|
20
|
-
end
|
21
|
-
end
|
22
8
|
end
|
23
9
|
end
|
@@ -42,7 +42,7 @@ module FmRest
|
|
42
42
|
)
|
43
43
|
|
44
44
|
# Update mod id on record
|
45
|
-
@base.
|
45
|
+
@base.__mod_id = response.body[:data][:__mod_id]
|
46
46
|
|
47
47
|
true
|
48
48
|
end
|
@@ -52,7 +52,7 @@ module FmRest
|
|
52
52
|
# @param repetition [Integer]
|
53
53
|
# @return [String] the path for uploading a file to the container
|
54
54
|
def upload_path(repetition)
|
55
|
-
FmRest::V1.container_field_path(@base.class.layout, @base.
|
55
|
+
FmRest::V1.container_field_path(@base.class.layout, @base.__record_id, name, repetition)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
data/lib/fmrest/spyke/model.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "fmrest/spyke/model/connection"
|
4
4
|
require "fmrest/spyke/model/uri"
|
5
|
+
require "fmrest/spyke/model/record_id"
|
5
6
|
require "fmrest/spyke/model/attributes"
|
6
7
|
require "fmrest/spyke/model/serialization"
|
7
8
|
require "fmrest/spyke/model/associations"
|
@@ -17,7 +18,8 @@ module FmRest
|
|
17
18
|
extend ::ActiveSupport::Concern
|
18
19
|
|
19
20
|
include Connection
|
20
|
-
include
|
21
|
+
include URI
|
22
|
+
include RecordID
|
21
23
|
include Attributes
|
22
24
|
include Serialization
|
23
25
|
include Associations
|
@@ -26,11 +28,6 @@ module FmRest
|
|
26
28
|
include GlobalFields
|
27
29
|
include Http
|
28
30
|
include Auth
|
29
|
-
|
30
|
-
included do
|
31
|
-
# @return [Integer] the record's modId
|
32
|
-
attr_accessor :mod_id
|
33
|
-
end
|
34
31
|
end
|
35
32
|
end
|
36
33
|
end
|
@@ -5,6 +5,8 @@ require "fmrest/spyke/portal"
|
|
5
5
|
module FmRest
|
6
6
|
module Spyke
|
7
7
|
module Model
|
8
|
+
# This module adds portal support to Spyke models.
|
9
|
+
#
|
8
10
|
module Associations
|
9
11
|
extend ::ActiveSupport::Concern
|
10
12
|
|
@@ -22,17 +24,18 @@ module FmRest
|
|
22
24
|
end
|
23
25
|
|
24
26
|
class_methods do
|
25
|
-
# Based on
|
27
|
+
# Based on `has_many`, but creates a special Portal association
|
26
28
|
# instead.
|
27
29
|
#
|
28
|
-
#
|
30
|
+
# @option :portal_key [String] The key used for the portal in the FM
|
31
|
+
# Data JSON portalData
|
32
|
+
# @option :attribute_prefix [String] The prefix used for portal
|
33
|
+
# attributes in the FM Data JSON
|
29
34
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# has_portal :jobs, portal_key: "JobsTable", attribute_prefix: "Job"
|
35
|
+
# @example
|
36
|
+
# class Person < FmRest::Spyke::Base
|
37
|
+
# has_portal :jobs, portal_key: "JobsTable", attribute_prefix: "Job"
|
38
|
+
# end
|
36
39
|
#
|
37
40
|
def has_portal(name, options = {})
|
38
41
|
create_association(name, Portal, options)
|
@@ -47,9 +50,8 @@ module FmRest
|
|
47
50
|
end
|
48
51
|
end
|
49
52
|
|
50
|
-
#
|
51
|
-
#
|
52
|
-
# each time.
|
53
|
+
# Spyke override -- Keep a cache of loaded portals. Spyke's default
|
54
|
+
# behavior is to reload the association each time.
|
53
55
|
#
|
54
56
|
def association(name)
|
55
57
|
@loaded_portals ||= {}
|
@@ -64,6 +66,8 @@ module FmRest
|
|
64
66
|
end
|
65
67
|
end
|
66
68
|
|
69
|
+
# Spyke override -- Add portals awareness
|
70
|
+
#
|
67
71
|
def reload(*_)
|
68
72
|
super.tap { @loaded_portals = nil }
|
69
73
|
end
|
@@ -5,6 +5,10 @@ require "fmrest/spyke/model/orm"
|
|
5
5
|
module FmRest
|
6
6
|
module Spyke
|
7
7
|
module Model
|
8
|
+
# Extends Spyke models with support for mapped attributes,
|
9
|
+
# `ActiveModel::Dirty` and forbidden attributes (e.g. Rails'
|
10
|
+
# `params.permit`).
|
11
|
+
#
|
8
12
|
module Attributes
|
9
13
|
extend ::ActiveSupport::Concern
|
10
14
|
|
@@ -20,9 +24,6 @@ module FmRest
|
|
20
24
|
# by Spyke
|
21
25
|
self.attribute_method_matchers.shift
|
22
26
|
|
23
|
-
# ActiveModel::Dirty methods for id
|
24
|
-
define_attribute_method(:id)
|
25
|
-
|
26
27
|
# Keep track of attribute mappings so we can get the FM field names
|
27
28
|
# for changed attributes
|
28
29
|
class_attribute :mapped_attributes, instance_writer: false, instance_predicate: false
|
@@ -36,10 +37,12 @@ module FmRest
|
|
36
37
|
end
|
37
38
|
|
38
39
|
class_methods do
|
40
|
+
# Spyke override
|
41
|
+
#
|
39
42
|
# Similar to Spyke::Base.attributes, but allows defining attribute
|
40
43
|
# methods that map to FM attributes with different names.
|
41
44
|
#
|
42
|
-
#
|
45
|
+
# @example
|
43
46
|
#
|
44
47
|
# class Person < Spyke::Base
|
45
48
|
# include FmRest::Spyke::Model
|
@@ -61,9 +64,10 @@ module FmRest
|
|
61
64
|
|
62
65
|
private
|
63
66
|
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
+
# Spyke override (private)
|
68
|
+
#
|
69
|
+
# Called whenever loading records from the HTTP API, so we can reset
|
70
|
+
# dirty info on freshly loaded records
|
67
71
|
#
|
68
72
|
# See: https://github.com/balvig/spyke/blob/master/lib/spyke/http.rb
|
69
73
|
#
|
@@ -88,8 +92,6 @@ module FmRest
|
|
88
92
|
end
|
89
93
|
|
90
94
|
def _fmrest_define_attribute(from, to)
|
91
|
-
raise ArgumentError, "attribute name `id' is reserved for the recordId" if from.to_s == "id"
|
92
|
-
|
93
95
|
# We use a setter here instead of injecting the hash key/value pair
|
94
96
|
# directly with #[]= so that we don't change the mapped_attributes
|
95
97
|
# hash on the parent class. The resulting hash is frozen for the
|
@@ -112,35 +114,25 @@ module FmRest
|
|
112
114
|
end
|
113
115
|
end
|
114
116
|
|
115
|
-
|
116
|
-
|
117
|
-
super
|
118
|
-
end
|
119
|
-
|
117
|
+
# Spyke override -- Adds AM::Dirty support
|
118
|
+
#
|
120
119
|
def reload(*args)
|
121
120
|
super.tap { |r| clear_changes_information }
|
122
121
|
end
|
123
122
|
|
123
|
+
# Spyke override -- Adds AM::Dirty support
|
124
|
+
#
|
124
125
|
def save(*args)
|
125
126
|
super.tap { |r| changes_applied_after_save if r }
|
126
127
|
end
|
127
128
|
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# we override the instance variable name used by Spyke to avoid issues.
|
131
|
-
#
|
132
|
-
# TODO: Submit a pull request to Spyke so this isn't needed
|
133
|
-
#
|
134
|
-
def attributes
|
135
|
-
@_spyke_attributes
|
136
|
-
end
|
137
|
-
|
138
|
-
# In addition to the comments above on `attributes`, this also adds
|
139
|
-
# support for forbidden attributes
|
129
|
+
# Spyke override -- Adds support for forbidden attributes (i.e. Rails'
|
130
|
+
# `params.permit`, etc.)
|
140
131
|
#
|
141
132
|
def attributes=(new_attributes)
|
142
|
-
@
|
143
|
-
|
133
|
+
@spyke_attributes ||= ::Spyke::Attributes.new(scope.params)
|
134
|
+
return unless new_attributes && !new_attributes.empty?
|
135
|
+
use_setters(sanitize_for_mass_assignment(new_attributes))
|
144
136
|
end
|
145
137
|
|
146
138
|
private
|
@@ -153,7 +145,7 @@ module FmRest
|
|
153
145
|
mapped_attributes.values_at(*changed)
|
154
146
|
end
|
155
147
|
|
156
|
-
# Use known mapped_attributes for inspect
|
148
|
+
# Spyke override (private) -- Use known mapped_attributes for inspect
|
157
149
|
#
|
158
150
|
def inspect_attributes
|
159
151
|
mapped_attributes.except(primary_key).map do |k, v|
|
@@ -3,6 +3,9 @@
|
|
3
3
|
module FmRest
|
4
4
|
module Spyke
|
5
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
|
+
#
|
6
9
|
module Connection
|
7
10
|
extend ActiveSupport::Concern
|
8
11
|
|
@@ -10,7 +13,7 @@ module FmRest
|
|
10
13
|
class_attribute :faraday_block, instance_accessor: false, instance_predicate: false
|
11
14
|
class << self; private :faraday_block, :faraday_block=; end
|
12
15
|
|
13
|
-
# FM Data API expects PATCH for updates (Spyke
|
16
|
+
# FM Data API expects PATCH for updates (Spyke uses PUT by default)
|
14
17
|
self.callback_methods = { create: :post, update: :patch }.freeze
|
15
18
|
end
|
16
19
|
|
@@ -23,28 +26,37 @@ module FmRest
|
|
23
26
|
FmRest.default_connection_settings
|
24
27
|
end
|
25
28
|
|
26
|
-
#
|
27
|
-
#
|
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
|
28
35
|
#
|
29
36
|
def fmrest_config=(settings)
|
30
37
|
settings = ConnectionSettings.new(settings, skip_validation: true)
|
31
38
|
|
32
|
-
|
39
|
+
singleton_class.redefine_method(:fmrest_config) do
|
33
40
|
overlay = fmrest_config_overlay
|
34
41
|
return settings.merge(overlay, skip_validation: true) if overlay
|
35
42
|
settings
|
36
43
|
end
|
37
44
|
end
|
38
45
|
|
39
|
-
# Allows
|
46
|
+
# Allows overriding some connection settings in a thread-local
|
40
47
|
# manner. Useful in the use case where you want to connect to the
|
41
48
|
# same database using different accounts (e.g. credentials provided
|
42
|
-
# by users in a web app context)
|
49
|
+
# by users in a web app context).
|
50
|
+
#
|
51
|
+
# @param (see #fmrest_config=)
|
43
52
|
#
|
44
53
|
def fmrest_config_overlay=(settings)
|
45
54
|
Thread.current[fmrest_config_overlay_key] = settings
|
46
55
|
end
|
47
56
|
|
57
|
+
# @return [FmRest::ConnectionSettings] the connection settings
|
58
|
+
# overlay if any is in use
|
59
|
+
#
|
48
60
|
def fmrest_config_overlay
|
49
61
|
Thread.current[fmrest_config_overlay_key] || begin
|
50
62
|
superclass.fmrest_config_overlay
|
@@ -53,10 +65,23 @@ module FmRest
|
|
53
65
|
end
|
54
66
|
end
|
55
67
|
|
68
|
+
# Clears the connection settings overlay.
|
69
|
+
#
|
56
70
|
def clear_fmrest_config_overlay
|
57
71
|
Thread.current[fmrest_config_overlay_key] = nil
|
58
72
|
end
|
59
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
|
+
#
|
60
85
|
def with_overlay(settings, &block)
|
61
86
|
Fiber.new do
|
62
87
|
begin
|
@@ -68,19 +93,22 @@ module FmRest
|
|
68
93
|
end.resume
|
69
94
|
end
|
70
95
|
|
96
|
+
# Spyke override -- Defaults to `fmrest_connection`
|
97
|
+
#
|
71
98
|
def connection
|
72
99
|
super || fmrest_connection
|
73
100
|
end
|
74
101
|
|
75
102
|
# Sets a block for injecting custom middleware into the Faraday
|
76
|
-
# connection.
|
103
|
+
# connection.
|
77
104
|
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
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
|
83
110
|
# end
|
111
|
+
# end
|
84
112
|
#
|
85
113
|
def faraday(&block)
|
86
114
|
self.faraday_block = block
|
@@ -5,10 +5,25 @@ require "fmrest/spyke/container_field"
|
|
5
5
|
module FmRest
|
6
6
|
module Spyke
|
7
7
|
module Model
|
8
|
+
# This module adds support for container fields.
|
9
|
+
#
|
8
10
|
module ContainerFields
|
9
11
|
extend ::ActiveSupport::Concern
|
10
12
|
|
11
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
|
+
#
|
12
27
|
def container(name, options = {})
|
13
28
|
field_name = options[:field_name] || name
|
14
29
|
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "fmrest/spyke/relation"
|
4
|
-
|
5
3
|
module FmRest
|
6
4
|
module Spyke
|
7
5
|
module Model
|
@@ -15,6 +13,8 @@ module FmRest
|
|
15
13
|
# execution results after a save, etc.
|
16
14
|
|
17
15
|
|
16
|
+
# Spyke override -- Keeps metadata in thread-local class variable.
|
17
|
+
#
|
18
18
|
def request(*args)
|
19
19
|
super.tap do |r|
|
20
20
|
Thread.current[last_request_metadata_key] = r.metadata
|
@@ -32,6 +32,46 @@ module FmRest
|
|
32
32
|
end
|
33
33
|
end
|
34
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
|
35
75
|
end
|
36
76
|
end
|
37
77
|
end
|
@@ -6,6 +6,10 @@ require "fmrest/spyke/validation_error"
|
|
6
6
|
module FmRest
|
7
7
|
module Spyke
|
8
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
|
+
#
|
9
13
|
module Orm
|
10
14
|
extend ::ActiveSupport::Concern
|
11
15
|
|
@@ -21,22 +25,25 @@ module FmRest
|
|
21
25
|
end
|
22
26
|
|
23
27
|
class_methods do
|
24
|
-
# Methods delegated to FmRest::Spyke::Relation
|
28
|
+
# Methods delegated to `FmRest::Spyke::Relation`
|
25
29
|
delegate :limit, :offset, :sort, :order, :query, :omit, :portal,
|
26
30
|
:portals, :includes, :with_all_portals, :without_portals,
|
27
31
|
:script, :find_one, :first, :any, :find_some,
|
28
32
|
:find_in_batches, :find_each, to: :all
|
29
33
|
|
34
|
+
# Spyke override -- Use FmRest's Relation instead of Spyke's vanilla
|
35
|
+
# one
|
36
|
+
#
|
30
37
|
def all
|
31
|
-
# Use FmRest's Relation instead of Spyke's vanilla one
|
32
38
|
current_scope || Relation.new(self, uri: uri)
|
33
39
|
end
|
34
40
|
|
35
|
-
#
|
41
|
+
# Spyke override -- allows properly setting limit, offset and other
|
36
42
|
# options, as well as using the appropriate HTTP method/URL depending
|
37
|
-
# on whether there's a query present in the current scope
|
43
|
+
# on whether there's a query present in the current scope.
|
38
44
|
#
|
39
|
-
#
|
45
|
+
# @example
|
46
|
+
# Person.query(first_name: "Stefan").fetch # -> POST .../_find
|
40
47
|
#
|
41
48
|
def fetch
|
42
49
|
if current_scope.has_query?
|
@@ -62,12 +69,21 @@ module FmRest
|
|
62
69
|
self.current_scope = previous
|
63
70
|
end
|
64
71
|
|
65
|
-
#
|
72
|
+
# Exception-raising version of `#create`
|
73
|
+
#
|
74
|
+
# @param attributes [Hash] the attributes to initialize the
|
75
|
+
# record with
|
66
76
|
#
|
67
77
|
def create!(attributes = {})
|
68
78
|
new(attributes).tap(&:save!)
|
69
79
|
end
|
70
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
|
+
#
|
71
87
|
def execute_script(script_name, param: nil)
|
72
88
|
params = {}
|
73
89
|
params = {"script.param" => param} unless param.nil?
|
@@ -108,12 +124,20 @@ module FmRest
|
|
108
124
|
end
|
109
125
|
end
|
110
126
|
|
111
|
-
#
|
127
|
+
# Spyke override -- Adds a number of features to original `#save`:
|
112
128
|
#
|
113
129
|
# * Validations
|
114
130
|
# * Data API scripts execution
|
115
131
|
# * Refresh of dirty attributes
|
116
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
|
+
#
|
117
141
|
def save(options = {})
|
118
142
|
callback = persisted? ? :update : :create
|
119
143
|
|
@@ -123,11 +147,34 @@ module FmRest
|
|
123
147
|
true
|
124
148
|
end
|
125
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
|
+
#
|
126
158
|
def save!(options = {})
|
127
159
|
save(options.merge(raise_validation_errors: true))
|
128
160
|
end
|
129
161
|
|
130
|
-
#
|
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
|
131
178
|
#
|
132
179
|
def destroy(options = {})
|
133
180
|
# For whatever reason the Data API wants the script params as query
|
@@ -142,22 +189,19 @@ module FmRest
|
|
142
189
|
self.attributes = delete(uri.to_s + script_query_string)
|
143
190
|
end
|
144
191
|
|
145
|
-
#
|
192
|
+
# (see #destroy)
|
193
|
+
#
|
194
|
+
# @option (see #destroy)
|
146
195
|
#
|
147
|
-
def update!(new_attributes, options = {})
|
148
|
-
self.attributes = new_attributes
|
149
|
-
save!(options)
|
150
|
-
end
|
151
|
-
|
152
196
|
def reload(options = {})
|
153
197
|
scope = self.class
|
154
198
|
scope = scope.script(options[:script]) if options.has_key?(:script)
|
155
|
-
reloaded = scope.find(
|
199
|
+
reloaded = scope.find(__record_id)
|
156
200
|
self.attributes = reloaded.attributes
|
157
|
-
self.
|
201
|
+
self.__mod_id = reloaded.mod_id
|
158
202
|
end
|
159
203
|
|
160
|
-
# ActiveModel 5+ implements this method, so we only
|
204
|
+
# ActiveModel 5+ implements this method, so we only need it if we're in
|
161
205
|
# the older AM4
|
162
206
|
if ActiveModel::VERSION::MAJOR == 4
|
163
207
|
def validate!(context = nil)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FmRest
|
4
|
+
module Spyke
|
5
|
+
module Model
|
6
|
+
# Modifies Spyke models to use `__record_id` instead of `id` as the
|
7
|
+
# "primary key" method, so that we can map a model class to a FM layout
|
8
|
+
# with a field named `id` without clobbering it.
|
9
|
+
#
|
10
|
+
# The `id` reader method still maps to the record ID for backwards
|
11
|
+
# compatibility and because Spyke hardcodes its use at various points
|
12
|
+
# through its codebase, but it can be safely overwritten (e.g. to map to
|
13
|
+
# a FM field).
|
14
|
+
#
|
15
|
+
# The recommended way to deal with a layout that maps an `id` attribute
|
16
|
+
# is to remap it in the model to something else, e.g. `unique_id`.
|
17
|
+
#
|
18
|
+
module RecordID
|
19
|
+
extend ::ActiveSupport::Concern
|
20
|
+
|
21
|
+
included do
|
22
|
+
# @return [Integer] the record's recordId
|
23
|
+
attr_accessor :__record_id
|
24
|
+
alias_method :record_id, :__record_id
|
25
|
+
alias_method :id, :__record_id
|
26
|
+
|
27
|
+
# @return [Integer] the record's modId
|
28
|
+
attr_accessor :__mod_id
|
29
|
+
alias_method :mod_id, :__mod_id
|
30
|
+
|
31
|
+
# Get rid of Spyke's id= setter method, as we'll be using __record_id=
|
32
|
+
# instead
|
33
|
+
undef_method :id=
|
34
|
+
|
35
|
+
# Tell Spyke that we want __record_id as the PK
|
36
|
+
self.primary_key = :__record_id
|
37
|
+
end
|
38
|
+
|
39
|
+
def __record_id?
|
40
|
+
__record_id.present?
|
41
|
+
end
|
42
|
+
alias_method :record_id?, :__record_id?
|
43
|
+
alias_method :persisted?, :__record_id?
|
44
|
+
|
45
|
+
# Spyke override -- Use `__record_id` instead of `id`
|
46
|
+
#
|
47
|
+
def hash
|
48
|
+
__record_id.hash
|
49
|
+
end
|
50
|
+
|
51
|
+
# Spyke override -- Renders class string with layout name and
|
52
|
+
# `record_id`.
|
53
|
+
#
|
54
|
+
# @return [String] A string representation of the class
|
55
|
+
#
|
56
|
+
def inspect
|
57
|
+
"#<#{self.class}(layout: #{self.class.layout}) record_id: #{__record_id.inspect} #{inspect_attributes}>"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Spyke override -- Use `__record_id` instead of `id`
|
61
|
+
#
|
62
|
+
# @param id [Integer] The id of the record to destroy
|
63
|
+
#
|
64
|
+
def destroy(id = nil)
|
65
|
+
new(__record_id: id).destroy
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Spyke override (private)
|
71
|
+
#
|
72
|
+
def conflicting_ids?(attributes)
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -7,8 +7,8 @@ module FmRest
|
|
7
7
|
FM_DATE_FORMAT = "%m/%d/%Y"
|
8
8
|
FM_DATETIME_FORMAT = "#{FM_DATE_FORMAT} %H:%M:%S"
|
9
9
|
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# Spyke override -- Return FM Data API's expected JSON format,
|
11
|
+
# including only modified fields.
|
12
12
|
#
|
13
13
|
def to_params
|
14
14
|
params = {
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module FmRest
|
4
4
|
module Spyke
|
5
5
|
module Model
|
6
|
-
module
|
6
|
+
module URI
|
7
7
|
extend ::ActiveSupport::Concern
|
8
8
|
|
9
9
|
class_methods do
|
@@ -14,13 +14,12 @@ module FmRest
|
|
14
14
|
@layout ||= model_name.name
|
15
15
|
end
|
16
16
|
|
17
|
-
#
|
17
|
+
# Spyke override -- Extends `uri` to default to FM Data's URI schema
|
18
18
|
#
|
19
19
|
def uri(uri_template = nil)
|
20
20
|
if @uri.nil? && uri_template.nil?
|
21
|
-
return FmRest::V1.record_path(layout) + "(
|
21
|
+
return FmRest::V1.record_path(layout) + "(/:#{primary_key})"
|
22
22
|
end
|
23
|
-
|
24
23
|
super
|
25
24
|
end
|
26
25
|
end
|
@@ -63,8 +63,8 @@ module FmRest
|
|
63
63
|
response = json[:response]
|
64
64
|
|
65
65
|
data = {}
|
66
|
-
data[:
|
67
|
-
data[:
|
66
|
+
data[:__mod_id] = response[:modId] if response[:modId]
|
67
|
+
data[:__record_id] = response[:recordId].to_i if response[:recordId]
|
68
68
|
|
69
69
|
build_base_hash(json, true).merge!(data: data)
|
70
70
|
end
|
@@ -188,7 +188,7 @@ module FmRest
|
|
188
188
|
# @param json_data [Hash]
|
189
189
|
# @return [Hash] the record data in Spyke format
|
190
190
|
def prepare_record_data(json_data)
|
191
|
-
out = {
|
191
|
+
out = { __record_id: json_data[:recordId].to_i, __mod_id: json_data[:modId] }
|
192
192
|
out.merge!(json_data[:fieldData])
|
193
193
|
out.merge!(prepare_portal_data(json_data[:portalData])) if json_data[:portalData]
|
194
194
|
out
|
@@ -213,8 +213,8 @@ module FmRest
|
|
213
213
|
|
214
214
|
out[portal_name] =
|
215
215
|
portal_records.map do |portal_fields|
|
216
|
-
attributes = {
|
217
|
-
attributes[:
|
216
|
+
attributes = { __record_id: portal_fields[:recordId].to_i }
|
217
|
+
attributes[:__mod_id] = portal_fields[:modId] if portal_fields[:modId]
|
218
218
|
|
219
219
|
prefix = portal_options[:attribute_prefix] || portal_name
|
220
220
|
prefix_matcher = /\A#{prefix}::/
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module FmRest
|
6
|
+
module TokenStore
|
7
|
+
module Null < Base
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def delete(key)
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def store(key, value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/fmrest/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fmrest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pedro Carbajal
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -98,14 +98,14 @@ dependencies:
|
|
98
98
|
requirements:
|
99
99
|
- - ">="
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version:
|
101
|
+
version: 5.3.3
|
102
102
|
type: :development
|
103
103
|
prerelease: false
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
105
105
|
requirements:
|
106
106
|
- - ">="
|
107
107
|
- !ruby/object:Gem::Version
|
108
|
-
version:
|
108
|
+
version: 5.3.3
|
109
109
|
- !ruby/object:Gem::Dependency
|
110
110
|
name: webmock
|
111
111
|
requirement: !ruby/object:Gem::Requirement
|
@@ -152,16 +152,16 @@ dependencies:
|
|
152
152
|
name: sqlite3
|
153
153
|
requirement: !ruby/object:Gem::Requirement
|
154
154
|
requirements:
|
155
|
-
- - "
|
155
|
+
- - "~>"
|
156
156
|
- !ruby/object:Gem::Version
|
157
|
-
version:
|
157
|
+
version: 1.4.0
|
158
158
|
type: :development
|
159
159
|
prerelease: false
|
160
160
|
version_requirements: !ruby/object:Gem::Requirement
|
161
161
|
requirements:
|
162
|
-
- - "
|
162
|
+
- - "~>"
|
163
163
|
- !ruby/object:Gem::Version
|
164
|
-
version:
|
164
|
+
version: 1.4.0
|
165
165
|
- !ruby/object:Gem::Dependency
|
166
166
|
name: mock_redis
|
167
167
|
requirement: !ruby/object:Gem::Requirement
|
@@ -226,7 +226,6 @@ executables: []
|
|
226
226
|
extensions: []
|
227
227
|
extra_rdoc_files: []
|
228
228
|
files:
|
229
|
-
- ".github/workflows/ci.yml"
|
230
229
|
- ".gitignore"
|
231
230
|
- ".rspec"
|
232
231
|
- ".travis.yml"
|
@@ -236,6 +235,7 @@ files:
|
|
236
235
|
- LICENSE.txt
|
237
236
|
- README.md
|
238
237
|
- Rakefile
|
238
|
+
- UPGRADING
|
239
239
|
- fmrest.gemspec
|
240
240
|
- lib/fmrest.rb
|
241
241
|
- lib/fmrest/connection_settings.rb
|
@@ -252,6 +252,7 @@ files:
|
|
252
252
|
- lib/fmrest/spyke/model/global_fields.rb
|
253
253
|
- lib/fmrest/spyke/model/http.rb
|
254
254
|
- lib/fmrest/spyke/model/orm.rb
|
255
|
+
- lib/fmrest/spyke/model/record_id.rb
|
255
256
|
- lib/fmrest/spyke/model/serialization.rb
|
256
257
|
- lib/fmrest/spyke/model/uri.rb
|
257
258
|
- lib/fmrest/spyke/portal.rb
|
@@ -264,6 +265,7 @@ files:
|
|
264
265
|
- lib/fmrest/token_store/base.rb
|
265
266
|
- lib/fmrest/token_store/memory.rb
|
266
267
|
- lib/fmrest/token_store/moneta.rb
|
268
|
+
- lib/fmrest/token_store/null.rb
|
267
269
|
- lib/fmrest/token_store/redis.rb
|
268
270
|
- lib/fmrest/v1.rb
|
269
271
|
- lib/fmrest/v1/auth.rb
|
@@ -282,7 +284,22 @@ homepage: https://github.com/beezwax/fmrest-ruby
|
|
282
284
|
licenses:
|
283
285
|
- MIT
|
284
286
|
metadata: {}
|
285
|
-
post_install_message:
|
287
|
+
post_install_message: |+
|
288
|
+
=======================================
|
289
|
+
Notes on upgrading from fmrest < 0.12
|
290
|
+
=======================================
|
291
|
+
|
292
|
+
There's a small breaking change in fmrest 0.12 that will most likely not affect
|
293
|
+
you, but you may want to be aware of:
|
294
|
+
|
295
|
+
Previous to this version the record ID on an FmRest::Spyke::Base instance could
|
296
|
+
be set with `id=`. This caused problems in cases where a FileMaker layout had a
|
297
|
+
field named `id`, so `id=` got renamed to `__record_id=`. Setting the record ID
|
298
|
+
by hand however isn't something useful or that should be done at all, so it's
|
299
|
+
very unlikely that this change will affect your existing code at all.
|
300
|
+
|
301
|
+
Thanks for using fmrest-ruby!
|
302
|
+
|
286
303
|
rdoc_options: []
|
287
304
|
require_paths:
|
288
305
|
- lib
|
@@ -302,3 +319,4 @@ signing_key:
|
|
302
319
|
specification_version: 4
|
303
320
|
summary: FileMaker Data API client using Faraday
|
304
321
|
test_files: []
|
322
|
+
...
|
data/.github/workflows/ci.yml
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# This workflow uses actions that are not certified by GitHub.
|
2
|
-
# They are provided by a third-party and are governed by
|
3
|
-
# separate terms of service, privacy policy, and support
|
4
|
-
# documentation.
|
5
|
-
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
-
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
-
|
8
|
-
name: CI
|
9
|
-
|
10
|
-
on:
|
11
|
-
push:
|
12
|
-
branches: [ master ]
|
13
|
-
pull_request:
|
14
|
-
branches: [ master ]
|
15
|
-
|
16
|
-
jobs:
|
17
|
-
test:
|
18
|
-
|
19
|
-
runs-on: ubuntu-latest
|
20
|
-
|
21
|
-
steps:
|
22
|
-
- uses: actions/checkout@v2
|
23
|
-
- name: Set up Ruby
|
24
|
-
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
25
|
-
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
26
|
-
# uses: ruby/setup-ruby@v1
|
27
|
-
uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
|
28
|
-
with:
|
29
|
-
ruby-version: 2.6
|
30
|
-
- name: Install dependencies
|
31
|
-
run: bundle install
|
32
|
-
- name: Run specs
|
33
|
-
run: bundle exec rspec spec
|