fmrest-core 0.13.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 +7 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +127 -0
- data/LICENSE.txt +21 -0
- data/README.md +523 -0
- data/lib/fmrest-core.rb +3 -0
- data/lib/fmrest-spyke.rb +3 -0
- data/lib/fmrest.rb +36 -0
- data/lib/fmrest/connection_settings.rb +124 -0
- data/lib/fmrest/errors.rb +30 -0
- data/lib/fmrest/string_date.rb +220 -0
- data/lib/fmrest/token_store.rb +12 -0
- data/lib/fmrest/token_store/active_record.rb +74 -0
- data/lib/fmrest/token_store/base.rb +25 -0
- data/lib/fmrest/token_store/memory.rb +26 -0
- data/lib/fmrest/token_store/moneta.rb +41 -0
- data/lib/fmrest/token_store/null.rb +20 -0
- data/lib/fmrest/token_store/redis.rb +45 -0
- data/lib/fmrest/v1.rb +23 -0
- data/lib/fmrest/v1/auth.rb +30 -0
- data/lib/fmrest/v1/connection.rb +116 -0
- data/lib/fmrest/v1/container_fields.rb +114 -0
- data/lib/fmrest/v1/dates.rb +81 -0
- data/lib/fmrest/v1/paths.rb +42 -0
- data/lib/fmrest/v1/raise_errors.rb +57 -0
- data/lib/fmrest/v1/token_session.rb +133 -0
- data/lib/fmrest/v1/token_store/active_record.rb +13 -0
- data/lib/fmrest/v1/token_store/memory.rb +13 -0
- data/lib/fmrest/v1/type_coercer.rb +192 -0
- data/lib/fmrest/v1/utils.rb +113 -0
- data/lib/fmrest/version.rb +5 -0
- metadata +115 -0
@@ -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
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FmRest
|
4
|
+
module V1
|
5
|
+
module Paths
|
6
|
+
def session_path(token = nil)
|
7
|
+
url = "sessions"
|
8
|
+
url += "/#{token}" if token
|
9
|
+
url
|
10
|
+
end
|
11
|
+
|
12
|
+
def record_path(layout, id = nil)
|
13
|
+
url = "layouts/#{V1.url_encode(layout)}/records"
|
14
|
+
url += "/#{id}" if id
|
15
|
+
url
|
16
|
+
end
|
17
|
+
|
18
|
+
def container_field_path(layout, id, field_name, field_repetition = 1)
|
19
|
+
url = record_path(layout, id)
|
20
|
+
url += "/containers/#{V1.url_encode(field_name)}"
|
21
|
+
url += "/#{field_repetition}" if field_repetition
|
22
|
+
url
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_path(layout)
|
26
|
+
"layouts/#{V1.url_encode(layout)}/_find"
|
27
|
+
end
|
28
|
+
|
29
|
+
def script_path(layout, script)
|
30
|
+
"layouts/#{V1.url_encode(layout)}/script/#{V1.url_encode(script)}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def globals_path
|
34
|
+
"globals"
|
35
|
+
end
|
36
|
+
|
37
|
+
def product_info_path
|
38
|
+
"#{V1::Connection::BASE_PATH}/productInfo"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FmRest
|
4
|
+
module V1
|
5
|
+
# FM Data API response middleware for raising exceptions on API response
|
6
|
+
# errors
|
7
|
+
#
|
8
|
+
# https://fmhelp.filemaker.com/help/17/fmp/en/index.html#page/FMP_Help/error-codes.html
|
9
|
+
#
|
10
|
+
class RaiseErrors < Faraday::Response::Middleware
|
11
|
+
# https://fmhelp.filemaker.com/help/17/fmp/en/index.html#page/FMP_Help/error-codes.html
|
12
|
+
ERROR_RANGES = {
|
13
|
+
-1 => APIError::UnknownError,
|
14
|
+
100 => APIError::ResourceMissingError,
|
15
|
+
101 => APIError::RecordMissingError,
|
16
|
+
102..199 => APIError::ResourceMissingError,
|
17
|
+
200..299 => APIError::AccountError,
|
18
|
+
300..399 => APIError::LockError,
|
19
|
+
400 => APIError::ParameterError,
|
20
|
+
401 => APIError::NoMatchingRecordsError,
|
21
|
+
402..499 => APIError::ParameterError,
|
22
|
+
500..599 => APIError::ValidationError,
|
23
|
+
800..899 => APIError::SystemError,
|
24
|
+
952 => APIError::InvalidToken,
|
25
|
+
953 => APIError::MaximumDataAPICallsExceeded,
|
26
|
+
1200..1299 => APIError::ScriptError,
|
27
|
+
1400..1499 => APIError::ODBCError
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
def on_complete(env)
|
31
|
+
# Sniff for either straight JSON parsing or Spyke's format
|
32
|
+
if env.body[:metadata] && env.body[:metadata][:messages]
|
33
|
+
check_errors(env.body[:metadata][:messages])
|
34
|
+
elsif env.body["messages"]
|
35
|
+
check_errors(env.body["messages"])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def check_errors(messages)
|
42
|
+
messages.each do |message|
|
43
|
+
error_code = (message["code"] || message[:code]).to_i
|
44
|
+
|
45
|
+
# Code 0 means "No Error"
|
46
|
+
next if error_code.zero?
|
47
|
+
|
48
|
+
error_message = message["message"] || message[:message]
|
49
|
+
|
50
|
+
*, exception_class = ERROR_RANGES.find { |k, v| k === error_code }
|
51
|
+
|
52
|
+
raise (exception_class || APIError).new(error_code, error_message)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fmrest/v1/connection"
|
4
|
+
|
5
|
+
module FmRest
|
6
|
+
module V1
|
7
|
+
# FM Data API authentication middleware using the credentials strategy
|
8
|
+
#
|
9
|
+
class TokenSession < Faraday::Middleware
|
10
|
+
class NoSessionTokenSet < FmRest::Error; end
|
11
|
+
|
12
|
+
HEADER_KEY = "Authorization"
|
13
|
+
TOKEN_STORE_INTERFACE = [:load, :store, :delete].freeze
|
14
|
+
LOGOUT_PATH_MATCHER = %r{\A(#{FmRest::V1::Connection::DATABASES_PATH}/[^/]+/sessions/)[^/]+\Z}.freeze
|
15
|
+
|
16
|
+
# @param app [#call]
|
17
|
+
# @param settings [FmRest::ConnectionSettings]
|
18
|
+
def initialize(app, settings)
|
19
|
+
super(app)
|
20
|
+
@settings = settings
|
21
|
+
end
|
22
|
+
|
23
|
+
# Entry point for the middleware when sending a request
|
24
|
+
#
|
25
|
+
def call(env)
|
26
|
+
return handle_logout(env) if is_logout_request?(env)
|
27
|
+
|
28
|
+
set_auth_header(env)
|
29
|
+
|
30
|
+
request_body = env[:body] # After failure env[:body] is set to the response body
|
31
|
+
|
32
|
+
@app.call(env).on_complete do |response_env|
|
33
|
+
if response_env[:status] == 401 # Unauthorized
|
34
|
+
delete_token_store_key
|
35
|
+
|
36
|
+
if @settings.autologin
|
37
|
+
env[:body] = request_body
|
38
|
+
set_auth_header(env)
|
39
|
+
return @app.call(env)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def delete_token_store_key
|
48
|
+
token_store.delete(token_store_key)
|
49
|
+
# Sometimes we may want to pass the :token in settings manually, and
|
50
|
+
# refrain from passing a :username. In that case the call to
|
51
|
+
# #token_store_key above would fail as it tries to fetch :username, so
|
52
|
+
# we purposely ignore that error.
|
53
|
+
rescue FmRest::ConnectionSettings::MissingSetting
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_logout(env)
|
57
|
+
token = @settings.token? ? @settings.token : token_store.load(token_store_key)
|
58
|
+
|
59
|
+
raise NoSessionTokenSet, "Couldn't send logout request because no session token was set" unless token
|
60
|
+
|
61
|
+
env.url.path = env.url.path.gsub(LOGOUT_PATH_MATCHER, "\\1#{token}")
|
62
|
+
|
63
|
+
@app.call(env).on_complete do |response_env|
|
64
|
+
delete_token_store_key if response_env[:status] == 200
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def is_logout_request?(env)
|
69
|
+
return false unless env.method == :delete
|
70
|
+
return env.url.path.match?(LOGOUT_PATH_MATCHER)
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_auth_header(env)
|
74
|
+
env.request_headers[HEADER_KEY] = "Bearer #{token}"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Uses the token given in connection settings if available,
|
78
|
+
# otherwisek tries to get an existing token from the token store,
|
79
|
+
# otherwise requests one through basic auth,
|
80
|
+
# otherwise raises an exception.
|
81
|
+
#
|
82
|
+
def token
|
83
|
+
return @settings.token if @settings.token?
|
84
|
+
|
85
|
+
token = token_store.load(token_store_key)
|
86
|
+
return token if token
|
87
|
+
|
88
|
+
return nil unless @settings.autologin
|
89
|
+
|
90
|
+
token = V1.request_auth_token!(auth_connection)
|
91
|
+
token_store.store(token_store_key, token)
|
92
|
+
token
|
93
|
+
end
|
94
|
+
|
95
|
+
# The key to use to store a token, uses the format host:database:username
|
96
|
+
#
|
97
|
+
def token_store_key
|
98
|
+
@token_store_key ||=
|
99
|
+
begin
|
100
|
+
# Strip the host part to just the hostname (i.e. no scheme or port)
|
101
|
+
host = @settings.host!
|
102
|
+
host = URI(host).hostname if host =~ /\Ahttps?:\/\//
|
103
|
+
"#{host}:#{@settings.database!}:#{@settings.username!}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def token_store
|
108
|
+
@token_store ||=
|
109
|
+
begin
|
110
|
+
if TOKEN_STORE_INTERFACE.all? { |method| token_store_option.respond_to?(method) }
|
111
|
+
token_store_option
|
112
|
+
elsif token_store_option.kind_of?(Class)
|
113
|
+
if token_store_option.respond_to?(:instance)
|
114
|
+
token_store_option.instance
|
115
|
+
else
|
116
|
+
token_store_option.new
|
117
|
+
end
|
118
|
+
else
|
119
|
+
FmRest::TokenStore::Memory.new
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def token_store_option
|
125
|
+
@settings.token_store || FmRest.token_store
|
126
|
+
end
|
127
|
+
|
128
|
+
def auth_connection
|
129
|
+
@auth_connection ||= V1.auth_connection(@settings)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
warn "FmRest::V1::TokenStore::ActiveRecord is deprecated, use FmRest::TokenStore::ActiveRecord instead"
|
4
|
+
|
5
|
+
require "fmrest/token_store/active_record"
|
6
|
+
|
7
|
+
module FmRest
|
8
|
+
module V1
|
9
|
+
module TokenStore
|
10
|
+
ActiveRecord = ::FmRest::TokenStore::ActiveRecord
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
warn "FmRest::V1::TokenStore::Memory is deprecated, use FmRest::TokenStore::Memory instead"
|
4
|
+
|
5
|
+
require "fmrest/token_store/memory"
|
6
|
+
|
7
|
+
module FmRest
|
8
|
+
module V1
|
9
|
+
module TokenStore
|
10
|
+
Memory = ::FmRest::TokenStore::Memory
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,192 @@
|
|
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 FileMaker time for consistency with
|
9
|
+
# ginjo-rfm
|
10
|
+
JULIAN_ZERO_DAY = "-4712/1/1"
|
11
|
+
|
12
|
+
COERCE_HYBRID = [:hybrid, "hybrid", true].freeze
|
13
|
+
COERCE_FULL = [:full, "full"].freeze
|
14
|
+
|
15
|
+
# @param app [#call]
|
16
|
+
# @param settings [FmRest::ConnectionSettings]
|
17
|
+
def initialize(app, settings)
|
18
|
+
super(app)
|
19
|
+
@settings = settings
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_complete(env)
|
23
|
+
return unless enabled?
|
24
|
+
return unless env.body.kind_of?(Hash)
|
25
|
+
|
26
|
+
data = env.body.dig("response", "data") || env.body.dig(:response, :data)
|
27
|
+
|
28
|
+
return unless data
|
29
|
+
|
30
|
+
data.each do |record|
|
31
|
+
field_data = record["fieldData"] || record[:fieldData]
|
32
|
+
portal_data = record["portalData"] || record[:portalData]
|
33
|
+
|
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)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def coerce_fields(hash)
|
47
|
+
hash.each do |k, v|
|
48
|
+
next unless v.is_a?(String)
|
49
|
+
next if k == "recordId" || k == :recordId || k == "modId" || k == :modId
|
50
|
+
|
51
|
+
if quick_check_timestamp(v)
|
52
|
+
begin
|
53
|
+
hash[k] = coerce_timestamp(v)
|
54
|
+
next
|
55
|
+
rescue ArgumentError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
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
|
65
|
+
end
|
66
|
+
|
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
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
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
|
+
|
94
|
+
def date_class
|
95
|
+
@date_class ||=
|
96
|
+
case coerce_dates
|
97
|
+
when *COERCE_HYBRID
|
98
|
+
StringDate
|
99
|
+
when *COERCE_FULL
|
100
|
+
Date
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def datetime_class
|
105
|
+
@datetime_class ||=
|
106
|
+
case coerce_dates
|
107
|
+
when *COERCE_HYBRID
|
108
|
+
StringDateTime
|
109
|
+
when *COERCE_FULL
|
110
|
+
DateTime
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def date_fm_format
|
115
|
+
@settings.date_format
|
116
|
+
end
|
117
|
+
|
118
|
+
def timestamp_fm_format
|
119
|
+
@settings.timestamp_format
|
120
|
+
end
|
121
|
+
|
122
|
+
def time_fm_format
|
123
|
+
@settings.time_format
|
124
|
+
end
|
125
|
+
|
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
|
179
|
+
end
|
180
|
+
|
181
|
+
def local_timezone?
|
182
|
+
@local_timezone ||= @settings.timezone.try(:to_sym) == :local
|
183
|
+
end
|
184
|
+
|
185
|
+
def coerce_dates
|
186
|
+
@settings.coerce_dates
|
187
|
+
end
|
188
|
+
|
189
|
+
alias_method :enabled?, :coerce_dates
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|