fmrest-spyke 0.17.0 → 0.18.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d408eaa7cf8d45fc4df2b4377dedb7a3e97a5c60046639361c4d8e46b9ebd2ef
4
- data.tar.gz: cc33f07b7d465790adafe46e66f730735bdf2852326e287032b0cb319cdc2802
3
+ metadata.gz: cede78f266ad81e49d80fcd0cbd423a824ebf630fafe562fc44c01544714c8e0
4
+ data.tar.gz: 5d290aa99e21e9b0c4f538ff6b17078a7de9da1cf34314bf44f9a6e8de988c05
5
5
  SHA512:
6
- metadata.gz: 58bb740c8fa723749140a40992145a030ac62cf4d9568e669e49926a579af464c7930c86039b5d09afbd8b3dae9fc5106f4f705eb4a7c96b19b68096f37b7b86
7
- data.tar.gz: 5797a3ae31a82cbb18b96993084ed178eca3959678ae49ca74803b2e798aab5a05316aa39471270aaaa27b1dac0b7607c5e0fdc4045cec25cce797b46a6f81c3
6
+ metadata.gz: e0b7cd1662a463f3a3a5cc938e9135f668cd137343b1256c128358efa18eb53597da2e96ac0c30616704401d7a1fdd4284bb22f8ec0d432a9221d73c9f856990
7
+ data.tar.gz: ced9d235187d687d72b799fcc1d2b350ea7e8bb8440e7dece6cc9ff98170dc3aaab9c21dd4f762bfde725b17950a3ae568487bc5a2350c201fe32b9d6a8c37a6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## Changelog
2
2
 
3
+ ### 0.18.0
4
+
5
+ * Better support for portals with mismatching field qualifiers
6
+ * Better ergonomics for script execution, improved documentation
7
+ * Defining an attribute on a model that would collide with an existing method
8
+ now raises an error
9
+ * Cleared Faraday deprecation messages on authentication methods
10
+ * Added fmrest-ruby/VERSION to User-Agent headers
11
+
12
+ ### 0.17.1
13
+
14
+ * Fixed crash when `fmid_token` is set but `username` isn't
15
+
3
16
  ### 0.17.0
4
17
 
5
18
  * Added support for Claris ID token login
data/README.md CHANGED
@@ -543,6 +543,10 @@ class LoggyBee < FmRest::Layout
543
543
  end
544
544
  ```
545
545
 
546
+ ## Gotchas
547
+
548
+ Read about unexpected scenarios in the [gotchas doc](docs/Gotchas.md).
549
+
546
550
  ## API implementation completeness table
547
551
 
548
552
  FM Data API reference: https://fmhelp.filemaker.com/docs/18/en/dataapi/
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fmrest/spyke/portal_builder"
3
4
  require "fmrest/spyke/portal"
4
5
 
5
6
  module FmRest
@@ -13,6 +14,8 @@ module FmRest
13
14
  included do
14
15
  # Keep track of portal options by their FM keys as we could need it
15
16
  # to parse the portalData JSON in SpykeFormatter
17
+ #
18
+ # TODO: Replace this with options in PortalBuilder
16
19
  class_attribute :portal_options, instance_accessor: false, instance_predicate: false
17
20
 
18
21
  # class_attribute supports a :default option since ActiveSupport 5.2,
@@ -40,11 +43,13 @@ module FmRest
40
43
  # end
41
44
  #
42
45
  def has_portal(name, options = {})
43
- create_association(name, Portal, options)
46
+ # This is analogous to Spyke's create_association method, but using
47
+ # our custom builder instead
48
+ self.associations = associations.merge(name => PortalBuilder.new(self, name, Portal, options))
44
49
 
45
50
  # Store options for SpykeFormatter to use if needed
46
51
  portal_key = options[:portal_key] || name
47
- self.portal_options = portal_options.merge(portal_key.to_s => options.dup.merge(name: name.to_s)).freeze
52
+ self.portal_options = portal_options.merge(portal_key.to_s => options.dup.merge(name: name.to_s).freeze).freeze
48
53
 
49
54
  define_method "#{name.to_s.singularize}_ids" do
50
55
  association(name).map(&:id)
@@ -94,11 +94,17 @@ module FmRest
94
94
  end
95
95
 
96
96
  def _fmrest_define_attribute(from, to)
97
+ if existing_method = ((method_defined?(from) || private_method_defined?(from)) && from) ||
98
+ ((method_defined?("#{from}=") || private_method_defined?("#{from}=")) && "#{from}=")
99
+
100
+ raise ArgumentError, "You tried to define an attribute named `#{from}' on `#{name}', but this will generate a instance method `#{existing_method}', which is already defined by FmRest::Layout."
101
+ end
102
+
97
103
  # We use a setter here instead of injecting the hash key/value pair
98
104
  # directly with #[]= so that we don't change the mapped_attributes
99
105
  # hash on the parent class. The resulting hash is frozen for the
100
106
  # same reason.
101
- self.mapped_attributes = mapped_attributes.merge(from => to).freeze
107
+ self.mapped_attributes = mapped_attributes.merge(from => to.to_s).freeze
102
108
 
103
109
  _fmrest_attribute_methods_container.module_eval do
104
110
  define_method(from) do
@@ -6,8 +6,6 @@ module FmRest
6
6
  module GlobalFields
7
7
  extend ::ActiveSupport::Concern
8
8
 
9
- FULLY_QUALIFIED_FIELD_NAME_MATCHER = /\A[^:]+::[^:]+\Z/.freeze
10
-
11
9
  class_methods do
12
10
  def set_globals(values_hash)
13
11
  connection.patch(FmRest::V1.globals_path, {
@@ -26,7 +24,7 @@ module FmRest
26
24
  next
27
25
  end
28
26
 
29
- unless FULLY_QUALIFIED_FIELD_NAME_MATCHER === k.to_s
27
+ unless V1.is_fully_qualified?(k.to_s)
30
28
  raise ArgumentError, "global fields must be given in fully qualified format (table name::field name)"
31
29
  end
32
30
 
@@ -78,18 +78,6 @@ module FmRest
78
78
  new(attributes).tap(&:save!)
79
79
  end
80
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
81
  private
94
82
 
95
83
  def extend_scope_with_fm_params(scope, prefixed: false)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FmRest
4
+ module Spyke
5
+ module Model
6
+ # This module adds and extends various ORM features in Spyke models,
7
+ # including custom query methods, remote script execution and
8
+ # exception-raising persistence methods.
9
+ #
10
+ module ScriptExecution
11
+ extend ::ActiveSupport::Concern
12
+
13
+ class_methods do
14
+ # Requests execution of a FileMaker script, returning its result
15
+ # object.
16
+ #
17
+ # @example
18
+ # response = MyLayout.execute("Uppercasing Script", "hello")
19
+ #
20
+ # response.result # => "HELLO"
21
+ # response.error # => "0"
22
+ # response.success? # => true
23
+ #
24
+ # @param script_name [String] the name of the FileMaker script to
25
+ # execute
26
+ # @param param [String] an optional paramater for the script
27
+ # @return [FmRest::Spyke::ScriptResult] the script result object
28
+ # containing the return value and error code
29
+ #
30
+ def execute(script_name, param = nil)
31
+ # Allow keyword argument format for compatibility with execute_script
32
+ if param.respond_to?(:has_key?) && param.has_key?(:param)
33
+ param = param[:param]
34
+ end
35
+
36
+ response = execute_script(script_name, param: param)
37
+ response.metadata.script.after
38
+ end
39
+
40
+ # Requests execution of a FileMaker script, returning the entire
41
+ # response object.
42
+ #
43
+ # The execution results will be in `response.metadata.script.after`
44
+ #
45
+ # In general you'd want to use the simpler `.execute` instead of this
46
+ # method, as it provides more direct access to the script results.
47
+ #
48
+ # @example
49
+ # response = MyLayout.execute_script("My Script", param: "hello")
50
+ #
51
+ # @param script_name [String] the name of the FileMaker script to
52
+ # execute
53
+ # @param param [String] an optional paramater for the script
54
+ # @return the complete response object
55
+ #
56
+ def execute_script(script_name, param: nil)
57
+ params = param.nil? ? {} : {"script.param" => param}
58
+ request(:get, FmRest::V1::script_path(layout, script_name), params)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -26,7 +26,7 @@ module FmRest
26
26
  def serialize_for_portal(portal)
27
27
  params =
28
28
  changed_params.except(:__record_id).transform_keys do |key|
29
- "#{portal.attribute_prefix}::#{key}"
29
+ V1.is_fully_qualified?(key) ? key : "#{portal.attribute_prefix}::#{key}"
30
30
  end
31
31
 
32
32
  params[:recordId] = __record_id.to_s if __record_id
@@ -11,6 +11,7 @@ require "fmrest/spyke/model/container_fields"
11
11
  require "fmrest/spyke/model/global_fields"
12
12
  require "fmrest/spyke/model/http"
13
13
  require "fmrest/spyke/model/auth"
14
+ require "fmrest/spyke/model/script_execution"
14
15
 
15
16
  module FmRest
16
17
  module Spyke
@@ -28,6 +29,7 @@ module FmRest
28
29
  include GlobalFields
29
30
  include Http
30
31
  include Auth
32
+ include ScriptExecution
31
33
 
32
34
  autoload :Rescuable, "fmrest/spyke/model/rescuable"
33
35
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FmRest
4
+ module Spyke
5
+ class PortalBuilder < ::Spyke::Associations::Builder
6
+ attr_reader :options
7
+
8
+ def klass
9
+ begin
10
+ super
11
+ rescue NameError => e
12
+ ::FmRest::Layout
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -6,7 +6,7 @@ require "ostruct"
6
6
  module FmRest
7
7
  module Spyke
8
8
  # Metadata class to be passed to Spyke::Collection#metadata
9
- class Metadata < Struct.new(:messages, :script, :data_info)
9
+ Metadata = Struct.new(:messages, :script, :data_info) do
10
10
  alias_method :scripts, :script
11
11
  end
12
12
 
@@ -16,6 +16,11 @@ module FmRest
16
16
  def returned_count; returnedCount; end
17
17
  end
18
18
 
19
+ ScriptResult = Struct.new(:result, :error) do
20
+ def success?; error == "0"; end
21
+ def error?; !success?; end
22
+ end
23
+
19
24
  # Response Faraday middleware for converting FM API's response JSON into
20
25
  # Spyke's expected format
21
26
  class SpykeFormatter < ::Faraday::Response::Middleware
@@ -119,17 +124,18 @@ module FmRest
119
124
 
120
125
  [:prerequest, :presort].each do |s|
121
126
  if json[:response][:"scriptError.#{s}"]
122
- results[s] = OpenStruct.new(
123
- result: json[:response][:"scriptResult.#{s}"],
124
- error: json[:response][:"scriptError.#{s}"]
127
+ results[s] = ScriptResult.new(
128
+ json[:response][:"scriptResult.#{s}"],
129
+ json[:response][:"scriptError.#{s}"]
125
130
  ).freeze
126
131
  end
127
132
  end
128
133
 
134
+ # after/default script
129
135
  if json[:response][:scriptError]
130
- results[:after] = OpenStruct.new(
131
- result: json[:response][:scriptResult],
132
- error: json[:response][:scriptError]
136
+ results[:after] = ScriptResult.new(
137
+ json[:response][:scriptResult],
138
+ json[:response][:scriptError]
133
139
  ).freeze
134
140
  end
135
141
 
@@ -195,8 +201,8 @@ module FmRest
195
201
  out
196
202
  end
197
203
 
198
- # Extracts `recordId` and strips the `"PortalName::"` field prefix for each
199
- # portal
204
+ # Extracts `recordId` and strips the `"tableName::"` field qualifier for
205
+ # each portal
200
206
  #
201
207
  # Sample `json_portal_data`:
202
208
  #
@@ -210,19 +216,33 @@ module FmRest
210
216
  # @return [Hash] the portal data in Spyke format
211
217
  def prepare_portal_data(json_portal_data)
212
218
  json_portal_data.each_with_object({}) do |(portal_name, portal_records), out|
219
+
213
220
  portal_options = @model.portal_options[portal_name.to_s] || {}
221
+ portal_builder = portal_options[:name] && @model.associations[portal_options[:name].to_sym]
222
+ portal_class = portal_builder && portal_builder.klass
223
+ portal_attributes = (portal_class && portal_class.mapped_attributes.values) || []
214
224
 
215
225
  out[portal_name] =
216
226
  portal_records.map do |portal_fields|
217
227
  attributes = { __record_id: portal_fields[:recordId] }
218
228
  attributes[:__mod_id] = portal_fields[:modId] if portal_fields[:modId]
219
229
 
220
- prefix = portal_options[:attribute_prefix] || portal_name
221
- prefix_matcher = /\A#{prefix}::/
230
+ qualifier = portal_options[:attribute_prefix] || portal_name
231
+ qualifier_matcher = /\A#{qualifier}::/
222
232
 
223
233
  portal_fields.each do |k, v|
224
234
  next if :recordId == k || :modId == k
225
- attributes[k.to_s.gsub(prefix_matcher, "").to_sym] = v
235
+
236
+ stripped_field_name = k.to_s.gsub(qualifier_matcher, "")
237
+
238
+ # Only use the non-qualified attribute name if it was defined
239
+ # that way on the portal model, otherwise default to the fully
240
+ # qualified name
241
+ if portal_attributes.include?(stripped_field_name)
242
+ attributes[stripped_field_name.to_sym] = v
243
+ else
244
+ attributes[k.to_sym] = v
245
+ end
226
246
  end
227
247
 
228
248
  attributes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fmrest-spyke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.18.0.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pedro Carbajal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-05 00:00:00.000000000 Z
11
+ date: 2021-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fmrest-core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.17.0
19
+ version: 0.18.0.rc3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.17.0
26
+ version: 0.18.0.rc3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: spyke
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -65,9 +65,11 @@ files:
65
65
  - lib/fmrest/spyke/model/orm.rb
66
66
  - lib/fmrest/spyke/model/record_id.rb
67
67
  - lib/fmrest/spyke/model/rescuable.rb
68
+ - lib/fmrest/spyke/model/script_execution.rb
68
69
  - lib/fmrest/spyke/model/serialization.rb
69
70
  - lib/fmrest/spyke/model/uri.rb
70
71
  - lib/fmrest/spyke/portal.rb
72
+ - lib/fmrest/spyke/portal_builder.rb
71
73
  - lib/fmrest/spyke/relation.rb
72
74
  - lib/fmrest/spyke/spyke_formatter.rb
73
75
  - lib/fmrest/spyke/validation_error.rb
@@ -86,9 +88,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
88
  version: '0'
87
89
  required_rubygems_version: !ruby/object:Gem::Requirement
88
90
  requirements:
89
- - - ">="
91
+ - - ">"
90
92
  - !ruby/object:Gem::Version
91
- version: '0'
93
+ version: 1.3.1
92
94
  requirements: []
93
95
  rubygems_version: 3.2.3
94
96
  signing_key: