fmrest 0.11.1 → 0.12.0

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: 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