fmrest 0.8.0 → 0.11.1

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: 5873c0192a2b166cfef71b33c521665dd6953660bd9d23d3ef728b89c55e886f
4
- data.tar.gz: 7615faa115cc7506d3a472d2fac70ea293bd3dbec1e03bb3c19f0faca51b7c9d
3
+ metadata.gz: d087824f401c6c36c5f0ac00acf9e3ee12fced43d002000533ba0b97e579d89d
4
+ data.tar.gz: 2c3f0018686f48f02c6e177cc10ba0cbdecd584523776c9269023bb7192eb271
5
5
  SHA512:
6
- metadata.gz: b627994fa66842be8200692895077649837a7b343c337aaf5762926c633de02baf14ba6428cc33b8b3cc0d0314d7d62a4f6cf16173edb09d32ba8218c533d7d6
7
- data.tar.gz: 2cd9e7f7003f9ec05120587f50ee892076d8e02045a6b18e9311276792bf831dee6c4434969990b17f4f5898f95040da0614aabbdb5940fa8906f3c507f7f0e8
6
+ metadata.gz: 9746926229c22793428d1c0d76c7d0b373cee751c15d83389e3a426ca434773524e1f8133d65914b0301d47c7e6c22f893f14850377bb8b665f065ce377f972f
7
+ data.tar.gz: a8d32edd04361bb9a4f8add3cc8ecd9e89cf1bcbcf0437cfcc3d30cba652c67b935d58091246f1657041dc0678ce0b01c3685d300b6a699fea3bb73dfb131846
@@ -0,0 +1,33 @@
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
@@ -1,11 +1,45 @@
1
1
  ## Changelog
2
2
 
3
+ ### 0.11.1
4
+
5
+ * Fix a couple crashes due to missing constants
6
+
7
+ ### 0.11.0
8
+
9
+ * Added custom class for connection settings, providing indifferent access
10
+ (i.e. keys can be strings or symbols), and centralized default values and
11
+ validations
12
+ * Added `:autologin`, `:token` and `:token_store` connection settings
13
+ * Added `FmRest::Base.fmrest_config_overlay=` and related methods
14
+ * Added `FmRest::V1.request_auth_token` and
15
+ `FmRest::Spyke::Base.request_auth_token` (as well as `!`-suffixed versions
16
+ which raise exceptions on failure)
17
+
18
+ ### 0.10.1
19
+
20
+ * Fix `URI.escape` obsolete warning messages in Ruby 2.7 by replacing it with
21
+ `URI.encode_www_form_component`
22
+ ([PR#40](https://github.com/beezwax/fmrest-ruby/pull/40))
23
+
24
+ ### 0.10.0
25
+
26
+ * Added `FmRest::StringDateAwareness` module to correct some issues when using
27
+ `FmRest::StringDate`
28
+ * Added basic timezones support
29
+ * Deprecated `class < FmRest::Spyke::Base(config_hash)` syntax in favor of
30
+ using `self.fmrest_config=`
31
+
32
+ ### 0.9.0
33
+
34
+ * Added `FmRest::Spyke::Base.set_globals`
35
+
3
36
  ### 0.8.0
4
37
 
5
38
  * Improved metadata when using `FmRest::Spyke::Model`. Metadata now uses
6
39
  Struct/OpenStruct, so properties are accessible through `.property`, as well
7
40
  as `[:property]`
8
- * Implemented `.find_in_batches` and `.find_each` for `FmRest::Spyke::Model`
41
+ * Added batch-finders `.find_in_batches` and `.find_each` for
42
+ * `FmRest::Spyke::Base`
9
43
 
10
44
  ### 0.7.1
11
45
 
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
@@ -943,6 +1020,32 @@ to retrieving single records, in that case you'll have to use
943
1020
  `.last_request_metadata`.
944
1021
 
945
1022
 
1023
+ ### Setting global field values
1024
+
1025
+ You can call `.set_globals` on any `FmRest::Spyke::Base` model to set glabal
1026
+ field values on the database that model is configured for.
1027
+
1028
+ You can pass it either a hash of fully qualified field names
1029
+ (table_name::field_name), or 1-level-deep nested hashes, with the outer being a
1030
+ table name and the inner keys being the field names:
1031
+
1032
+ ```ruby
1033
+ Honeybee.set_globals(
1034
+ "beeTable::myVar" => "value",
1035
+ "beeTable::myOtherVar" => "also a value"
1036
+ )
1037
+
1038
+ # Equivalent to the above example
1039
+ Honeybee.set_globals(beeTable: { myVar: "value", myOtherVar: "also a value" })
1040
+
1041
+ # Combined
1042
+ Honeybee.set_globals(
1043
+ "beeTable::myVar" => "value",
1044
+ beeTable: { myOtherVar: "also a value" }
1045
+ )
1046
+ ```
1047
+
1048
+
946
1049
  ## Logging
947
1050
 
948
1051
  If using fmrest-ruby + Spyke in a Rails app pretty log output will be set up
@@ -1011,7 +1114,7 @@ FM Data API reference: https://fmhelp.filemaker.com/docs/18/en/dataapi/
1011
1114
  | Get container data | Manual* | Yes |
1012
1115
  | Upload container data | Manual* | Yes |
1013
1116
  | Perform a find request | Manual* | Yes |
1014
- | Set global field values | Manual* | No |
1117
+ | Set global field values | Manual* | Yes
1015
1118
  | Run a script | Manual* | Yes |
1016
1119
  | Run a script with another request | Manual* | Yes |
1017
1120
 
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.add_dependency 'faraday', '>= 0.9.0', '< 2.0'
24
24
  spec.add_dependency 'faraday_middleware', '>= 0.9.1', '< 2.0'
25
25
 
26
- spec.add_development_dependency "bundler", "~> 1.16"
27
- spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "bundler"
27
+ spec.add_development_dependency "rake"
28
28
  spec.add_development_dependency "rspec", "~> 3.0"
29
29
  spec.add_development_dependency "spyke"
30
30
  spec.add_development_dependency "webmock"
@@ -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