fmrest 0.7.0 → 0.10.1
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/CHANGELOG.md +31 -0
- data/README.md +113 -28
- data/fmrest.gemspec +2 -2
- data/lib/fmrest/spyke/base.rb +2 -0
- data/lib/fmrest/spyke/model.rb +2 -0
- data/lib/fmrest/spyke/model/connection.rb +12 -3
- 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/v1.rb +2 -0
- data/lib/fmrest/v1/connection.rb +7 -5
- data/lib/fmrest/v1/dates.rb +81 -0
- data/lib/fmrest/v1/type_coercer.rb +107 -32
- data/lib/fmrest/v1/utils.rb +0 -16
- data/lib/fmrest/version.rb +1 -1
- metadata +15 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccf62593af664237c1cd0c67928c869ae74cdcde4b2e3c9bd82c20fa0f6b0354
|
4
|
+
data.tar.gz: 9f6bd645aed2551d278b0b31e194dc97726e66396a76148ee7dae3bca5c67a93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 421a3be4119f862e31787b6027c7824e42641d85d6dbdcb6d5df2b849ef3e93556a03060a74e093a11fed962cdfbe61be19833c5d2195f6b4dad121ef759aeb6
|
7
|
+
data.tar.gz: 7bd1359945a7da96afdfd05c8948148bc6c8740351eb7c4dd1a31ba501fc82f5ad79dd68a166b2fee3022c636fddd187f0b27b97fc2d645922afddb813f98787
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,36 @@
|
|
1
1
|
## Changelog
|
2
2
|
|
3
|
+
### 0.10.1
|
4
|
+
|
5
|
+
* Fix `URI.escape` obsolete warning messages in Ruby 2.7 by replacing it with
|
6
|
+
`URI.encode_www_form_component`
|
7
|
+
([PR#40](https://github.com/beezwax/fmrest-ruby/pull/40))
|
8
|
+
|
9
|
+
### 0.10.0
|
10
|
+
|
11
|
+
* Added `FmRest::StringDateAwareness` module to correct some issues when using
|
12
|
+
`FmRest::StringDate`
|
13
|
+
* Added basic timezones support
|
14
|
+
* Deprecated `class < FmRest::Spyke::Base(config_hash)` syntax in favor of
|
15
|
+
using `self.fmrest_config=`
|
16
|
+
|
17
|
+
### 0.9.0
|
18
|
+
|
19
|
+
* Added `FmRest::Spyke::Base.set_globals`
|
20
|
+
|
21
|
+
### 0.8.0
|
22
|
+
|
23
|
+
* Improved metadata when using `FmRest::Spyke::Model`. Metadata now uses
|
24
|
+
Struct/OpenStruct, so properties are accessible through `.property`, as well
|
25
|
+
as `[:property]`
|
26
|
+
* Added batch-finders `.find_in_batches` and `.find_each` for
|
27
|
+
* `FmRest::Spyke::Base`
|
28
|
+
|
29
|
+
### 0.7.1
|
30
|
+
|
31
|
+
* Made sure `Model.find_one` and `Model.find_some` work without needing to call
|
32
|
+
`Model.all` in between
|
33
|
+
|
3
34
|
### 0.7.0
|
4
35
|
|
5
36
|
* Added date coercion feature
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
<a href="https://rubygems.org/gems/fmrest"><img src="https://badge.fury.io/rb/fmrest.svg?style=flat" alt="Gem Version"></a>
|
4
4
|
|
5
5
|
A Ruby client for
|
6
|
-
[FileMaker 18's Data API](https://
|
6
|
+
[FileMaker 18 and 19's Data API](https://help.claris.com/en/data-api-guide)
|
7
7
|
using
|
8
8
|
[Faraday](https://github.com/lostisland/faraday) and with optional
|
9
9
|
[Spyke](https://github.com/balvig/spyke) support (ActiveRecord-ish models).
|
@@ -120,6 +120,7 @@ Option | Description | Format
|
|
120
120
|
`:date_format` | Date parsing format | String (FM date format) | `"MM/dd/yyyy"`
|
121
121
|
`:timestamp_format` | Timestmap parsing format | String (FM date format) | `"MM/dd/yyyy HH:mm:ss"`
|
122
122
|
`:time_format` | Time parsing format | String (FM date format) | `"HH:mm:ss"`
|
123
|
+
`:timezone` | The timezone for the FM server | `:local` \| `:utc` \| `nil` | `nil`
|
123
124
|
|
124
125
|
### Default connection settings
|
125
126
|
|
@@ -195,7 +196,7 @@ FmRest.token_store = FmRest::TokenStore::Redis.new(redis: Redis.new, prefix: "my
|
|
195
196
|
FmRest.token_store = FmRest::TokenStore::Redis.new(prefix: "my-fancy-prefix:", host: "10.0.1.1", port: 6380, db: 15)
|
196
197
|
```
|
197
198
|
|
198
|
-
|
199
|
+
NOTE: redis-rb is not included as a gem dependency of fmrest-ruby, so you'll
|
199
200
|
have to add it to your Gemfile.
|
200
201
|
|
201
202
|
### Moneta
|
@@ -232,7 +233,7 @@ FmRest.token_store = FmRest::TokenStore::Moneta.new(
|
|
232
233
|
)
|
233
234
|
```
|
234
235
|
|
235
|
-
|
236
|
+
NOTE: the moneta gem is not included as a dependency of fmrest-ruby, so
|
236
237
|
you'll have to add it to your Gemfile.
|
237
238
|
|
238
239
|
|
@@ -246,14 +247,14 @@ to automatically "coerce" them into Ruby date objects.
|
|
246
247
|
The connection option `:coerce_dates` controls this feature. Possible values
|
247
248
|
are:
|
248
249
|
|
249
|
-
* `:full
|
250
|
+
* `:full` - whenever a string matches the given date/timestamp/time format,
|
250
251
|
convert them to `Date` or `DateTime` objects as appropriate
|
251
|
-
* `:hybrid` or `true
|
252
|
+
* `:hybrid` or `true` - similar as above, but instead of converting to regular
|
252
253
|
`Date`/`DateTime` it converts strings to `FmRest::StringDate` and
|
253
254
|
`FmRest::StringDateTime`, "hybrid" classes provided by fmrest-ruby that
|
254
255
|
retain the functionality of `String` while also providing most the
|
255
256
|
functionality of `Date`/`DateTime` (more on this below)
|
256
|
-
* `false
|
257
|
+
* `false` - disable date coercion entirely (default), leave original string
|
257
258
|
values untouched
|
258
259
|
|
259
260
|
Enabling date coercion works with both basic fmrest-ruby connections and Spyke
|
@@ -270,9 +271,9 @@ a DSL in model classes).
|
|
270
271
|
### Hybrid string/date objects
|
271
272
|
|
272
273
|
`FmRest::StringDate` and `FmRest::StringDateTime` are special classes that
|
273
|
-
inherit from `String`, but internally parse and store a `Date
|
274
|
-
|
275
|
-
|
274
|
+
inherit from `String`, but internally parse and store a `Date` or `DateTime`,
|
275
|
+
and delegate any methods not provided by `String` to those objects. In other
|
276
|
+
words, they quack like a duck *and* bark like a dog.
|
276
277
|
|
277
278
|
You can use these when you want fmrest-ruby to provide you with date objects,
|
278
279
|
but you don't want to worry about date coercion of false positives (i.e. a
|
@@ -280,7 +281,29 @@ string field that gets converted to `Date` because it just so matched the given
|
|
280
281
|
date format).
|
281
282
|
|
282
283
|
Be warned however that these classes come with a fair share of known gotchas
|
283
|
-
(see GitHub wiki for more info).
|
284
|
+
(see GitHub wiki for more info). Some of those gothas can be removed by calling
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
FmRest::StringDateAwareness.enable
|
288
|
+
```
|
289
|
+
|
290
|
+
Which will extend the core `Date` and `DateTime` classes to be aware of
|
291
|
+
`FmRest::StringDate`, especially when calling `Date.===`, `Date.parse` or
|
292
|
+
`Date._parse`.
|
293
|
+
|
294
|
+
If you're working with ActiveRecord models this will also make them accept
|
295
|
+
`FmRest::StringDate` values for date fields.
|
296
|
+
|
297
|
+
### Timezones
|
298
|
+
|
299
|
+
fmrest-ruby has basic timezone support. You can set the `:timezone` option in
|
300
|
+
your connection settings to one of the following values:
|
301
|
+
|
302
|
+
* `:local` - dates will be converted to your system local time offset (as
|
303
|
+
defined by `ENV["TZ"]`), or the timezone set by `Time.zone` if you're using
|
304
|
+
ActiveSupport
|
305
|
+
* `:utc` - dates will be converted to UTC offset
|
306
|
+
* `nil` - (default) ignore timezones altogether
|
284
307
|
|
285
308
|
|
286
309
|
## Spyke support (ActiveRecord-like ORM)
|
@@ -322,17 +345,6 @@ class Honeybee < FmRest::Spyke::Base
|
|
322
345
|
end
|
323
346
|
```
|
324
347
|
|
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
348
|
All of Spyke's basic ORM operations work:
|
337
349
|
|
338
350
|
```ruby
|
@@ -555,7 +567,7 @@ Honeybee.limit(10)
|
|
555
567
|
```
|
556
568
|
|
557
569
|
NOTE: You can also set a default limit value for a model class, see
|
558
|
-
[
|
570
|
+
[other notes on querying](#other-notes-on-querying).
|
559
571
|
|
560
572
|
You can also use `.limit` to set limits on portals:
|
561
573
|
|
@@ -727,15 +739,15 @@ the scope object:
|
|
727
739
|
Honeybee.limit(10).sort(:name).find_some # => [<Honeybee...>, ...]
|
728
740
|
```
|
729
741
|
|
730
|
-
If you want just a single result you can use `.
|
742
|
+
If you want just a single result you can use `.first` instead (this will
|
731
743
|
force `.limit(1)`):
|
732
744
|
|
733
745
|
```ruby
|
734
|
-
Honeybee.query(name: "Hutch").
|
746
|
+
Honeybee.query(name: "Hutch").first # => <Honeybee...>
|
735
747
|
```
|
736
748
|
|
737
749
|
If you know the id of the record you should use `.find(id)` instead of
|
738
|
-
`.query(id: id).
|
750
|
+
`.query(id: id).first` (so that the sent request is
|
739
751
|
`GET ../:layout/records/:id` instead of `POST ../:layout/_find`).
|
740
752
|
|
741
753
|
```ruby
|
@@ -746,6 +758,52 @@ Note also that if you use `.find(id)` your `.query()` parameters (as well as
|
|
746
758
|
limit, offset and sort parameters) will be discarded as they're not supported
|
747
759
|
by the single record endpoint.
|
748
760
|
|
761
|
+
|
762
|
+
### Finding records in batches
|
763
|
+
|
764
|
+
Sometimes you want to iterate over a very large number of records to do some
|
765
|
+
processing, but requesting them all at once would result in one huge request to
|
766
|
+
the Data API, and loading too many records in memory all at once.
|
767
|
+
|
768
|
+
To mitigate this problem you can use `.find_in_batches` and `.find_each`. If
|
769
|
+
you've used ActiveRecord you're probably familiar with how they operate:
|
770
|
+
|
771
|
+
```ruby
|
772
|
+
# Find records in batches of 100 each
|
773
|
+
Honeybee.query(hive: "Queensville").find_in_batches(batch_size: 100) do |batch|
|
774
|
+
dispatch_bees(batch)
|
775
|
+
end
|
776
|
+
|
777
|
+
# Iterate over all records using batches
|
778
|
+
Honeybee.query(hive: "Queensville").find_each(batch_size: 100) do |bee|
|
779
|
+
bee.dispatch
|
780
|
+
end
|
781
|
+
```
|
782
|
+
|
783
|
+
`.find_in_batches` yields collections of records (batches), while `.find_each`
|
784
|
+
yields individual records, but using batches behind the scenes.
|
785
|
+
|
786
|
+
Both methods accept a block-less form in which case they return an
|
787
|
+
`Enumerator`:
|
788
|
+
|
789
|
+
```ruby
|
790
|
+
batch_enum = Honeybee.find_in_batches
|
791
|
+
|
792
|
+
batch = batch_enum.next # => Spyke::Collection
|
793
|
+
|
794
|
+
batch_enum.each do |batch|
|
795
|
+
process_batch(batch)
|
796
|
+
end
|
797
|
+
|
798
|
+
record_enum = Honeybee.find_each
|
799
|
+
|
800
|
+
record_enum.next # => Honeybee
|
801
|
+
```
|
802
|
+
|
803
|
+
NOTE: By its nature, batch processing is subject to race conditions if other
|
804
|
+
processes are modifying the database.
|
805
|
+
|
806
|
+
|
749
807
|
### Container fields
|
750
808
|
|
751
809
|
You can define container fields on your model class with `container`:
|
@@ -783,6 +841,7 @@ bee.photo.upload(filename_or_io) # Upload a file to the container
|
|
783
841
|
* `:content_type` - The MIME content type to use (defaults to
|
784
842
|
`application/octet-stream`)
|
785
843
|
|
844
|
+
|
786
845
|
### Script execution
|
787
846
|
|
788
847
|
The Data API allows running scripts as part of many types of requests.
|
@@ -870,7 +929,7 @@ separately, under their matching key.
|
|
870
929
|
```ruby
|
871
930
|
bee.save(script: { presort: "My Presort Script", after: "My Script" })
|
872
931
|
|
873
|
-
Honeybee.last_request_metadata
|
932
|
+
Honeybee.last_request_metadata.script
|
874
933
|
# => { after: { result: "oh hi", error: "0" }, presort: { result: "lo", error: "0" } }
|
875
934
|
```
|
876
935
|
|
@@ -884,7 +943,7 @@ is performed on that scope.
|
|
884
943
|
|
885
944
|
```ruby
|
886
945
|
# Find one Honeybee record executing a presort and after script
|
887
|
-
Honeybee.script(presort: ["My Presort Script", "parameter"], after: "My Script").
|
946
|
+
Honeybee.script(presort: ["My Presort Script", "parameter"], after: "My Script").first
|
888
947
|
```
|
889
948
|
|
890
949
|
The model class' `.last_request_metadata` will be set in case you need to get the result.
|
@@ -896,6 +955,32 @@ to retrieving single records, in that case you'll have to use
|
|
896
955
|
`.last_request_metadata`.
|
897
956
|
|
898
957
|
|
958
|
+
### Setting global field values
|
959
|
+
|
960
|
+
You can call `.set_globals` on any `FmRest::Spyke::Base` model to set glabal
|
961
|
+
field values on the database that model is configured for.
|
962
|
+
|
963
|
+
You can pass it either a hash of fully qualified field names
|
964
|
+
(table_name::field_name), or 1-level-deep nested hashes, with the outer being a
|
965
|
+
table name and the inner keys being the field names:
|
966
|
+
|
967
|
+
```ruby
|
968
|
+
Honeybee.set_globals(
|
969
|
+
"beeTable::myVar" => "value",
|
970
|
+
"beeTable::myOtherVar" => "also a value"
|
971
|
+
)
|
972
|
+
|
973
|
+
# Equivalent to the above example
|
974
|
+
Honeybee.set_globals(beeTable: { myVar: "value", myOtherVar: "also a value" })
|
975
|
+
|
976
|
+
# Combined
|
977
|
+
Honeybee.set_globals(
|
978
|
+
"beeTable::myVar" => "value",
|
979
|
+
beeTable: { myOtherVar: "also a value" }
|
980
|
+
)
|
981
|
+
```
|
982
|
+
|
983
|
+
|
899
984
|
## Logging
|
900
985
|
|
901
986
|
If using fmrest-ruby + Spyke in a Rails app pretty log output will be set up
|
@@ -964,7 +1049,7 @@ FM Data API reference: https://fmhelp.filemaker.com/docs/18/en/dataapi/
|
|
964
1049
|
| Get container data | Manual* | Yes |
|
965
1050
|
| Upload container data | Manual* | Yes |
|
966
1051
|
| Perform a find request | Manual* | Yes |
|
967
|
-
| Set global field values | Manual* |
|
1052
|
+
| Set global field values | Manual* | Yes
|
968
1053
|
| Run a script | Manual* | Yes |
|
969
1054
|
| Run a script with another request | Manual* | Yes |
|
970
1055
|
|
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/spyke/base.rb
CHANGED
@@ -8,6 +8,8 @@ module FmRest
|
|
8
8
|
|
9
9
|
class << self
|
10
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
|
+
|
11
13
|
if config
|
12
14
|
return Class.new(::FmRest::Spyke::Base) do
|
13
15
|
self.fmrest_config = config
|
data/lib/fmrest/spyke/model.rb
CHANGED
@@ -7,6 +7,7 @@ require "fmrest/spyke/model/serialization"
|
|
7
7
|
require "fmrest/spyke/model/associations"
|
8
8
|
require "fmrest/spyke/model/orm"
|
9
9
|
require "fmrest/spyke/model/container_fields"
|
10
|
+
require "fmrest/spyke/model/global_fields"
|
10
11
|
require "fmrest/spyke/model/http"
|
11
12
|
require "fmrest/spyke/model/auth"
|
12
13
|
|
@@ -22,6 +23,7 @@ module FmRest
|
|
22
23
|
include Associations
|
23
24
|
include Orm
|
24
25
|
include ContainerFields
|
26
|
+
include GlobalFields
|
25
27
|
include Http
|
26
28
|
include Auth
|
27
29
|
|
@@ -4,10 +4,19 @@ module FmRest
|
|
4
4
|
module Spyke
|
5
5
|
module Model
|
6
6
|
module Connection
|
7
|
-
extend
|
7
|
+
extend ActiveSupport::Concern
|
8
8
|
|
9
9
|
included do
|
10
|
-
class_attribute :fmrest_config,
|
10
|
+
class_attribute :fmrest_config, instance_writer: false, instance_predicate: false
|
11
|
+
|
12
|
+
# Overrides the fmrest_config reader created by class_attribute so we
|
13
|
+
# can default set the default at call time.
|
14
|
+
#
|
15
|
+
# This method gets overwriten in subclasses if self.fmrest_config= is
|
16
|
+
# called.
|
17
|
+
define_singleton_method(:fmrest_config) do
|
18
|
+
FmRest.default_connection_settings
|
19
|
+
end
|
11
20
|
|
12
21
|
class_attribute :faraday_block, instance_accessor: false, instance_predicate: false
|
13
22
|
class << self; private :faraday_block, :faraday_block=; end
|
@@ -40,7 +49,7 @@ module FmRest
|
|
40
49
|
def fmrest_connection
|
41
50
|
@fmrest_connection ||=
|
42
51
|
begin
|
43
|
-
config = fmrest_config
|
52
|
+
config = fmrest_config
|
44
53
|
|
45
54
|
FmRest::V1.build_connection(config) do |conn|
|
46
55
|
faraday_block.call(conn) if faraday_block
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FmRest
|
4
|
+
module Spyke
|
5
|
+
module Model
|
6
|
+
module GlobalFields
|
7
|
+
extend ::ActiveSupport::Concern
|
8
|
+
|
9
|
+
FULLY_QUALIFIED_FIELD_NAME_MATCHER = /\A[^:]+::[^:]+\Z/.freeze
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
def set_globals(values_hash)
|
13
|
+
connection.patch(FmRest::V1.globals_path, {
|
14
|
+
globalFields: normalize_globals_hash(values_hash)
|
15
|
+
})
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def normalize_globals_hash(hash)
|
21
|
+
hash.each_with_object({}) do |(k, v), normalized|
|
22
|
+
if v.kind_of?(Hash)
|
23
|
+
v.each do |k2, v2|
|
24
|
+
normalized["#{k}::#{k2}"] = v2
|
25
|
+
end
|
26
|
+
next
|
27
|
+
end
|
28
|
+
|
29
|
+
unless FULLY_QUALIFIED_FIELD_NAME_MATCHER === k.to_s
|
30
|
+
raise ArgumentError, "global fields must be given in fully qualified format (table name::field name)"
|
31
|
+
end
|
32
|
+
|
33
|
+
normalized[k] = v
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -24,7 +24,8 @@ module FmRest
|
|
24
24
|
# Methods delegated to FmRest::Spyke::Relation
|
25
25
|
delegate :limit, :offset, :sort, :order, :query, :omit, :portal,
|
26
26
|
:portals, :includes, :with_all_portals, :without_portals,
|
27
|
-
:script,
|
27
|
+
:script, :find_one, :first, :any, :find_some,
|
28
|
+
:find_in_batches, :find_each, to: :all
|
28
29
|
|
29
30
|
def all
|
30
31
|
# Use FmRest's Relation instead of Spyke's vanilla one
|
@@ -4,8 +4,8 @@ module FmRest
|
|
4
4
|
module Spyke
|
5
5
|
module Model
|
6
6
|
module Serialization
|
7
|
-
FM_DATE_FORMAT = "%m/%d/%Y"
|
8
|
-
FM_DATETIME_FORMAT = "#{FM_DATE_FORMAT} %H:%M:%S"
|
7
|
+
FM_DATE_FORMAT = "%m/%d/%Y"
|
8
|
+
FM_DATETIME_FORMAT = "#{FM_DATE_FORMAT} %H:%M:%S"
|
9
9
|
|
10
10
|
# Override Spyke's to_params to return FM Data API's expected JSON
|
11
11
|
# format, and including only modified fields
|
@@ -63,9 +63,9 @@ module FmRest
|
|
63
63
|
def serialize_values!(params)
|
64
64
|
params.transform_values! do |value|
|
65
65
|
case value
|
66
|
-
when DateTime, Time
|
67
|
-
value.strftime(FM_DATETIME_FORMAT)
|
68
|
-
when Date
|
66
|
+
when DateTime, Time, FmRest::StringDateTime
|
67
|
+
convert_datetime_timezone(value.to_datetime).strftime(FM_DATETIME_FORMAT)
|
68
|
+
when Date, FmRest::StringDate
|
69
69
|
value.strftime(FM_DATE_FORMAT)
|
70
70
|
else
|
71
71
|
value
|
@@ -74,6 +74,17 @@ module FmRest
|
|
74
74
|
|
75
75
|
params
|
76
76
|
end
|
77
|
+
|
78
|
+
def convert_datetime_timezone(dt)
|
79
|
+
case fmrest_config.fetch(:timezone, nil)
|
80
|
+
when :utc, "utc"
|
81
|
+
dt.new_offset(0)
|
82
|
+
when :local, "local"
|
83
|
+
dt.new_offset(FmRest::V1.local_offset_for_datetime(dt))
|
84
|
+
when nil
|
85
|
+
dt
|
86
|
+
end
|
87
|
+
end
|
77
88
|
end
|
78
89
|
end
|
79
90
|
end
|
@@ -190,6 +190,79 @@ module FmRest
|
|
190
190
|
rescue ::Spyke::ConnectionError => error
|
191
191
|
fallback_or_reraise(error, default: nil)
|
192
192
|
end
|
193
|
+
alias_method :first, :find_one
|
194
|
+
alias_method :any, :find_one
|
195
|
+
|
196
|
+
# Yields each batch of records that was found by the find options.
|
197
|
+
#
|
198
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
199
|
+
# other processes are modifying the database
|
200
|
+
#
|
201
|
+
# @param batch_size [Integer] Specifies the size of the batch.
|
202
|
+
# @return [Enumerator] if called without a block.
|
203
|
+
def find_in_batches(batch_size: 1000)
|
204
|
+
unless block_given?
|
205
|
+
return to_enum(:find_in_batches, batch_size: batch_size) do
|
206
|
+
total = limit(1).find_some.metadata.data_info.found_count
|
207
|
+
(total - 1).div(batch_size) + 1
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
offset = 1 # DAPI offset is 1-based
|
212
|
+
|
213
|
+
loop do
|
214
|
+
relation = offset(offset).limit(batch_size)
|
215
|
+
|
216
|
+
records = relation.find_some
|
217
|
+
|
218
|
+
yield records if records.length > 0
|
219
|
+
|
220
|
+
break if records.length < batch_size
|
221
|
+
|
222
|
+
# Save one iteration if the total is a multiple of batch_size
|
223
|
+
if found_count = records.metadata.data_info && records.metadata.data_info.found_count
|
224
|
+
break if found_count == (offset - 1) + batch_size
|
225
|
+
end
|
226
|
+
|
227
|
+
offset += batch_size
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Looping through a collection of records from the database (using the
|
232
|
+
# #all method, for example) is very inefficient since it will fetch and
|
233
|
+
# instantiate all the objects at once.
|
234
|
+
#
|
235
|
+
# In that case, batch processing methods allow you to work with the
|
236
|
+
# records in batches, thereby greatly reducing memory consumption and be
|
237
|
+
# lighter on the Data API server.
|
238
|
+
#
|
239
|
+
# The find_each method uses #find_in_batches with a batch size of 1000
|
240
|
+
# (or as specified by the :batch_size option).
|
241
|
+
#
|
242
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
243
|
+
# other processes are modifying the database
|
244
|
+
#
|
245
|
+
# @param (see #find_in_batches)
|
246
|
+
# @example
|
247
|
+
# Person.find_each do |person|
|
248
|
+
# person.greet
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# Person.query(name: "==Mitch").find_each do |person|
|
252
|
+
# person.say_hi
|
253
|
+
# end
|
254
|
+
# @return (see #find_in_batches)
|
255
|
+
def find_each(batch_size: 1000)
|
256
|
+
unless block_given?
|
257
|
+
return to_enum(:find_each, batch_size: batch_size) do
|
258
|
+
limit(1).find_some.metadata.data_info.found_count
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
find_in_batches(batch_size: batch_size) do |records|
|
263
|
+
records.each { |r| yield r }
|
264
|
+
end
|
265
|
+
end
|
193
266
|
|
194
267
|
protected
|
195
268
|
|
@@ -1,9 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
+
require "ostruct"
|
4
5
|
|
5
6
|
module FmRest
|
6
7
|
module Spyke
|
8
|
+
# Metadata class to be passed to Spyke::Collection#metadata
|
9
|
+
class Metadata < Struct.new(:messages, :script, :data_info)
|
10
|
+
alias_method :scripts, :script
|
11
|
+
end
|
12
|
+
|
13
|
+
class DataInfo < OpenStruct
|
14
|
+
def total_record_count; totalRecordCount; end
|
15
|
+
def found_count; foundCount; end
|
16
|
+
def returned_count; returnedCount; end
|
17
|
+
end
|
18
|
+
|
7
19
|
# Response Faraday middleware for converting FM API's response JSON into
|
8
20
|
# Spyke's expected format
|
9
21
|
class SpykeFormatter < ::Faraday::Response::Middleware
|
@@ -77,36 +89,61 @@ module FmRest
|
|
77
89
|
|
78
90
|
# @param json [Hash]
|
79
91
|
# @param include_errors [Boolean]
|
80
|
-
# @return [
|
92
|
+
# @return [FmRest::Spyke::Metadata] the skeleton structure for a
|
93
|
+
# Spyke-formatted response
|
81
94
|
def build_base_hash(json, include_errors = false)
|
82
95
|
{
|
83
|
-
metadata:
|
84
|
-
|
96
|
+
metadata: Metadata.new(
|
97
|
+
prepare_messages(json),
|
98
|
+
prepare_script_results(json),
|
99
|
+
prepare_data_info(json)
|
100
|
+
).freeze,
|
101
|
+
errors: include_errors ? prepare_errors(json) : {}
|
85
102
|
}
|
86
103
|
end
|
87
104
|
|
88
105
|
# @param json [Hash]
|
89
|
-
# @return [
|
106
|
+
# @return [Array<OpenStruct>] the skeleton structure for a
|
107
|
+
# Spyke-formatted response
|
108
|
+
def prepare_messages(json)
|
109
|
+
return [] unless json[:messages]
|
110
|
+
json[:messages].map { |m| OpenStruct.new(m).freeze }.freeze
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param json [Hash]
|
114
|
+
# @return [OpenStruct] the script(s) execution results for Spyke metadata
|
115
|
+
# format
|
90
116
|
def prepare_script_results(json)
|
91
117
|
results = {}
|
92
118
|
|
93
119
|
[:prerequest, :presort].each do |s|
|
94
120
|
if json[:response][:"scriptError.#{s}"]
|
95
|
-
results[s] =
|
121
|
+
results[s] = OpenStruct.new(
|
96
122
|
result: json[:response][:"scriptResult.#{s}"],
|
97
123
|
error: json[:response][:"scriptError.#{s}"]
|
98
|
-
|
124
|
+
).freeze
|
99
125
|
end
|
100
126
|
end
|
101
127
|
|
102
128
|
if json[:response][:scriptError]
|
103
|
-
results[:after] =
|
129
|
+
results[:after] = OpenStruct.new(
|
104
130
|
result: json[:response][:scriptResult],
|
105
131
|
error: json[:response][:scriptError]
|
106
|
-
|
132
|
+
).freeze
|
107
133
|
end
|
108
134
|
|
109
|
-
results
|
135
|
+
results.present? ? OpenStruct.new(results).freeze : nil
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param json [Hash]
|
139
|
+
# @return [OpenStruct] the script(s) execution results for
|
140
|
+
# Spyke metadata format
|
141
|
+
def prepare_data_info(json)
|
142
|
+
data_info = json[:response] && json[:response][:dataInfo]
|
143
|
+
|
144
|
+
return nil unless data_info.present?
|
145
|
+
|
146
|
+
DataInfo.new(data_info).freeze
|
110
147
|
end
|
111
148
|
|
112
149
|
# @param json [Hash]
|
data/lib/fmrest/string_date.rb
CHANGED
@@ -79,17 +79,24 @@ module FmRest
|
|
79
79
|
class InvalidDate < ArgumentError; end
|
80
80
|
|
81
81
|
class << self
|
82
|
-
|
82
|
+
def strptime(str, date_format, *_)
|
83
|
+
begin
|
84
|
+
date = self::DELEGATE_CLASS.strptime(str, date_format)
|
85
|
+
rescue ArgumentError
|
86
|
+
raise InvalidDate
|
87
|
+
end
|
88
|
+
|
89
|
+
new(str, date)
|
90
|
+
end
|
83
91
|
end
|
84
92
|
|
85
|
-
def initialize(str,
|
93
|
+
def initialize(str, date, **str_args)
|
94
|
+
raise ArgumentError, "str must be of class String" unless str.is_a?(String)
|
95
|
+
raise ArgumentError, "date must be of class #{self.class::DELEGATE_CLASS.name}" unless date.is_a?(self.class::DELEGATE_CLASS)
|
96
|
+
|
86
97
|
super(str, **str_args)
|
87
98
|
|
88
|
-
|
89
|
-
@delegate = self.class::DELEGATE_CLASS.strptime(str, date_format)
|
90
|
-
rescue ArgumentError
|
91
|
-
raise InvalidDate
|
92
|
-
end
|
99
|
+
@delegate = date
|
93
100
|
|
94
101
|
freeze
|
95
102
|
end
|
@@ -178,4 +185,36 @@ module FmRest
|
|
178
185
|
@delegate
|
179
186
|
end
|
180
187
|
end
|
188
|
+
|
189
|
+
module StringDateAwareness
|
190
|
+
def _parse(v, *_)
|
191
|
+
if v.is_a?(StringDateTime)
|
192
|
+
return { year: v.year, mon: v.month, mday: v.mday, hour: v.hour, min: v.min, sec: v.sec, sec_fraction: v.sec_fraction, offset: v.offset }
|
193
|
+
end
|
194
|
+
if v.is_a?(StringDate)
|
195
|
+
return { year: v.year, mon: v.month, mday: v.mday }
|
196
|
+
end
|
197
|
+
super
|
198
|
+
end
|
199
|
+
|
200
|
+
def parse(v, *_)
|
201
|
+
if v.is_a?(StringDate)
|
202
|
+
return self == ::DateTime ? v.to_datetime : v.to_date
|
203
|
+
end
|
204
|
+
super
|
205
|
+
end
|
206
|
+
|
207
|
+
# Overriding case equality method so that it returns true for
|
208
|
+
# `FmRest::StringDate` instances
|
209
|
+
#
|
210
|
+
# Calls superclass method
|
211
|
+
#
|
212
|
+
def ===(other)
|
213
|
+
super || other.is_a?(StringDate)
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.enable(classes: [Date, DateTime])
|
217
|
+
classes.each { |klass| klass.singleton_class.prepend(self) }
|
218
|
+
end
|
219
|
+
end
|
181
220
|
end
|
data/lib/fmrest/v1.rb
CHANGED
@@ -4,6 +4,7 @@ require "fmrest/v1/connection"
|
|
4
4
|
require "fmrest/v1/paths"
|
5
5
|
require "fmrest/v1/container_fields"
|
6
6
|
require "fmrest/v1/utils"
|
7
|
+
require "fmrest/v1/dates"
|
7
8
|
|
8
9
|
module FmRest
|
9
10
|
module V1
|
@@ -15,5 +16,6 @@ module FmRest
|
|
15
16
|
extend Paths
|
16
17
|
extend ContainerFields
|
17
18
|
extend Utils
|
19
|
+
extend Dates
|
18
20
|
end
|
19
21
|
end
|
data/lib/fmrest/v1/connection.rb
CHANGED
@@ -31,10 +31,6 @@ module FmRest
|
|
31
31
|
conn.request :multipart
|
32
32
|
conn.request :json
|
33
33
|
|
34
|
-
if options[:log]
|
35
|
-
conn.response :logger, nil, bodies: true, headers: true
|
36
|
-
end
|
37
|
-
|
38
34
|
# Allow overriding the default response middleware
|
39
35
|
if block_given?
|
40
36
|
yield conn, options
|
@@ -43,6 +39,10 @@ module FmRest
|
|
43
39
|
conn.response :json
|
44
40
|
end
|
45
41
|
|
42
|
+
if options[:log]
|
43
|
+
conn.response :logger, nil, bodies: true, headers: true
|
44
|
+
end
|
45
|
+
|
46
46
|
conn.adapter Faraday.default_adapter
|
47
47
|
end
|
48
48
|
end
|
@@ -74,8 +74,10 @@ module FmRest
|
|
74
74
|
faraday_options[:ssl] = options[:ssl] if options.key?(:ssl)
|
75
75
|
faraday_options[:proxy] = options[:proxy] if options.key?(:proxy)
|
76
76
|
|
77
|
+
database = URI.encode_www_form_component(options.fetch(:database))
|
78
|
+
|
77
79
|
Faraday.new(
|
78
|
-
"#{scheme}://#{host}#{BASE_PATH}/#{
|
80
|
+
"#{scheme}://#{host}#{BASE_PATH}/#{database}/".freeze,
|
79
81
|
faraday_options,
|
80
82
|
&block
|
81
83
|
)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FmRest
|
4
|
+
module V1
|
5
|
+
module Dates
|
6
|
+
FM_DATETIME_FORMAT_MATCHER = /MM|mm|dd|HH|ss|yyyy/.freeze
|
7
|
+
|
8
|
+
FM_DATE_TO_STRPTIME_SUBSTITUTIONS = {
|
9
|
+
"MM" => "%m",
|
10
|
+
"dd" => "%d",
|
11
|
+
"yyyy" => "%Y",
|
12
|
+
"HH" => "%H",
|
13
|
+
"mm" => "%M",
|
14
|
+
"ss" => "%S"
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
FM_DATE_TO_REGEXP_SUBSTITUTIONS = {
|
18
|
+
"MM" => '(?:0[1-9]|1[012])',
|
19
|
+
"dd" => '(?:0[1-9]|[12][0-9]|3[01])',
|
20
|
+
"yyyy" => '\d{4}',
|
21
|
+
"HH" => '(?:[01]\d|2[0123])',
|
22
|
+
"mm" => '[0-5]\d',
|
23
|
+
"ss" => '[0-5]\d'
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
def self.extended(mod)
|
27
|
+
mod.instance_eval do
|
28
|
+
@date_strptime = {}
|
29
|
+
@date_regexp = {}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Converts a FM date-time format to `DateTime.strptime` format
|
34
|
+
#
|
35
|
+
# @param fm_format [String] The FileMaker date-time format
|
36
|
+
# @return [String] The `DateTime.strpdate` equivalent of the given FM
|
37
|
+
# date-time format
|
38
|
+
def fm_date_to_strptime_format(fm_format)
|
39
|
+
@date_strptime[fm_format] ||=
|
40
|
+
fm_format.gsub(FM_DATETIME_FORMAT_MATCHER, FM_DATE_TO_STRPTIME_SUBSTITUTIONS).freeze
|
41
|
+
end
|
42
|
+
|
43
|
+
# Converts a FM date-time format to a Regexp. This is mostly used a
|
44
|
+
# quicker way of checking whether a FM field is a date field than
|
45
|
+
# Date|DateTime.strptime
|
46
|
+
#
|
47
|
+
# @param fm_format [String] The FileMaker date-time format
|
48
|
+
# @return [Regexp] A reegular expression matching strings in the given FM
|
49
|
+
# date-time format
|
50
|
+
def fm_date_to_regexp(fm_format)
|
51
|
+
@date_regexp[fm_format] ||=
|
52
|
+
Regexp.new('\A' + fm_format.gsub(FM_DATETIME_FORMAT_MATCHER, FM_DATE_TO_REGEXP_SUBSTITUTIONS) + '\Z').freeze
|
53
|
+
end
|
54
|
+
|
55
|
+
# Takes a DateTime dt, and returns the correct local offset for that dt,
|
56
|
+
# daylight savings included, in fraction of a day.
|
57
|
+
#
|
58
|
+
# By default, if ActiveSupport's Time.zone is set it will be used instead
|
59
|
+
# of the system timezone.
|
60
|
+
#
|
61
|
+
# @param dt [DateTime] The DateTime to get the offset for
|
62
|
+
# @param zone [nil, String, TimeZone] The timezone to use to calculate
|
63
|
+
# the offset (defaults to system timezone, or ActiveSupport's Time.zone
|
64
|
+
# if set)
|
65
|
+
# @return [Rational] The offset in fraction of a day
|
66
|
+
def local_offset_for_datetime(dt, zone = nil)
|
67
|
+
dt = dt.new_offset(0)
|
68
|
+
time = ::Time.utc(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec)
|
69
|
+
|
70
|
+
# Do we have ActiveSupport's TimeZone?
|
71
|
+
time = if time.respond_to?(:in_time_zone)
|
72
|
+
time.in_time_zone(zone || ::Time.zone)
|
73
|
+
else
|
74
|
+
time.localtime
|
75
|
+
end
|
76
|
+
|
77
|
+
Rational(time.utc_offset, 86400) # seconds in one day (24*60*60)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -5,7 +5,8 @@ require "fmrest/string_date"
|
|
5
5
|
module FmRest
|
6
6
|
module V1
|
7
7
|
class TypeCoercer < Faraday::Response::Middleware
|
8
|
-
# We use this date to represent a time for consistency with
|
8
|
+
# We use this date to represent a FileMaker time for consistency with
|
9
|
+
# ginjo-rfm
|
9
10
|
JULIAN_ZERO_DAY = "-4712/1/1"
|
10
11
|
|
11
12
|
COERCE_HYBRID = [:hybrid, "hybrid", true].freeze
|
@@ -30,15 +31,13 @@ module FmRest
|
|
30
31
|
field_data = record["fieldData"] || record[:fieldData]
|
31
32
|
portal_data = record["portalData"] || record[:portalData]
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
coerce_fields(field_data)
|
35
|
+
|
36
|
+
portal_data.try(:each_value) do |portal_records|
|
37
|
+
portal_records.each do |pr|
|
38
|
+
coerce_fields(pr)
|
38
39
|
end
|
39
40
|
end
|
40
|
-
|
41
|
-
enum.each { |hash| coerce_fields(hash) }
|
42
41
|
end
|
43
42
|
end
|
44
43
|
|
@@ -49,29 +48,49 @@ module FmRest
|
|
49
48
|
next unless v.is_a?(String)
|
50
49
|
next if k == "recordId" || k == :recordId || k == "modId" || k == :modId
|
51
50
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
if quick_check_timestamp(v)
|
52
|
+
begin
|
53
|
+
hash[k] = coerce_timestamp(v)
|
54
|
+
next
|
55
|
+
rescue ArgumentError
|
56
|
+
end
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
if quick_check_date(v)
|
60
|
+
begin
|
61
|
+
hash[k] = date_class.strptime(v, date_strptime_format)
|
62
|
+
next
|
63
|
+
rescue ArgumentError
|
64
|
+
end
|
64
65
|
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
if quick_check_time(v)
|
68
|
+
begin
|
69
|
+
hash[k] = datetime_class.strptime("#{JULIAN_ZERO_DAY} #{v}", time_strptime_format)
|
70
|
+
next
|
71
|
+
rescue ArgumentError
|
72
|
+
end
|
71
73
|
end
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
77
|
+
def coerce_timestamp(str)
|
78
|
+
str_timestamp = DateTime.strptime(str, datetime_strptime_format)
|
79
|
+
|
80
|
+
if local_timezone?
|
81
|
+
# Change the DateTime to the local timezone, keeping the same
|
82
|
+
# time and just modifying the timezone
|
83
|
+
offset = FmRest::V1.local_offset_for_datetime(str_timestamp)
|
84
|
+
str_timestamp = str_timestamp.new_offset(offset) - offset
|
85
|
+
end
|
86
|
+
|
87
|
+
if datetime_class == StringDateTime
|
88
|
+
str_timestamp = StringDateTime.new(str, str_timestamp)
|
89
|
+
end
|
90
|
+
|
91
|
+
str_timestamp
|
92
|
+
end
|
93
|
+
|
75
94
|
def date_class
|
76
95
|
@date_class ||=
|
77
96
|
case coerce_dates
|
@@ -92,19 +111,75 @@ module FmRest
|
|
92
111
|
end
|
93
112
|
end
|
94
113
|
|
95
|
-
def
|
96
|
-
@date_format
|
97
|
-
|
114
|
+
def date_fm_format
|
115
|
+
@options[:date_format] || DEFAULT_DATE_FORMAT
|
116
|
+
end
|
117
|
+
|
118
|
+
def timestamp_fm_format
|
119
|
+
@options[:timestamp_format] || DEFAULT_TIMESTAMP_FORMAT
|
120
|
+
end
|
121
|
+
|
122
|
+
def time_fm_format
|
123
|
+
@options[:time_format] || DEFAULT_TIME_FORMAT
|
98
124
|
end
|
99
125
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
126
|
+
def date_strptime_format
|
127
|
+
FmRest::V1.fm_date_to_strptime_format(date_fm_format)
|
128
|
+
end
|
129
|
+
|
130
|
+
def datetime_strptime_format
|
131
|
+
FmRest::V1.fm_date_to_strptime_format(timestamp_fm_format)
|
132
|
+
end
|
133
|
+
|
134
|
+
def time_strptime_format
|
135
|
+
@time_strptime_format ||=
|
136
|
+
"%Y/%m/%d " + FmRest::V1.fm_date_to_strptime_format(time_fm_format)
|
137
|
+
end
|
138
|
+
|
139
|
+
# We use a string length test, followed by regexp match test to try to
|
140
|
+
# identify date fields. Benchmarking shows this should be between 1 and 3
|
141
|
+
# orders of magnitude faster for fails (i.e. non-dates) than just using
|
142
|
+
# Date.strptime.
|
143
|
+
#
|
144
|
+
# user system total real
|
145
|
+
# strptime: 0.268496 0.000962 0.269458 ( 0.270865)
|
146
|
+
# re=~: 0.024872 0.000070 0.024942 ( 0.025057)
|
147
|
+
# re.match?: 0.019745 0.000095 0.019840 ( 0.020058)
|
148
|
+
# strptime fail: 0.141309 0.000354 0.141663 ( 0.142266)
|
149
|
+
# re=~ fail: 0.031637 0.000095 0.031732 ( 0.031872)
|
150
|
+
# re.match? fail: 0.011249 0.000056 0.011305 ( 0.011375)
|
151
|
+
# length fail: 0.007177 0.000024 0.007201 ( 0.007222)
|
152
|
+
#
|
153
|
+
# NOTE: The faster Regexp#match? was introduced in Ruby 2.4.0, so we
|
154
|
+
# can't really rely on it being available
|
155
|
+
if //.respond_to?(:match?)
|
156
|
+
def quick_check_timestamp(v)
|
157
|
+
v.length == timestamp_fm_format.length && FmRest::V1::fm_date_to_regexp(timestamp_fm_format).match?(v)
|
158
|
+
end
|
159
|
+
|
160
|
+
def quick_check_date(v)
|
161
|
+
v.length == date_fm_format.length && FmRest::V1::fm_date_to_regexp(date_fm_format).match?(v)
|
162
|
+
end
|
163
|
+
|
164
|
+
def quick_check_time(v)
|
165
|
+
v.length == time_fm_format.length && FmRest::V1::fm_date_to_regexp(time_fm_format).match?(v)
|
166
|
+
end
|
167
|
+
else
|
168
|
+
def quick_check_timestamp(v)
|
169
|
+
v.length == timestamp_fm_format.length && FmRest::V1::fm_date_to_regexp(timestamp_fm_format) =~ v
|
170
|
+
end
|
171
|
+
|
172
|
+
def quick_check_date(v)
|
173
|
+
v.length == date_fm_format.length && FmRest::V1::fm_date_to_regexp(date_fm_format) =~ v
|
174
|
+
end
|
175
|
+
|
176
|
+
def quick_check_time(v)
|
177
|
+
v.length == time_fm_format.length && FmRest::V1::fm_date_to_regexp(time_fm_format) =~ v
|
178
|
+
end
|
103
179
|
end
|
104
180
|
|
105
|
-
def
|
106
|
-
@
|
107
|
-
"%Y/%m/%d " + FmRest::V1.convert_date_time_format(@options[:time_format] || DEFAULT_TIME_FORMAT)
|
181
|
+
def local_timezone?
|
182
|
+
@local_timezone ||= @options.fetch(:timezone, nil).try(:to_sym) == :local
|
108
183
|
end
|
109
184
|
|
110
185
|
def coerce_dates
|
data/lib/fmrest/v1/utils.rb
CHANGED
@@ -5,17 +5,6 @@ module FmRest
|
|
5
5
|
module Utils
|
6
6
|
VALID_SCRIPT_KEYS = [:prerequest, :presort, :after].freeze
|
7
7
|
|
8
|
-
FM_DATETIME_FORMAT_MATCHER = /MM|mm|dd|HH|ss|yyyy/.freeze
|
9
|
-
|
10
|
-
FM_DATETIME_FORMAT_SUBSTITUTIONS = {
|
11
|
-
"MM" => "%m",
|
12
|
-
"dd" => "%d",
|
13
|
-
"yyyy" => "%Y",
|
14
|
-
"HH" => "%H",
|
15
|
-
"mm" => "%M",
|
16
|
-
"ss" => "%S"
|
17
|
-
}.freeze
|
18
|
-
|
19
8
|
# Converts custom script options to a hash with the Data API's expected
|
20
9
|
# JSON script format.
|
21
10
|
#
|
@@ -83,11 +72,6 @@ module FmRest
|
|
83
72
|
params
|
84
73
|
end
|
85
74
|
|
86
|
-
# Converts a FM date-time format to `Date.strptime` format
|
87
|
-
#
|
88
|
-
def convert_date_time_format(fm_format)
|
89
|
-
fm_format.gsub(FM_DATETIME_FORMAT_MATCHER, FM_DATETIME_FORMAT_SUBSTITUTIONS)
|
90
|
-
end
|
91
75
|
|
92
76
|
private
|
93
77
|
|
data/lib/fmrest/version.rb
CHANGED
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.
|
4
|
+
version: 0.10.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pedro Carbajal
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -54,30 +54,30 @@ dependencies:
|
|
54
54
|
name: bundler
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
|
-
- - "
|
57
|
+
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: '
|
59
|
+
version: '0'
|
60
60
|
type: :development
|
61
61
|
prerelease: false
|
62
62
|
version_requirements: !ruby/object:Gem::Requirement
|
63
63
|
requirements:
|
64
|
-
- - "
|
64
|
+
- - ">="
|
65
65
|
- !ruby/object:Gem::Version
|
66
|
-
version: '
|
66
|
+
version: '0'
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: rake
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
70
70
|
requirements:
|
71
|
-
- - "
|
71
|
+
- - ">="
|
72
72
|
- !ruby/object:Gem::Version
|
73
|
-
version: '
|
73
|
+
version: '0'
|
74
74
|
type: :development
|
75
75
|
prerelease: false
|
76
76
|
version_requirements: !ruby/object:Gem::Requirement
|
77
77
|
requirements:
|
78
|
-
- - "
|
78
|
+
- - ">="
|
79
79
|
- !ruby/object:Gem::Version
|
80
|
-
version: '
|
80
|
+
version: '0'
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: rspec
|
83
83
|
requirement: !ruby/object:Gem::Requirement
|
@@ -247,6 +247,7 @@ files:
|
|
247
247
|
- lib/fmrest/spyke/model/auth.rb
|
248
248
|
- lib/fmrest/spyke/model/connection.rb
|
249
249
|
- lib/fmrest/spyke/model/container_fields.rb
|
250
|
+
- lib/fmrest/spyke/model/global_fields.rb
|
250
251
|
- lib/fmrest/spyke/model/http.rb
|
251
252
|
- lib/fmrest/spyke/model/orm.rb
|
252
253
|
- lib/fmrest/spyke/model/serialization.rb
|
@@ -265,6 +266,7 @@ files:
|
|
265
266
|
- lib/fmrest/v1.rb
|
266
267
|
- lib/fmrest/v1/connection.rb
|
267
268
|
- lib/fmrest/v1/container_fields.rb
|
269
|
+
- lib/fmrest/v1/dates.rb
|
268
270
|
- lib/fmrest/v1/paths.rb
|
269
271
|
- lib/fmrest/v1/raise_errors.rb
|
270
272
|
- lib/fmrest/v1/token_session.rb
|
@@ -277,7 +279,7 @@ homepage: https://github.com/beezwax/fmrest-ruby
|
|
277
279
|
licenses:
|
278
280
|
- MIT
|
279
281
|
metadata: {}
|
280
|
-
post_install_message:
|
282
|
+
post_install_message:
|
281
283
|
rdoc_options: []
|
282
284
|
require_paths:
|
283
285
|
- lib
|
@@ -293,7 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
293
295
|
version: '0'
|
294
296
|
requirements: []
|
295
297
|
rubygems_version: 3.0.6
|
296
|
-
signing_key:
|
298
|
+
signing_key:
|
297
299
|
specification_version: 4
|
298
300
|
summary: FileMaker Data API client using Faraday
|
299
301
|
test_files: []
|