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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d087824f401c6c36c5f0ac00acf9e3ee12fced43d002000533ba0b97e579d89d
4
- data.tar.gz: 2c3f0018686f48f02c6e177cc10ba0cbdecd584523776c9269023bb7192eb271
3
+ metadata.gz: 725c8356fc5933f0484f7d04a4458ecc85c638838c8d9869d039a6e09b0b15f6
4
+ data.tar.gz: 2a8047594d93d13b0f6d9981159c16b1afdcaa19713c054391fc08ca1fa68633
5
5
  SHA512:
6
- metadata.gz: 9746926229c22793428d1c0d76c7d0b373cee751c15d83389e3a426ca434773524e1f8133d65914b0301d47c7e6c22f893f14850377bb8b665f065ce377f972f
7
- data.tar.gz: a8d32edd04361bb9a4f8add3cc8ecd9e89cf1bcbcf0437cfcc3d30cba652c67b935d58091246f1657041dc0678ce0b01c3685d300b6a699fea3bb73dfb131846
6
+ metadata.gz: 7a41a3e747bf6ccfe0de7e5541b0db35d30d9723b7a0eeb895fbe9e21f4c2c7e6f08ccd3012c1e6f4edaa5403966e2a7563b74490327c721990fbdd4ab79608a
7
+ data.tar.gz: 16fb2868c102905a58632b1183581dcf4617d4e6c9f7529abad12916b78b3c692d21a469eaf7b23101d53ff55e8c9ccbfc1167556cc09855516647ce00565ea8
@@ -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
@@ -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
+
@@ -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
- spec.add_development_dependency "sqlite3", ENV["SQLITE3_VERSION"]
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"
@@ -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.mod_id = response.body[:data][:mod_id]
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.id, name, repetition)
55
+ FmRest::V1.container_field_path(@base.class.layout, @base.__record_id, name, repetition)
56
56
  end
57
57
  end
58
58
  end
@@ -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 Uri
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 +has_many+, but creates a special Portal association
27
+ # Based on `has_many`, but creates a special Portal association
26
28
  # instead.
27
29
  #
28
- # Custom options:
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
- # * <tt>:portal_key</tt> - The key used for the portal in the FM Data JSON portalData.
31
- # * <tt>:attribute_prefix</tt> - The prefix used for portal attributes in the FM Data JSON.
32
- #
33
- # Example:
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
- # Override Spyke's association reader to keep a cache of loaded
51
- # portals. Spyke's default behavior is to reload the association
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
- # Example:
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
- # Override Spyke::Base.new_or_return (private), called whenever
65
- # loading records from the HTTP API, so we can reset dirty info on
66
- # freshly loaded records
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
- def id=(value)
116
- id_will_change! unless value == id
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
- # ActiveModel::Dirty since version 5.2 assumes that if there's an
129
- # @attributes instance variable set we must be using ActiveRecord, so
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
- @_spyke_attributes ||= ::Spyke::Attributes.new(scope.params)
143
- use_setters(sanitize_for_mass_assignment(new_attributes)) if new_attributes && !new_attributes.empty?
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's default is PUT)
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
- # Behaves similar to ActiveSupport's class_attribute, redefining the
27
- # reader method so it can be inherited and overwritten in subclasses
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
- redefine_singleton_method(:fmrest_config) do
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 overwriting some connection settings in a thread-local
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. Example usage:
103
+ # connection.
77
104
  #
78
- # class MyModel < FmRest::Spyke::Base
79
- # faraday do |conn|
80
- # # Set up a custom logger for the model
81
- # conn.response :logger, MyApp.logger, bodies: true
82
- # end
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
- # Extended fetch to allow properly setting limit, offset and other
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, e.g.:
43
+ # on whether there's a query present in the current scope.
38
44
  #
39
- # Person.query(first_name: "Stefan").fetch # POST .../_find
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
- # API-error-raising version of #create
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
- # Overwrite Spyke's save to provide a number of features:
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
- # Overwrite Spyke's destroy to provide Data API script execution
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
- # API-error-raising version of #update
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(id)
199
+ reloaded = scope.find(__record_id)
156
200
  self.attributes = reloaded.attributes
157
- self.mod_id = reloaded.mod_id
201
+ self.__mod_id = reloaded.mod_id
158
202
  end
159
203
 
160
- # ActiveModel 5+ implements this method, so we only needed if we're in
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
- # Override Spyke's to_params to return FM Data API's expected JSON
11
- # format, and including only modified fields
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 Uri
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
- # Extend uri acccessor to default to FM Data schema
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) + "(/:id)"
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[:mod_id] = response[:modId] if response[:modId]
67
- data[:id] = response[:recordId].to_i if response[:recordId]
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 = { id: json_data[:recordId].to_i, mod_id: json_data[:modId] }
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 = { id: portal_fields[:recordId].to_i }
217
- attributes[:mod_id] = portal_fields[:modId] if portal_fields[:modId]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FmRest
4
- VERSION = "0.11.1"
4
+ VERSION = "0.12.0"
5
5
  end
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.11.1
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-09-24 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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
+ ...
@@ -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