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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 185e6173a3f500ea6fc8ad40df8f52ba538fdd8e3545537e947f19bfd79a3124
4
- data.tar.gz: 89f5c6774366599e1baeef57662af77a3ceb5174abd58e6fd562c2aa29b85384
3
+ metadata.gz: ab46f70bffcfea32ef2bfd5021f03fa654bb43134e22b8e22a20d9955afe9caf
4
+ data.tar.gz: 32ce4378ff291d2c41d9b255b032d4562d7544a477765748c87c49a655449405
5
5
  SHA512:
6
- metadata.gz: b7aa56e455da60fe7b571e2ce4d70ae118ca7ade57356cbdade2b0679f66d10e9d689a4bc3af5ddfb06dfc6d7395af35b0518c23077dbfb28a9e395b845e0841
7
- data.tar.gz: c764ee1dc1962b3f6264ed6a4f26f0b88dd5c69cb62bca93dd269398c3e0d350ced4177b2842ce24136d7d0b84cb9a4809ce23ca1965b923fb91fc7b37ae39e1
6
+ metadata.gz: a68af2d8e53631b1d5c4d9e5e0849d224ff3918d1138d437463e30318ae81c36ca968d226f2f3631dbaa0d21b6287c1899e3b322a716729ad0d8b77aff80fd8d
7
+ data.tar.gz: ace50f913960e6b2edd190c8c1cbb75a0ebf15c0c3f0e8343720155f1c53b78bb9fd942be0a5e9bf2a2bc1ff729277f86360ffa4bba397ba48abe86c07db02ad
@@ -1,5 +1,9 @@
1
1
  ## Changelog
2
2
 
3
+ ### 0.7.0
4
+
5
+ * Added date coercion feature
6
+
3
7
  ### 0.6.0
4
8
 
5
9
  * Implemented session logout
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).
@@ -8,7 +8,7 @@ rescue LoadError => e
8
8
  end
9
9
 
10
10
  require "fmrest"
11
- require "fmrest/spyke/json_parser"
11
+ require "fmrest/spyke/spyke_formatter"
12
12
  require "fmrest/spyke/model"
13
13
  require "fmrest/spyke/base"
14
14
 
@@ -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 JsonParser
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 JsonParser to use if needed
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 ||= FmRest::V1.build_connection(fmrest_config || FmRest.default_connection_settings) do |conn|
42
- faraday_block.call(conn) if faraday_block
43
-
44
- # Pass the class to JsonParser's initializer so it can have
45
- # access to extra context defined in the model, e.g. a portal
46
- # where name of the portal and the attributes prefix don't match
47
- # and need to be specified as options to `portal`
48
- conn.use FmRest::Spyke::JsonParser, self
49
- end
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 JsonParser < ::Faraday::Response::Middleware
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
- json = parse_json(env.body)
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
@@ -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
@@ -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".freeze
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
@@ -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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FmRest
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
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.6.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-05 00:00:00.000000000 Z
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
@@ -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/