fmrest 0.9.0 → 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: 5f96cf4500b2417bde1c853954dee03a362088adac933fc7d803d8e37e91cc8a
4
- data.tar.gz: e6cd271dbfef1c87c04a515b8a22e7ead87ec88ab915b37245d4dcecc7c9a237
3
+ metadata.gz: 725c8356fc5933f0484f7d04a4458ecc85c638838c8d9869d039a6e09b0b15f6
4
+ data.tar.gz: 2a8047594d93d13b0f6d9981159c16b1afdcaa19713c054391fc08ca1fa68633
5
5
  SHA512:
6
- metadata.gz: 68c4083e32e942fd609b050c2001d32b97c18d88ab70301f10ff0ce31527af2c5faff6e210afceaf152280dc2e0924a8edf2e2994e28ad331d2f015860f172f3
7
- data.tar.gz: 9af1f38e2a59bda96639d6b6f1d3756b007a8c327392159f6288f2365161a94a211ff483744499771716e8e1d61cd8a57876fbfffcf7646e61db52d1794ab1a3
6
+ metadata.gz: 7a41a3e747bf6ccfe0de7e5541b0db35d30d9723b7a0eeb895fbe9e21f4c2c7e6f08ccd3012c1e6f4edaa5403966e2a7563b74490327c721990fbdd4ab79608a
7
+ data.tar.gz: 16fb2868c102905a58632b1183581dcf4617d4e6c9f7529abad12916b78b3c692d21a469eaf7b23101d53ff55e8c9ccbfc1167556cc09855516647ce00565ea8
@@ -1,5 +1,40 @@
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
+
9
+ ### 0.11.1
10
+
11
+ * Fix a couple crashes due to missing constants
12
+
13
+ ### 0.11.0
14
+
15
+ * Added custom class for connection settings, providing indifferent access
16
+ (i.e. keys can be strings or symbols), and centralized default values and
17
+ validations
18
+ * Added `:autologin`, `:token` and `:token_store` connection settings
19
+ * Added `FmRest::Base.fmrest_config_overlay=` and related methods
20
+ * Added `FmRest::V1.request_auth_token` and
21
+ `FmRest::Spyke::Base.request_auth_token` (as well as `!`-suffixed versions
22
+ which raise exceptions on failure)
23
+
24
+ ### 0.10.1
25
+
26
+ * Fix `URI.escape` obsolete warning messages in Ruby 2.7 by replacing it with
27
+ `URI.encode_www_form_component`
28
+ ([PR#40](https://github.com/beezwax/fmrest-ruby/pull/40))
29
+
30
+ ### 0.10.0
31
+
32
+ * Added `FmRest::StringDateAwareness` module to correct some issues when using
33
+ `FmRest::StringDate`
34
+ * Added basic timezones support
35
+ * Deprecated `class < FmRest::Spyke::Base(config_hash)` syntax in favor of
36
+ using `self.fmrest_config=`
37
+
3
38
  ### 0.9.0
4
39
 
5
40
  * Added `FmRest::Spyke::Base.set_globals`
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # fmrest-ruby
2
2
 
3
- <a href="https://rubygems.org/gems/fmrest"><img src="https://badge.fury.io/rb/fmrest.svg?style=flat" alt="Gem Version"></a>
3
+ [![Gem Version](https://badge.fury.io/rb/fmrest.svg?style=flat)](https://rubygems.org/gems/fmrest)
4
+ ![CI](https://github.com/beezwax/fmrest-ruby/workflows/CI/badge.svg)
4
5
 
5
6
  A Ruby client for
6
- [FileMaker 18's Data API](https://fmhelp.filemaker.com/docs/18/en/dataapi/)
7
+ [FileMaker 18 and 19's Data API](https://help.claris.com/en/data-api-guide)
7
8
  using
8
9
  [Faraday](https://github.com/lostisland/faraday) and with optional
9
10
  [Spyke](https://github.com/balvig/spyke) support (ActiveRecord-ish models).
@@ -120,6 +121,9 @@ Option | Description | Format
120
121
  `:date_format` | Date parsing format | String (FM date format) | `"MM/dd/yyyy"`
121
122
  `:timestamp_format` | Timestmap parsing format | String (FM date format) | `"MM/dd/yyyy HH:mm:ss"`
122
123
  `:time_format` | Time parsing format | String (FM date format) | `"HH:mm:ss"`
124
+ `:timezone` | The timezone for the FM server | `:local` \| `:utc` \| `nil` | `nil`
125
+ `:autologin` | Whether to automatically start Data API sessions | Boolean | `true`
126
+ `:token` | Used to manually provide a session token (e.g. if `:autologin` is `false`) | String | None
123
127
 
124
128
  ### Default connection settings
125
129
 
@@ -270,9 +274,9 @@ a DSL in model classes).
270
274
  ### Hybrid string/date objects
271
275
 
272
276
  `FmRest::StringDate` and `FmRest::StringDateTime` are special classes that
273
- inherit from `String`, but internally parse and store a `Date`/`DateTime`
274
- (respectively), and delegate any methods not provided by `String` to those
275
- objects. In other words, they quack like a duck *and* bark like a dog.
277
+ inherit from `String`, but internally parse and store a `Date` or `DateTime`,
278
+ and delegate any methods not provided by `String` to those objects. In other
279
+ words, they quack like a duck *and* bark like a dog.
276
280
 
277
281
  You can use these when you want fmrest-ruby to provide you with date objects,
278
282
  but you don't want to worry about date coercion of false positives (i.e. a
@@ -280,7 +284,29 @@ string field that gets converted to `Date` because it just so matched the given
280
284
  date format).
281
285
 
282
286
  Be warned however that these classes come with a fair share of known gotchas
283
- (see GitHub wiki for more info).
287
+ (see GitHub wiki for more info). Some of those gothas can be removed by calling
288
+
289
+ ```ruby
290
+ FmRest::StringDateAwareness.enable
291
+ ```
292
+
293
+ Which will extend the core `Date` and `DateTime` classes to be aware of
294
+ `FmRest::StringDate`, especially when calling `Date.===`, `Date.parse` or
295
+ `Date._parse`.
296
+
297
+ If you're working with ActiveRecord models this will also make them accept
298
+ `FmRest::StringDate` values for date fields.
299
+
300
+ ### Timezones
301
+
302
+ fmrest-ruby has basic timezone support. You can set the `:timezone` option in
303
+ your connection settings to one of the following values:
304
+
305
+ * `:local` - dates will be converted to your system local time offset (as
306
+ defined by `ENV["TZ"]`), or the timezone set by `Time.zone` if you're using
307
+ ActiveSupport
308
+ * `:utc` - dates will be converted to UTC offset
309
+ * `nil` - (default) ignore timezones altogether
284
310
 
285
311
 
286
312
  ## Spyke support (ActiveRecord-like ORM)
@@ -322,17 +348,6 @@ class Honeybee < FmRest::Spyke::Base
322
348
  end
323
349
  ```
324
350
 
325
- In this case you can pass the [`fmrest_config`](#modelfmrest_config) hash as an
326
- argument to `Base()`:
327
-
328
- ```ruby
329
- class Honeybee < FmRest::Spyke::Base(host: "...", database: "...", username: "...", password: "...")
330
- end
331
-
332
- Honeybee.fmrest_config
333
- # => { host: "...", database: "...", username: "...", password: "..." }
334
- ```
335
-
336
351
  All of Spyke's basic ORM operations work:
337
352
 
338
353
  ```ruby
@@ -406,6 +421,59 @@ class Honeybee < BeeBase
406
421
  end
407
422
  ```
408
423
 
424
+ ### Model.fmrest_config_overlay=
425
+
426
+ There may be cases where you want to use different connection settings
427
+ depending on context, for example if you want to use username and password
428
+ provided by the user in a web application. Since `Model.fmrest_config` is
429
+ global, changing the username/password for one context would also change it for
430
+ all other contexts, leading to security issues.
431
+
432
+ `Model.fmrest_config_overlay=` solves that issue by allowing you to override
433
+ some settings in a thread-local and reversible manner. That way, using the same
434
+ example as above, you could connect to the Data API with user-provided
435
+ credentials without having them leak into other users of your web application.
436
+
437
+ E.g.:
438
+
439
+ ```ruby
440
+ class BeeBase < Spyke::Base
441
+ include FmRest::Spyke
442
+
443
+ # Host and database provided as base settings
444
+ self.fmrest_config = {
445
+ host: "example.com",
446
+ database: "My Database"
447
+ }
448
+ end
449
+
450
+ # E.g. in a controller-action of a Rails application:
451
+
452
+ # User-provided credentials
453
+ BeeBase.fmrest_config_overlay = {
454
+ username: params[:username],
455
+ password: params[:password]
456
+ }
457
+
458
+ # Perform some Data API requests ...
459
+ ```
460
+
461
+ ### Model.clear_fmrest_config_overlay
462
+
463
+ Clears the thread-local settings provided to `fmrest_config_overaly=`.
464
+
465
+ ### Model.with_overlay
466
+
467
+ Runs a block with the given settings overlay, resetting them after the block
468
+ finishes running. It wraps execution in its own fiber, so it doesn't affect the
469
+ overlay of the currently-running thread.
470
+
471
+ ```ruby
472
+ Honeybee.with_overlay(username: "...", password: "...") do
473
+ Honeybee.query(...)
474
+ end
475
+ ```
476
+
409
477
  ### Model.layout
410
478
 
411
479
  Use `layout` to set the `:layout` part of API URLs, e.g.:
@@ -422,6 +490,15 @@ Data API models.
422
490
  Note that you only need to set this if the name of the model and the name of
423
491
  the layout differ, otherwise the default will just work.
424
492
 
493
+ ### Model.request_auth_token
494
+
495
+ Requests a Data API session token using the connection settings in
496
+ `fmrest_config` and returns it if successful, otherwise returns `false`.
497
+
498
+ You normally don't need to use this method as fmrest-ruby will automatically
499
+ request and store session tokens for you (provided that `:autologin` is
500
+ `true`).
501
+
425
502
  ### Model.logout
426
503
 
427
504
  Use `logout` to log out from the database session (you may call it on any model
@@ -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
- spec.add_development_dependency "bundler", "~> 1.16"
27
- spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "bundler"
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"
@@ -4,16 +4,23 @@ require "faraday"
4
4
  require "faraday_middleware"
5
5
 
6
6
  require "fmrest/version"
7
- require "fmrest/v1"
7
+ require "fmrest/connection_settings"
8
+
9
+ require "fmrest/errors"
8
10
 
9
11
  module FmRest
12
+ autoload :V1, "fmrest/v1"
13
+ autoload :TokenStore, "fmrest/token_store"
14
+
10
15
  class << self
11
16
  attr_accessor :token_store
12
17
 
13
- attr_writer :default_connection_settings
18
+ def default_connection_settings=(settings)
19
+ @default_connection_settings = ConnectionSettings.wrap(settings, skip_validation: true)
20
+ end
14
21
 
15
22
  def default_connection_settings
16
- @default_connection_settings || {}
23
+ @default_connection_settings || ConnectionSettings.new({}, skip_validation: true)
17
24
  end
18
25
 
19
26
  def config=(connection_hash)
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FmRest
4
+ # Wrapper class for connection settings hash, with a number of purposes:
5
+ #
6
+ # * Provide indifferent access (base hash can have either string or symbol
7
+ # keys)
8
+ # * Method access
9
+ # * Default values
10
+ # * Basic validation
11
+ # * Normalization (e.g. aliased settings)
12
+ # * Useful error messages
13
+ class ConnectionSettings
14
+ class MissingSetting < ArgumentError; end
15
+
16
+ PROPERTIES = %i(
17
+ host
18
+ database
19
+ username
20
+ password
21
+ token
22
+ token_store
23
+ autologin
24
+ ssl
25
+ proxy
26
+ log
27
+ coerce_dates
28
+ date_format
29
+ timestamp_format
30
+ time_format
31
+ timezone
32
+ ).freeze
33
+
34
+ # NOTE: password intentionally left non-required since it's only really
35
+ # needed when no token exists, and should only be required when logging in
36
+ REQUIRED = %i(
37
+ host
38
+ database
39
+ ).freeze
40
+
41
+ DEFAULT_DATE_FORMAT = "MM/dd/yyyy"
42
+ DEFAULT_TIME_FORMAT = "HH:mm:ss"
43
+ DEFAULT_TIMESTAMP_FORMAT = "#{DEFAULT_DATE_FORMAT} #{DEFAULT_TIME_FORMAT}"
44
+
45
+ DEFAULTS = {
46
+ autologin: true,
47
+ log: false,
48
+ date_format: DEFAULT_DATE_FORMAT,
49
+ time_format: DEFAULT_TIME_FORMAT,
50
+ timestamp_format: DEFAULT_TIMESTAMP_FORMAT,
51
+ coerce_dates: false
52
+ }.freeze
53
+
54
+ def self.wrap(settings, skip_validation: false)
55
+ if settings.kind_of?(self)
56
+ settings.validate unless skip_validation
57
+ return settings
58
+ end
59
+ new(settings, skip_validation: skip_validation)
60
+ end
61
+
62
+ def initialize(settings, skip_validation: false)
63
+ @settings = settings.to_h.dup
64
+ normalize
65
+ validate unless skip_validation
66
+ end
67
+
68
+ PROPERTIES.each do |p|
69
+ define_method(p) do
70
+ get(p)
71
+ end
72
+
73
+ define_method("#{p}!") do
74
+ r = get(p)
75
+ raise MissingSetting, "Missing required setting: `#{p}'" if r.nil?
76
+ r
77
+ end
78
+
79
+ define_method("#{p}?") do
80
+ !!get(p)
81
+ end
82
+ end
83
+
84
+ def [](key)
85
+ raise ArgumentError, "Unknown setting `#{key}'" unless PROPERTIES.include?(key.to_sym)
86
+ get(key)
87
+ end
88
+
89
+ def to_h
90
+ PROPERTIES.each_with_object({}) do |p, h|
91
+ v = get(p)
92
+ h[p] = v unless v == DEFAULTS[p]
93
+ end
94
+ end
95
+
96
+ def merge(other, **keyword_args)
97
+ other = self.class.wrap(other, skip_validation: true)
98
+ self.class.new(to_h.merge(other.to_h), **keyword_args)
99
+ end
100
+
101
+ def validate
102
+ missing = REQUIRED.select { |r| get(r).nil? }.map { |m| "`#{m}'" }
103
+ raise MissingSetting, "Missing required setting(s): #{missing.join(', ')}" unless missing.empty?
104
+
105
+ unless username? || token?
106
+ raise MissingSetting, "A minimum of `username' or `token' are required to be able to establish a connection"
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def get(key)
113
+ return @settings[key.to_sym] if @settings.has_key?(key.to_sym)
114
+ return @settings[key.to_s] if @settings.has_key?(key.to_s)
115
+ DEFAULTS[key.to_sym]
116
+ end
117
+
118
+ def normalize
119
+ if !get(:username) && account_name = get(:account_name)
120
+ @settings[:username] = account_name
121
+ end
122
+ end
123
+ end
124
+ end
@@ -21,6 +21,8 @@ module FmRest
21
21
  class APIError::NoMatchingRecordsError < APIError::ParameterError; end
22
22
  class APIError::ValidationError < APIError; end # error codes 500..599
23
23
  class APIError::SystemError < APIError; end # error codes 800..899
24
+ class APIError::InvalidToken < APIError; end # error code 952
25
+ class APIError::MaximumDataAPICallsExceeded < APIError; end # error code 953
24
26
  class APIError::ScriptError < APIError; end # error codes 1200..1299
25
27
  class APIError::ODBCError < APIError; end # error codes 1400..1499
26
28
 
@@ -5,17 +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
- if config
12
- return Class.new(::FmRest::Spyke::Base) do
13
- self.fmrest_config = config
14
- end
15
- end
16
-
17
- ::FmRest::Spyke::Base
18
- end
19
- end
20
8
  end
21
9
  end