fmrest 0.6.0 → 0.7.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/CHANGELOG.md +4 -0
- data/README.md +70 -20
- data/lib/fmrest/spyke.rb +1 -1
- data/lib/fmrest/spyke/model/associations.rb +2 -2
- data/lib/fmrest/spyke/model/connection.rb +19 -9
- data/lib/fmrest/spyke/{json_parser.rb → spyke_formatter.rb} +4 -8
- data/lib/fmrest/string_date.rb +181 -0
- data/lib/fmrest/v1.rb +4 -0
- data/lib/fmrest/v1/connection.rb +4 -3
- data/lib/fmrest/v1/token_session.rb +2 -0
- data/lib/fmrest/v1/type_coercer.rb +117 -0
- data/lib/fmrest/v1/utils.rb +17 -0
- data/lib/fmrest/version.rb +1 -1
- metadata +5 -4
- data/CODE_OF_CONDUCT.md +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab46f70bffcfea32ef2bfd5021f03fa654bb43134e22b8e22a20d9955afe9caf
|
4
|
+
data.tar.gz: 32ce4378ff291d2c41d9b255b032d4562d7544a477765748c87c49a655449405
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a68af2d8e53631b1d5c4d9e5e0849d224ff3918d1138d437463e30318ae81c36ca968d226f2f3631dbaa0d21b6287c1899e3b322a716729ad0d8b77aff80fd8d
|
7
|
+
data.tar.gz: ace50f913960e6b2edd190c8c1cbb75a0ebf15c0c3f0e8343720155f1c53b78bb9fd942be0a5e9bf2a2bc1ff729277f86360ffa4bba397ba48abe86c07db02ad
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -105,6 +105,22 @@ You can also pass a `:log` option for basic request logging, see the section on
|
|
105
105
|
`:username` is also aliased as `:account_name` to provide cross-compatibility
|
106
106
|
with the ginjo-rfm gem.
|
107
107
|
|
108
|
+
### Full list of available options
|
109
|
+
|
110
|
+
Option | Description | Format | Default
|
111
|
+
--------------------|--------------------------------------------|-----------------------------|--------
|
112
|
+
`:host` | Hostname with optional port, e.g. `"example.com:9000"` | String | None
|
113
|
+
`:database` | | String | None
|
114
|
+
`:username` | | String | None
|
115
|
+
`:password` | | String | None
|
116
|
+
`:ssl` | SSL options to be forwarded to Faraday | Faraday SSL options | None
|
117
|
+
`:proxy` | Proxy options to be forwarded to Faraday | Faraday proxy options | None
|
118
|
+
`:log` | Log JSON responses to STDOUT | Boolean | `false`
|
119
|
+
`:coerce_dates` | See section on [date fields](#date-fields) | Boolean \| `:hybrid` \| `:full` | `false`
|
120
|
+
`:date_format` | Date parsing format | String (FM date format) | `"MM/dd/yyyy"`
|
121
|
+
`:timestamp_format` | Timestmap parsing format | String (FM date format) | `"MM/dd/yyyy HH:mm:ss"`
|
122
|
+
`:time_format` | Time parsing format | String (FM date format) | `"HH:mm:ss"`
|
123
|
+
|
108
124
|
### Default connection settings
|
109
125
|
|
110
126
|
If you're only connecting to a single FM database you can configure it globally
|
@@ -122,6 +138,7 @@ FmRest.default_connection_settings = {
|
|
122
138
|
This configuration will be used by default by `FmRest::V1.build_connection` as
|
123
139
|
well as your models whenever you don't pass a configuration hash explicitly.
|
124
140
|
|
141
|
+
|
125
142
|
## Session token store
|
126
143
|
|
127
144
|
By default fmrest-ruby will use a memory-based store for the session tokens.
|
@@ -218,6 +235,54 @@ FmRest.token_store = FmRest::TokenStore::Moneta.new(
|
|
218
235
|
**NOTE:** the moneta gem is not included as a dependency of fmrest-ruby, so
|
219
236
|
you'll have to add it to your Gemfile.
|
220
237
|
|
238
|
+
|
239
|
+
## Date fields
|
240
|
+
|
241
|
+
Since the Data API uses JSON (wich doesn't provide a native date/time object),
|
242
|
+
dates and timestamps are received in string format. By default fmrest-ruby
|
243
|
+
leaves those string fields untouched, but it provides an opt-in feature to try
|
244
|
+
to automatically "coerce" them into Ruby date objects.
|
245
|
+
|
246
|
+
The connection option `:coerce_dates` controls this feature. Possible values
|
247
|
+
are:
|
248
|
+
|
249
|
+
* `:full`: whenever a string matches the given date/timestamp/time format,
|
250
|
+
convert them to `Date` or `DateTime` objects as appropriate
|
251
|
+
* `:hybrid` or `true`: similar as above, but instead of converting to regular
|
252
|
+
`Date`/`DateTime` it converts strings to `FmRest::StringDate` and
|
253
|
+
`FmRest::StringDateTime`, "hybrid" classes provided by fmrest-ruby that
|
254
|
+
retain the functionality of `String` while also providing most the
|
255
|
+
functionality of `Date`/`DateTime` (more on this below)
|
256
|
+
* `false`: disable date coercion entirely (default), leave original string
|
257
|
+
values untouched
|
258
|
+
|
259
|
+
Enabling date coercion works with both basic fmrest-ruby connections and Spyke
|
260
|
+
models (ORM).
|
261
|
+
|
262
|
+
The connection options `:date_format`, `:timestamp_format` and `:time_format`
|
263
|
+
control how to match and parse dates. You only need to provide these if you use
|
264
|
+
a date/time localization different from American format (the default).
|
265
|
+
|
266
|
+
Future versions of fmrest-ruby will provide better (and less heuristic) ways of
|
267
|
+
specifying and/or detecting date fields (e.g. by requesting layout metadata or
|
268
|
+
a DSL in model classes).
|
269
|
+
|
270
|
+
### Hybrid string/date objects
|
271
|
+
|
272
|
+
`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.
|
276
|
+
|
277
|
+
You can use these when you want fmrest-ruby to provide you with date objects,
|
278
|
+
but you don't want to worry about date coercion of false positives (i.e. a
|
279
|
+
string field that gets converted to `Date` because it just so matched the given
|
280
|
+
date format).
|
281
|
+
|
282
|
+
Be warned however that these classes come with a fair share of known gotchas
|
283
|
+
(see GitHub wiki for more info).
|
284
|
+
|
285
|
+
|
221
286
|
## Spyke support (ActiveRecord-like ORM)
|
222
287
|
|
223
288
|
[Spyke](https://github.com/balvig/spyke) is an ActiveRecord-like gem for
|
@@ -830,6 +895,7 @@ same metadata hash with script execution results. Note that this does not apply
|
|
830
895
|
to retrieving single records, in that case you'll have to use
|
831
896
|
`.last_request_metadata`.
|
832
897
|
|
898
|
+
|
833
899
|
## Logging
|
834
900
|
|
835
901
|
If using fmrest-ruby + Spyke in a Rails app pretty log output will be set up
|
@@ -883,7 +949,7 @@ FM Data API reference: https://fmhelp.filemaker.com/docs/18/en/dataapi/
|
|
883
949
|
| Log in using OAuth | No | No |
|
884
950
|
| Log in to an external data source | No | No |
|
885
951
|
| Log in using a FileMaker ID account | No | No |
|
886
|
-
| Log out | Yes | Yes
|
952
|
+
| Log out | Yes | Yes |
|
887
953
|
| Get product information | Manual* | No |
|
888
954
|
| Get database names | Manual* | No |
|
889
955
|
| Get script names | Manual* | No |
|
@@ -904,19 +970,6 @@ FM Data API reference: https://fmhelp.filemaker.com/docs/18/en/dataapi/
|
|
904
970
|
|
905
971
|
\* You can manually supply the URL and JSON to a `FmRest` connection.
|
906
972
|
|
907
|
-
## TODO
|
908
|
-
|
909
|
-
- [ ] Support for FM18 features
|
910
|
-
- [ ] Better/simpler-to-use core Ruby API
|
911
|
-
- [ ] Better API documentation and README
|
912
|
-
- [ ] Oauth support
|
913
|
-
- [x] Support for portal limit and offset
|
914
|
-
- [x] More options for token storage
|
915
|
-
- [x] Support for container fields
|
916
|
-
- [x] Optional logging
|
917
|
-
- [x] FmRest::Spyke::Base class for single inheritance (as alternative for mixin)
|
918
|
-
- [x] Specs
|
919
|
-
- [x] Support for portal data
|
920
973
|
|
921
974
|
## Gem development
|
922
975
|
|
@@ -931,6 +984,7 @@ release a new version, update the version number in `version.rb`, and then run
|
|
931
984
|
git commits and tags, and push the `.gem` file to
|
932
985
|
[rubygems.org](https://rubygems.org).
|
933
986
|
|
987
|
+
|
934
988
|
## Contributing
|
935
989
|
|
936
990
|
Bug reports and pull requests are welcome. This project is intended to be a
|
@@ -938,20 +992,16 @@ safe, welcoming space for collaboration, and contributors are expected to
|
|
938
992
|
adhere to the [Contributor Covenant](http://contributor-covenant.org) code of
|
939
993
|
conduct.
|
940
994
|
|
995
|
+
|
941
996
|
## License
|
942
997
|
|
943
998
|
The gem is available as open source under the terms of the
|
944
999
|
[MIT License](https://opensource.org/licenses/MIT).
|
945
1000
|
See [LICENSE.txt](LICENSE.txt).
|
946
1001
|
|
1002
|
+
|
947
1003
|
## Disclaimer
|
948
1004
|
|
949
1005
|
This project is not sponsored by or otherwise affiliated with FileMaker, Inc,
|
950
1006
|
an Apple subsidiary. FileMaker is a trademark of FileMaker, Inc., registered in
|
951
1007
|
the U.S. and other countries.
|
952
|
-
|
953
|
-
## Code of Conduct
|
954
|
-
|
955
|
-
Everyone interacting in the fmrest-ruby project’s codebases, issue trackers,
|
956
|
-
chat rooms and mailing lists is expected to follow the [code of
|
957
|
-
conduct](CODE_OF_CONDUCT.md).
|
data/lib/fmrest/spyke.rb
CHANGED
@@ -10,7 +10,7 @@ module FmRest
|
|
10
10
|
|
11
11
|
included do
|
12
12
|
# Keep track of portal options by their FM keys as we could need it
|
13
|
-
# to parse the portalData JSON in
|
13
|
+
# to parse the portalData JSON in SpykeFormatter
|
14
14
|
class_attribute :portal_options, instance_accessor: false, instance_predicate: false
|
15
15
|
|
16
16
|
# class_attribute supports a :default option since ActiveSupport 5.2,
|
@@ -37,7 +37,7 @@ module FmRest
|
|
37
37
|
def has_portal(name, options = {})
|
38
38
|
create_association(name, Portal, options)
|
39
39
|
|
40
|
-
# Store options for
|
40
|
+
# Store options for SpykeFormatter to use if needed
|
41
41
|
portal_key = options[:portal_key] || name
|
42
42
|
self.portal_options = portal_options.merge(portal_key.to_s => options.dup.merge(name: name.to_s)).freeze
|
43
43
|
|
@@ -38,15 +38,25 @@ module FmRest
|
|
38
38
|
private
|
39
39
|
|
40
40
|
def fmrest_connection
|
41
|
-
@fmrest_connection ||=
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
41
|
+
@fmrest_connection ||=
|
42
|
+
begin
|
43
|
+
config = fmrest_config || FmRest.default_connection_settings
|
44
|
+
|
45
|
+
FmRest::V1.build_connection(config) do |conn|
|
46
|
+
faraday_block.call(conn) if faraday_block
|
47
|
+
|
48
|
+
# Pass the class to SpykeFormatter's initializer so it can have
|
49
|
+
# access to extra context defined in the model, e.g. a portal
|
50
|
+
# where name of the portal and the attributes prefix don't match
|
51
|
+
# and need to be specified as options to `portal`
|
52
|
+
conn.use FmRest::Spyke::SpykeFormatter, self
|
53
|
+
|
54
|
+
conn.use FmRest::V1::TypeCoercer, config
|
55
|
+
|
56
|
+
# FmRest::Spyke::JsonParse expects symbol keys
|
57
|
+
conn.response :json, parser_options: { symbolize_names: true }
|
58
|
+
end
|
59
|
+
end
|
50
60
|
end
|
51
61
|
end
|
52
62
|
end
|
@@ -6,7 +6,7 @@ module FmRest
|
|
6
6
|
module Spyke
|
7
7
|
# Response Faraday middleware for converting FM API's response JSON into
|
8
8
|
# Spyke's expected format
|
9
|
-
class
|
9
|
+
class SpykeFormatter < ::Faraday::Response::Middleware
|
10
10
|
SINGLE_RECORD_RE = %r(/records/\d+\z).freeze
|
11
11
|
MULTIPLE_RECORDS_RE = %r(/records\z).freeze
|
12
12
|
CONTAINER_RE = %r(/records/\d+/containers/[^/]+/\d+\z).freeze
|
@@ -24,7 +24,9 @@ module FmRest
|
|
24
24
|
|
25
25
|
# @param env [Faraday::Env]
|
26
26
|
def on_complete(env)
|
27
|
-
|
27
|
+
return unless env.body.is_a?(Hash)
|
28
|
+
|
29
|
+
json = env.body
|
28
30
|
|
29
31
|
case
|
30
32
|
when single_record_request?(env)
|
@@ -229,12 +231,6 @@ module FmRest
|
|
229
231
|
def execute_script_request?(env)
|
230
232
|
env.method == :get && env.url.path.match(SCRIPT_REQUEST_RE)
|
231
233
|
end
|
232
|
-
|
233
|
-
# @param source [String] a JSON string
|
234
|
-
# @return [Hash] the parsed JSON
|
235
|
-
def parse_json(source)
|
236
|
-
JSON.parse(source, symbolize_names: true)
|
237
|
-
end
|
238
234
|
end
|
239
235
|
end
|
240
236
|
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
module FmRest
|
6
|
+
# Gotchas:
|
7
|
+
#
|
8
|
+
# 1.
|
9
|
+
#
|
10
|
+
# Date === <StringDate instance> # => false
|
11
|
+
#
|
12
|
+
# The above can affect case conditions, as trying to match a StringDate
|
13
|
+
# with:
|
14
|
+
#
|
15
|
+
# case obj
|
16
|
+
# when Date
|
17
|
+
# ...
|
18
|
+
#
|
19
|
+
# ...will not work.
|
20
|
+
#
|
21
|
+
# Instead one must specify the FmRest::StringDate class:
|
22
|
+
#
|
23
|
+
# case obj
|
24
|
+
# when Date, FmRest::StringDate
|
25
|
+
# ...
|
26
|
+
#
|
27
|
+
# 2.
|
28
|
+
#
|
29
|
+
# StringDate#eql? only matches other strings, not dates.
|
30
|
+
#
|
31
|
+
# This could affect hash indexing when a StringDate is used as a key.
|
32
|
+
#
|
33
|
+
# TODO: Verify the above
|
34
|
+
#
|
35
|
+
# 3.
|
36
|
+
#
|
37
|
+
# StringDate#succ and StringDate#next return a String, despite Date#succ
|
38
|
+
# and Date#next also existing.
|
39
|
+
#
|
40
|
+
# Workaround: Use StringDate#next_day or strdate + 1
|
41
|
+
#
|
42
|
+
# 4.
|
43
|
+
#
|
44
|
+
# StringDate#to_s returns the original string, not the Date string
|
45
|
+
# representation.
|
46
|
+
#
|
47
|
+
# Workaround: Use strdate.to_date.to_s
|
48
|
+
#
|
49
|
+
# 5.
|
50
|
+
#
|
51
|
+
# StringDate#hash returns the hash for the string (important when using
|
52
|
+
# a StringDate as a hash key)
|
53
|
+
#
|
54
|
+
# 6.
|
55
|
+
#
|
56
|
+
# StringDate#as_json returns the string
|
57
|
+
#
|
58
|
+
# Workaround: Use strdate.to_date.as_json
|
59
|
+
#
|
60
|
+
# 7.
|
61
|
+
#
|
62
|
+
# Equality with Date is not reciprocal:
|
63
|
+
#
|
64
|
+
# str_date == date #=> true
|
65
|
+
# date == str_date #=> false
|
66
|
+
#
|
67
|
+
# NOTE: Potential workaround: Inherit StringDate from Date instead of String
|
68
|
+
#
|
69
|
+
# 8.
|
70
|
+
#
|
71
|
+
# Calling string transforming methods (e.g. .upcase) returns a StringDate
|
72
|
+
# instead of a String.
|
73
|
+
#
|
74
|
+
# NOTE: Potential workaround: Inherit StringDate from Date instead of String
|
75
|
+
#
|
76
|
+
class StringDate < String
|
77
|
+
DELEGATE_CLASS = ::Date
|
78
|
+
|
79
|
+
class InvalidDate < ArgumentError; end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
alias_method :strptime, :new
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(str, date_format, **str_args)
|
86
|
+
super(str, **str_args)
|
87
|
+
|
88
|
+
begin
|
89
|
+
@delegate = self.class::DELEGATE_CLASS.strptime(str, date_format)
|
90
|
+
rescue ArgumentError
|
91
|
+
raise InvalidDate
|
92
|
+
end
|
93
|
+
|
94
|
+
freeze
|
95
|
+
end
|
96
|
+
|
97
|
+
def is_a?(klass)
|
98
|
+
klass == ::Date || super
|
99
|
+
end
|
100
|
+
alias_method :kind_of?, :is_a?
|
101
|
+
|
102
|
+
def to_date
|
103
|
+
@delegate
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_datetime
|
107
|
+
@delegate.to_datetime
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_time
|
111
|
+
@delegate.to_time
|
112
|
+
end
|
113
|
+
|
114
|
+
# ActiveSupport method
|
115
|
+
def in_time_zone(*_)
|
116
|
+
@delegate.in_time_zone(*_)
|
117
|
+
end
|
118
|
+
|
119
|
+
def inspect
|
120
|
+
"#<#{self.class.name} #{@delegate.inspect} - #{super}>"
|
121
|
+
end
|
122
|
+
|
123
|
+
def <=>(oth)
|
124
|
+
return @delegate <=> oth if oth.is_a?(::Date) || oth.is_a?(Numeric)
|
125
|
+
super
|
126
|
+
end
|
127
|
+
|
128
|
+
def +(val)
|
129
|
+
return @delegate + val if val.kind_of?(Numeric)
|
130
|
+
super
|
131
|
+
end
|
132
|
+
|
133
|
+
def <<(val)
|
134
|
+
return @delegate << val if val.kind_of?(Numeric)
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
def ==(oth)
|
139
|
+
return @delegate == oth if oth.kind_of?(::Date) || oth.kind_of?(Numeric)
|
140
|
+
super
|
141
|
+
end
|
142
|
+
alias_method :===, :==
|
143
|
+
|
144
|
+
def upto(oth, &blk)
|
145
|
+
return @delegate.upto(oth, &blk) if oth.kind_of?(::Date) || oth.kind_of?(Numeric)
|
146
|
+
super
|
147
|
+
end
|
148
|
+
|
149
|
+
def between?(a, b)
|
150
|
+
return @delegate.between?(a, b) if [a, b].any? {|o| o.is_a?(::Date) || o.is_a?(Numeric) }
|
151
|
+
super
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def respond_to_missing?(name, include_private = false)
|
157
|
+
@delegate.respond_to?(name, include_private)
|
158
|
+
end
|
159
|
+
|
160
|
+
def method_missing(method, *args, &block)
|
161
|
+
@delegate.send(method, *args, &block)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class StringDateTime < StringDate
|
166
|
+
DELEGATE_CLASS = ::DateTime
|
167
|
+
|
168
|
+
def is_a?(klass)
|
169
|
+
klass == ::DateTime || super
|
170
|
+
end
|
171
|
+
alias_method :kind_of?, :is_a?
|
172
|
+
|
173
|
+
def to_date
|
174
|
+
@delegate.to_date
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_datetime
|
178
|
+
@delegate
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
data/lib/fmrest/v1.rb
CHANGED
@@ -7,6 +7,10 @@ require "fmrest/v1/utils"
|
|
7
7
|
|
8
8
|
module FmRest
|
9
9
|
module V1
|
10
|
+
DEFAULT_DATE_FORMAT = "MM/dd/yyyy"
|
11
|
+
DEFAULT_TIME_FORMAT = "HH:mm:ss"
|
12
|
+
DEFAULT_TIMESTAMP_FORMAT = "#{DEFAULT_DATE_FORMAT} #{DEFAULT_TIME_FORMAT}"
|
13
|
+
|
10
14
|
extend Connection
|
11
15
|
extend Paths
|
12
16
|
extend ContainerFields
|
data/lib/fmrest/v1/connection.rb
CHANGED
@@ -5,7 +5,7 @@ require "uri"
|
|
5
5
|
module FmRest
|
6
6
|
module V1
|
7
7
|
module Connection
|
8
|
-
BASE_PATH = "/fmi/data/v1/databases"
|
8
|
+
BASE_PATH = "/fmi/data/v1/databases"
|
9
9
|
|
10
10
|
# Builds a complete DAPI Faraday connection with middleware already
|
11
11
|
# configured to handle authentication, JSON parsing, logging and DAPI
|
@@ -19,7 +19,6 @@ module FmRest
|
|
19
19
|
# @option (see #base_connection)
|
20
20
|
# @return (see #base_connection)
|
21
21
|
def build_connection(options = FmRest.default_connection_settings, &block)
|
22
|
-
|
23
22
|
base_connection(options) do |conn|
|
24
23
|
conn.use RaiseErrors
|
25
24
|
conn.use TokenSession, options
|
@@ -38,8 +37,9 @@ module FmRest
|
|
38
37
|
|
39
38
|
# Allow overriding the default response middleware
|
40
39
|
if block_given?
|
41
|
-
yield conn
|
40
|
+
yield conn, options
|
42
41
|
else
|
42
|
+
conn.use TypeCoercer, options
|
43
43
|
conn.response :json
|
44
44
|
end
|
45
45
|
|
@@ -86,3 +86,4 @@ end
|
|
86
86
|
|
87
87
|
require "fmrest/v1/token_session"
|
88
88
|
require "fmrest/v1/raise_errors"
|
89
|
+
require "fmrest/v1/type_coercer"
|
@@ -14,6 +14,8 @@ module FmRest
|
|
14
14
|
TOKEN_STORE_INTERFACE = [:load, :store, :delete].freeze
|
15
15
|
LOGOUT_PATH_MATCHER = %r{\A(#{FmRest::V1::Connection::BASE_PATH}/[^/]+/sessions/)[^/]+\Z}.freeze
|
16
16
|
|
17
|
+
# @param app [#call]
|
18
|
+
# @param options [Hash]
|
17
19
|
def initialize(app, options = FmRest.default_connection_settings)
|
18
20
|
super(app)
|
19
21
|
@options = options
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fmrest/string_date"
|
4
|
+
|
5
|
+
module FmRest
|
6
|
+
module V1
|
7
|
+
class TypeCoercer < Faraday::Response::Middleware
|
8
|
+
# We use this date to represent a time for consistency with ginjo-rfm
|
9
|
+
JULIAN_ZERO_DAY = "-4712/1/1"
|
10
|
+
|
11
|
+
COERCE_HYBRID = [:hybrid, "hybrid", true].freeze
|
12
|
+
COERCE_FULL = [:full, "full"].freeze
|
13
|
+
|
14
|
+
# @param app [#call]
|
15
|
+
# @param options [Hash]
|
16
|
+
def initialize(app, options = FmRest.default_connection_settings)
|
17
|
+
super(app)
|
18
|
+
@options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_complete(env)
|
22
|
+
return unless enabled?
|
23
|
+
return unless env.body.kind_of?(Hash)
|
24
|
+
|
25
|
+
data = env.body.dig("response", "data") || env.body.dig(:response, :data)
|
26
|
+
|
27
|
+
return unless data
|
28
|
+
|
29
|
+
data.each do |record|
|
30
|
+
field_data = record["fieldData"] || record[:fieldData]
|
31
|
+
portal_data = record["portalData"] || record[:portalData]
|
32
|
+
|
33
|
+
# Build an enumerator that iterates over hashes of fields
|
34
|
+
enum = Enumerator.new { |y| y << field_data }
|
35
|
+
if portal_data
|
36
|
+
portal_data.each_value do |portal_records|
|
37
|
+
enum += portal_records.to_enum
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
enum.each { |hash| coerce_fields(hash) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def coerce_fields(hash)
|
48
|
+
hash.each do |k, v|
|
49
|
+
next unless v.is_a?(String)
|
50
|
+
next if k == "recordId" || k == :recordId || k == "modId" || k == :modId
|
51
|
+
|
52
|
+
begin
|
53
|
+
str_timestamp = datetime_class.strptime(v, datetime_format)
|
54
|
+
hash[k] = str_timestamp
|
55
|
+
next
|
56
|
+
rescue ArgumentError
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
str_date = date_class.strptime(v, date_format)
|
61
|
+
hash[k] = str_date
|
62
|
+
next
|
63
|
+
rescue ArgumentError
|
64
|
+
end
|
65
|
+
|
66
|
+
begin
|
67
|
+
str_time = datetime_class.strptime("#{JULIAN_ZERO_DAY} #{v}", time_format)
|
68
|
+
hash[k] = str_time
|
69
|
+
next
|
70
|
+
rescue ArgumentError
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def date_class
|
76
|
+
@date_class ||=
|
77
|
+
case coerce_dates
|
78
|
+
when *COERCE_HYBRID
|
79
|
+
StringDate
|
80
|
+
when *COERCE_FULL
|
81
|
+
Date
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def datetime_class
|
86
|
+
@datetime_class ||=
|
87
|
+
case coerce_dates
|
88
|
+
when *COERCE_HYBRID
|
89
|
+
StringDateTime
|
90
|
+
when *COERCE_FULL
|
91
|
+
DateTime
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def date_format
|
96
|
+
@date_format ||=
|
97
|
+
FmRest::V1.convert_date_time_format(@options[:date_format] || DEFAULT_DATE_FORMAT)
|
98
|
+
end
|
99
|
+
|
100
|
+
def datetime_format
|
101
|
+
@datetime_format ||=
|
102
|
+
FmRest::V1.convert_date_time_format(@options[:timestamp_format] || DEFAULT_TIMESTAMP_FORMAT)
|
103
|
+
end
|
104
|
+
|
105
|
+
def time_format
|
106
|
+
@time_format ||=
|
107
|
+
"%Y/%m/%d " + FmRest::V1.convert_date_time_format(@options[:time_format] || DEFAULT_TIME_FORMAT)
|
108
|
+
end
|
109
|
+
|
110
|
+
def coerce_dates
|
111
|
+
@options.fetch(:coerce_dates, false)
|
112
|
+
end
|
113
|
+
|
114
|
+
alias_method :enabled?, :coerce_dates
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/fmrest/v1/utils.rb
CHANGED
@@ -5,6 +5,17 @@ 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
|
+
|
8
19
|
# Converts custom script options to a hash with the Data API's expected
|
9
20
|
# JSON script format.
|
10
21
|
#
|
@@ -72,6 +83,12 @@ module FmRest
|
|
72
83
|
params
|
73
84
|
end
|
74
85
|
|
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
|
private
|
76
93
|
|
77
94
|
def convert_script_arguments(script_arguments, suffix = nil)
|
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.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pedro Carbajal
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -231,7 +231,6 @@ files:
|
|
231
231
|
- ".travis.yml"
|
232
232
|
- ".yardopts"
|
233
233
|
- CHANGELOG.md
|
234
|
-
- CODE_OF_CONDUCT.md
|
235
234
|
- Gemfile
|
236
235
|
- LICENSE.txt
|
237
236
|
- README.md
|
@@ -242,7 +241,6 @@ files:
|
|
242
241
|
- lib/fmrest/spyke.rb
|
243
242
|
- lib/fmrest/spyke/base.rb
|
244
243
|
- lib/fmrest/spyke/container_field.rb
|
245
|
-
- lib/fmrest/spyke/json_parser.rb
|
246
244
|
- lib/fmrest/spyke/model.rb
|
247
245
|
- lib/fmrest/spyke/model/associations.rb
|
248
246
|
- lib/fmrest/spyke/model/attributes.rb
|
@@ -255,7 +253,9 @@ files:
|
|
255
253
|
- lib/fmrest/spyke/model/uri.rb
|
256
254
|
- lib/fmrest/spyke/portal.rb
|
257
255
|
- lib/fmrest/spyke/relation.rb
|
256
|
+
- lib/fmrest/spyke/spyke_formatter.rb
|
258
257
|
- lib/fmrest/spyke/validation_error.rb
|
258
|
+
- lib/fmrest/string_date.rb
|
259
259
|
- lib/fmrest/token_store.rb
|
260
260
|
- lib/fmrest/token_store/active_record.rb
|
261
261
|
- lib/fmrest/token_store/base.rb
|
@@ -270,6 +270,7 @@ files:
|
|
270
270
|
- lib/fmrest/v1/token_session.rb
|
271
271
|
- lib/fmrest/v1/token_store/active_record.rb
|
272
272
|
- lib/fmrest/v1/token_store/memory.rb
|
273
|
+
- lib/fmrest/v1/type_coercer.rb
|
273
274
|
- lib/fmrest/v1/utils.rb
|
274
275
|
- lib/fmrest/version.rb
|
275
276
|
homepage: https://github.com/beezwax/fmrest-ruby
|
data/CODE_OF_CONDUCT.md
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
# Contributor Covenant Code of Conduct
|
2
|
-
|
3
|
-
## Our Pledge
|
4
|
-
|
5
|
-
In the interest of fostering an open and welcoming environment, we as
|
6
|
-
contributors and maintainers pledge to making participation in our project and
|
7
|
-
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
-
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
-
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
-
orientation.
|
11
|
-
|
12
|
-
## Our Standards
|
13
|
-
|
14
|
-
Examples of behavior that contributes to creating a positive environment
|
15
|
-
include:
|
16
|
-
|
17
|
-
* Using welcoming and inclusive language
|
18
|
-
* Being respectful of differing viewpoints and experiences
|
19
|
-
* Gracefully accepting constructive criticism
|
20
|
-
* Focusing on what is best for the community
|
21
|
-
* Showing empathy towards other community members
|
22
|
-
|
23
|
-
Examples of unacceptable behavior by participants include:
|
24
|
-
|
25
|
-
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
-
advances
|
27
|
-
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
-
* Public or private harassment
|
29
|
-
* Publishing others' private information, such as a physical or electronic
|
30
|
-
address, without explicit permission
|
31
|
-
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
-
professional setting
|
33
|
-
|
34
|
-
## Our Responsibilities
|
35
|
-
|
36
|
-
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
-
behavior and are expected to take appropriate and fair corrective action in
|
38
|
-
response to any instances of unacceptable behavior.
|
39
|
-
|
40
|
-
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
-
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
-
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
-
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
-
threatening, offensive, or harmful.
|
45
|
-
|
46
|
-
## Scope
|
47
|
-
|
48
|
-
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
-
when an individual is representing the project or its community. Examples of
|
50
|
-
representing a project or community include using an official project e-mail
|
51
|
-
address, posting via an official social media account, or acting as an appointed
|
52
|
-
representative at an online or offline event. Representation of a project may be
|
53
|
-
further defined and clarified by project maintainers.
|
54
|
-
|
55
|
-
## Enforcement
|
56
|
-
|
57
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
-
reported by contacting the project team at pedro_c@beezwax.net. All
|
59
|
-
complaints will be reviewed and investigated and will result in a response that
|
60
|
-
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
-
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
-
Further details of specific enforcement policies may be posted separately.
|
63
|
-
|
64
|
-
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
-
faith may face temporary or permanent repercussions as determined by other
|
66
|
-
members of the project's leadership.
|
67
|
-
|
68
|
-
## Attribution
|
69
|
-
|
70
|
-
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
-
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
-
|
73
|
-
[homepage]: http://contributor-covenant.org
|
74
|
-
[version]: http://contributor-covenant.org/version/1/4/
|