fmrest 0.7.1 → 0.11.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 +4 -4
- data/.github/workflows/ci.yml +33 -0
- data/CHANGELOG.md +37 -0
- data/README.md +176 -26
- data/fmrest.gemspec +2 -2
- data/lib/fmrest.rb +8 -3
- data/lib/fmrest/connection_settings.rb +124 -0
- data/lib/fmrest/errors.rb +2 -0
- data/lib/fmrest/spyke/base.rb +2 -0
- data/lib/fmrest/spyke/model.rb +2 -0
- data/lib/fmrest/spyke/model/auth.rb +8 -0
- data/lib/fmrest/spyke/model/connection.rb +88 -18
- data/lib/fmrest/spyke/model/global_fields.rb +40 -0
- data/lib/fmrest/spyke/model/orm.rb +2 -1
- data/lib/fmrest/spyke/model/serialization.rb +16 -5
- data/lib/fmrest/spyke/relation.rb +73 -0
- data/lib/fmrest/spyke/spyke_formatter.rb +46 -9
- data/lib/fmrest/string_date.rb +46 -7
- data/lib/fmrest/token_store.rb +6 -0
- data/lib/fmrest/token_store/base.rb +3 -3
- data/lib/fmrest/v1.rb +8 -4
- data/lib/fmrest/v1/auth.rb +30 -0
- data/lib/fmrest/v1/connection.rb +54 -28
- data/lib/fmrest/v1/dates.rb +81 -0
- data/lib/fmrest/v1/raise_errors.rb +3 -1
- data/lib/fmrest/v1/token_session.rb +41 -49
- data/lib/fmrest/v1/type_coercer.rb +111 -36
- data/lib/fmrest/v1/utils.rb +0 -17
- data/lib/fmrest/version.rb +1 -1
- metadata +18 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6433cf32d6d0111377f6080967e96fd5ff4cd54c5e223551c777a24c58e8662
|
4
|
+
data.tar.gz: 1a0fcd5951ab9b5bee88d822a429f16b02dcb01d5c95f74de9d529bb1ed00278
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0130f8b3598b3723d3c9e2514448a44a10381045176894cfed21244547c7ed41d0bc83fd18ab40981e9a2fc9ac7bd5c7290a5026b301878ea2c1d9ec440bb76
|
7
|
+
data.tar.gz: 0fc6a827bb57dea0b261bb812b58322dad8a01eecf78a4344bef877732f4f0b016636750833c048c5831e63e5015c707264a0697282cc7fba9a6879a0cd7e8b1
|
@@ -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
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,42 @@
|
|
1
1
|
## Changelog
|
2
2
|
|
3
|
+
### 0.11.0
|
4
|
+
|
5
|
+
* Added custom class for connection settings, providing indifferent access
|
6
|
+
(i.e. keys can be strings or symbols), and centralized default values and
|
7
|
+
validations
|
8
|
+
* Added `:autologin`, `:token` and `:token_store` connection settings
|
9
|
+
* Added `FmRest::Base.fmrest_config_overlay=` and related methods
|
10
|
+
* Added `FmRest::V1.request_auth_token` and
|
11
|
+
`FmRest::Spyke::Base.request_auth_token` (as well as `!`-suffixed versions
|
12
|
+
which raise exceptions on failure)
|
13
|
+
|
14
|
+
### 0.10.1
|
15
|
+
|
16
|
+
* Fix `URI.escape` obsolete warning messages in Ruby 2.7 by replacing it with
|
17
|
+
`URI.encode_www_form_component`
|
18
|
+
([PR#40](https://github.com/beezwax/fmrest-ruby/pull/40))
|
19
|
+
|
20
|
+
### 0.10.0
|
21
|
+
|
22
|
+
* Added `FmRest::StringDateAwareness` module to correct some issues when using
|
23
|
+
`FmRest::StringDate`
|
24
|
+
* Added basic timezones support
|
25
|
+
* Deprecated `class < FmRest::Spyke::Base(config_hash)` syntax in favor of
|
26
|
+
using `self.fmrest_config=`
|
27
|
+
|
28
|
+
### 0.9.0
|
29
|
+
|
30
|
+
* Added `FmRest::Spyke::Base.set_globals`
|
31
|
+
|
32
|
+
### 0.8.0
|
33
|
+
|
34
|
+
* Improved metadata when using `FmRest::Spyke::Model`. Metadata now uses
|
35
|
+
Struct/OpenStruct, so properties are accessible through `.property`, as well
|
36
|
+
as `[:property]`
|
37
|
+
* Added batch-finders `.find_in_batches` and `.find_each` for
|
38
|
+
* `FmRest::Spyke::Base`
|
39
|
+
|
3
40
|
### 0.7.1
|
4
41
|
|
5
42
|
* Made sure `Model.find_one` and `Model.find_some` work without needing to call
|
data/README.md
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# fmrest-ruby
|
2
2
|
|
3
|
-
|
3
|
+
[](https://rubygems.org/gems/fmrest)
|
4
|
+

|
4
5
|
|
5
6
|
A Ruby client for
|
6
|
-
[FileMaker 18's Data API](https://
|
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
|
|
@@ -195,7 +199,7 @@ FmRest.token_store = FmRest::TokenStore::Redis.new(redis: Redis.new, prefix: "my
|
|
195
199
|
FmRest.token_store = FmRest::TokenStore::Redis.new(prefix: "my-fancy-prefix:", host: "10.0.1.1", port: 6380, db: 15)
|
196
200
|
```
|
197
201
|
|
198
|
-
|
202
|
+
NOTE: redis-rb is not included as a gem dependency of fmrest-ruby, so you'll
|
199
203
|
have to add it to your Gemfile.
|
200
204
|
|
201
205
|
### Moneta
|
@@ -232,7 +236,7 @@ FmRest.token_store = FmRest::TokenStore::Moneta.new(
|
|
232
236
|
)
|
233
237
|
```
|
234
238
|
|
235
|
-
|
239
|
+
NOTE: the moneta gem is not included as a dependency of fmrest-ruby, so
|
236
240
|
you'll have to add it to your Gemfile.
|
237
241
|
|
238
242
|
|
@@ -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
|
274
|
-
|
275
|
-
|
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
|
@@ -555,7 +632,7 @@ Honeybee.limit(10)
|
|
555
632
|
```
|
556
633
|
|
557
634
|
NOTE: You can also set a default limit value for a model class, see
|
558
|
-
[
|
635
|
+
[other notes on querying](#other-notes-on-querying).
|
559
636
|
|
560
637
|
You can also use `.limit` to set limits on portals:
|
561
638
|
|
@@ -727,15 +804,15 @@ the scope object:
|
|
727
804
|
Honeybee.limit(10).sort(:name).find_some # => [<Honeybee...>, ...]
|
728
805
|
```
|
729
806
|
|
730
|
-
If you want just a single result you can use `.
|
807
|
+
If you want just a single result you can use `.first` instead (this will
|
731
808
|
force `.limit(1)`):
|
732
809
|
|
733
810
|
```ruby
|
734
|
-
Honeybee.query(name: "Hutch").
|
811
|
+
Honeybee.query(name: "Hutch").first # => <Honeybee...>
|
735
812
|
```
|
736
813
|
|
737
814
|
If you know the id of the record you should use `.find(id)` instead of
|
738
|
-
`.query(id: id).
|
815
|
+
`.query(id: id).first` (so that the sent request is
|
739
816
|
`GET ../:layout/records/:id` instead of `POST ../:layout/_find`).
|
740
817
|
|
741
818
|
```ruby
|
@@ -746,6 +823,52 @@ Note also that if you use `.find(id)` your `.query()` parameters (as well as
|
|
746
823
|
limit, offset and sort parameters) will be discarded as they're not supported
|
747
824
|
by the single record endpoint.
|
748
825
|
|
826
|
+
|
827
|
+
### Finding records in batches
|
828
|
+
|
829
|
+
Sometimes you want to iterate over a very large number of records to do some
|
830
|
+
processing, but requesting them all at once would result in one huge request to
|
831
|
+
the Data API, and loading too many records in memory all at once.
|
832
|
+
|
833
|
+
To mitigate this problem you can use `.find_in_batches` and `.find_each`. If
|
834
|
+
you've used ActiveRecord you're probably familiar with how they operate:
|
835
|
+
|
836
|
+
```ruby
|
837
|
+
# Find records in batches of 100 each
|
838
|
+
Honeybee.query(hive: "Queensville").find_in_batches(batch_size: 100) do |batch|
|
839
|
+
dispatch_bees(batch)
|
840
|
+
end
|
841
|
+
|
842
|
+
# Iterate over all records using batches
|
843
|
+
Honeybee.query(hive: "Queensville").find_each(batch_size: 100) do |bee|
|
844
|
+
bee.dispatch
|
845
|
+
end
|
846
|
+
```
|
847
|
+
|
848
|
+
`.find_in_batches` yields collections of records (batches), while `.find_each`
|
849
|
+
yields individual records, but using batches behind the scenes.
|
850
|
+
|
851
|
+
Both methods accept a block-less form in which case they return an
|
852
|
+
`Enumerator`:
|
853
|
+
|
854
|
+
```ruby
|
855
|
+
batch_enum = Honeybee.find_in_batches
|
856
|
+
|
857
|
+
batch = batch_enum.next # => Spyke::Collection
|
858
|
+
|
859
|
+
batch_enum.each do |batch|
|
860
|
+
process_batch(batch)
|
861
|
+
end
|
862
|
+
|
863
|
+
record_enum = Honeybee.find_each
|
864
|
+
|
865
|
+
record_enum.next # => Honeybee
|
866
|
+
```
|
867
|
+
|
868
|
+
NOTE: By its nature, batch processing is subject to race conditions if other
|
869
|
+
processes are modifying the database.
|
870
|
+
|
871
|
+
|
749
872
|
### Container fields
|
750
873
|
|
751
874
|
You can define container fields on your model class with `container`:
|
@@ -783,6 +906,7 @@ bee.photo.upload(filename_or_io) # Upload a file to the container
|
|
783
906
|
* `:content_type` - The MIME content type to use (defaults to
|
784
907
|
`application/octet-stream`)
|
785
908
|
|
909
|
+
|
786
910
|
### Script execution
|
787
911
|
|
788
912
|
The Data API allows running scripts as part of many types of requests.
|
@@ -870,7 +994,7 @@ separately, under their matching key.
|
|
870
994
|
```ruby
|
871
995
|
bee.save(script: { presort: "My Presort Script", after: "My Script" })
|
872
996
|
|
873
|
-
Honeybee.last_request_metadata
|
997
|
+
Honeybee.last_request_metadata.script
|
874
998
|
# => { after: { result: "oh hi", error: "0" }, presort: { result: "lo", error: "0" } }
|
875
999
|
```
|
876
1000
|
|
@@ -884,7 +1008,7 @@ is performed on that scope.
|
|
884
1008
|
|
885
1009
|
```ruby
|
886
1010
|
# Find one Honeybee record executing a presort and after script
|
887
|
-
Honeybee.script(presort: ["My Presort Script", "parameter"], after: "My Script").
|
1011
|
+
Honeybee.script(presort: ["My Presort Script", "parameter"], after: "My Script").first
|
888
1012
|
```
|
889
1013
|
|
890
1014
|
The model class' `.last_request_metadata` will be set in case you need to get the result.
|
@@ -896,6 +1020,32 @@ to retrieving single records, in that case you'll have to use
|
|
896
1020
|
`.last_request_metadata`.
|
897
1021
|
|
898
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
|
+
|
899
1049
|
## Logging
|
900
1050
|
|
901
1051
|
If using fmrest-ruby + Spyke in a Rails app pretty log output will be set up
|
@@ -964,7 +1114,7 @@ FM Data API reference: https://fmhelp.filemaker.com/docs/18/en/dataapi/
|
|
964
1114
|
| Get container data | Manual* | Yes |
|
965
1115
|
| Upload container data | Manual* | Yes |
|
966
1116
|
| Perform a find request | Manual* | Yes |
|
967
|
-
| Set global field values | Manual* |
|
1117
|
+
| Set global field values | Manual* | Yes
|
968
1118
|
| Run a script | Manual* | Yes |
|
969
1119
|
| Run a script with another request | Manual* | Yes |
|
970
1120
|
|
data/fmrest.gemspec
CHANGED
@@ -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"
|
27
|
-
spec.add_development_dependency "rake"
|
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"
|
data/lib/fmrest.rb
CHANGED
@@ -4,16 +4,21 @@ require "faraday"
|
|
4
4
|
require "faraday_middleware"
|
5
5
|
|
6
6
|
require "fmrest/version"
|
7
|
-
require "fmrest/
|
7
|
+
require "fmrest/connection_settings"
|
8
8
|
|
9
9
|
module FmRest
|
10
|
+
autoload :V1, "fmrest/v1"
|
11
|
+
autoload :TokenStore, "fmrest/token_store"
|
12
|
+
|
10
13
|
class << self
|
11
14
|
attr_accessor :token_store
|
12
15
|
|
13
|
-
|
16
|
+
def default_connection_settings=(settings)
|
17
|
+
@default_connection_settings = ConnectionSettings.wrap(settings, skip_validation: true)
|
18
|
+
end
|
14
19
|
|
15
20
|
def default_connection_settings
|
16
|
-
@default_connection_settings || {}
|
21
|
+
@default_connection_settings || ConnectionSettings.new({}, skip_validation: true)
|
17
22
|
end
|
18
23
|
|
19
24
|
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
|