fmrest 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.yardopts +1 -0
- data/README.md +101 -7
- data/fmrest.gemspec +3 -0
- data/lib/fmrest.rb +2 -0
- data/lib/fmrest/errors.rb +27 -0
- data/lib/fmrest/spyke.rb +9 -0
- data/lib/fmrest/spyke/base.rb +2 -0
- data/lib/fmrest/spyke/container_field.rb +59 -0
- data/lib/fmrest/spyke/json_parser.rb +83 -24
- data/lib/fmrest/spyke/model.rb +7 -0
- data/lib/fmrest/spyke/model/associations.rb +2 -0
- data/lib/fmrest/spyke/model/attributes.rb +14 -55
- data/lib/fmrest/spyke/model/connection.rb +2 -0
- data/lib/fmrest/spyke/model/container_fields.rb +25 -0
- data/lib/fmrest/spyke/model/orm.rb +72 -5
- data/lib/fmrest/spyke/model/serialization.rb +80 -0
- data/lib/fmrest/spyke/model/uri.rb +2 -0
- data/lib/fmrest/spyke/portal.rb +2 -0
- data/lib/fmrest/spyke/relation.rb +30 -14
- data/lib/fmrest/token_store.rb +6 -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/redis.rb +45 -0
- data/lib/fmrest/v1.rb +10 -49
- data/lib/fmrest/v1/connection.rb +57 -0
- data/lib/fmrest/v1/container_fields.rb +73 -0
- data/lib/fmrest/v1/paths.rb +36 -0
- data/lib/fmrest/v1/raise_errors.rb +55 -0
- data/lib/fmrest/v1/token_session.rb +32 -12
- data/lib/fmrest/v1/token_store/active_record.rb +6 -66
- data/lib/fmrest/v1/token_store/memory.rb +6 -19
- data/lib/fmrest/v1/utils.rb +94 -0
- data/lib/fmrest/version.rb +3 -1
- metadata +60 -5
- data/lib/fmrest/v1/token_store.rb +0 -6
- data/lib/fmrest/v1/token_store/base.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9ac0ed8884efb3c03f32de33e934f5ba7298ff17fd6f2531bc31c09fff53654
|
4
|
+
data.tar.gz: c1b29afaf072953515843db4c95e9e7426662a64bc8df3aa88ec99dc7c3715f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 685dbf33e5fb4fd929e7fd5dba1a35ba6791f81590dd34e3abef16ea129b1603403e1c5fb09ea82b536e501ff7ab072a62e24b85223a53ab85602f757e825831
|
7
|
+
data.tar.gz: 25fd66ec79dfb9356c7939641e97ef65e6ba49decfc289fd4cdfc373de9656392b4889d143f91bf5df5a5c9e020913c068e1bfcdfe9573a16063a598cf86b8b9
|
data/.gitignore
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# fmrest-ruby
|
2
2
|
|
3
|
+
<a href="https://rubygems.org/gems/fmrest"><img src="https://badge.fury.io/rb/fmrest.svg?style=flat" alt="Gem Version"></a>
|
4
|
+
|
3
5
|
A Ruby client for
|
4
6
|
[FileMaker 17's Data API](https://fmhelp.filemaker.com/docs/17/en/dataapi/)
|
5
7
|
using
|
@@ -20,6 +22,9 @@ Add this line to your Gemfile:
|
|
20
22
|
|
21
23
|
```ruby
|
22
24
|
gem 'fmrest'
|
25
|
+
|
26
|
+
# Optional (for ORM features)
|
27
|
+
gem 'spyke'
|
23
28
|
```
|
24
29
|
|
25
30
|
## Basic usage
|
@@ -63,23 +68,59 @@ By default fmrest-ruby will use a memory-based store for the session tokens.
|
|
63
68
|
This is generally good enough for development, but not good enough for
|
64
69
|
production, as in-memory tokens aren't shared across threads/processes.
|
65
70
|
|
66
|
-
Besides the default
|
67
|
-
|
71
|
+
Besides the default token store the following token stores are bundled with fmrest-ruby:
|
72
|
+
|
73
|
+
### ActiveRecord
|
68
74
|
|
69
75
|
On Rails apps already using ActiveRecord setting up this token store should be
|
70
76
|
dead simple:
|
71
77
|
|
72
78
|
```ruby
|
73
79
|
# config/initializers/fmrest.rb
|
74
|
-
require "fmrest/
|
80
|
+
require "fmrest/token_store/active_record"
|
75
81
|
|
76
|
-
FmRest.token_store = FmRest::
|
82
|
+
FmRest.token_store = FmRest::TokenStore::ActiveRecord
|
77
83
|
```
|
78
84
|
|
79
85
|
No migrations are needed, the token store table will be created automatically
|
80
|
-
when needed, defaulting to the table name "fmrest_session_tokens".
|
86
|
+
when needed, defaulting to the table name "fmrest_session_tokens". If you want
|
87
|
+
to change the table name you can do so by initializing the token store and
|
88
|
+
passing it the `:table_name` option:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
FmRest.token_store = FmRest::TokenStore::ActiveRecord.new(table_name: "my_token_store")
|
92
|
+
```
|
93
|
+
|
94
|
+
### Redis
|
95
|
+
|
96
|
+
To use the Redis token store do:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
require "fmrest/token_store/redis"
|
100
|
+
|
101
|
+
FmRest.token_store = FmRest::TokenStore::Redis
|
102
|
+
```
|
103
|
+
|
104
|
+
You can also initialize it with the following options:
|
105
|
+
|
106
|
+
* `:redis` - A `Redis` object to use as connection, if ommited a new `Redis` object will be created with remaining options
|
107
|
+
* `:prefix` - The prefix to use for token keys, by default `"fmrest-token:"`
|
108
|
+
* Any other options will be passed to `Redis.new` if `:redis` isn't provided
|
109
|
+
|
110
|
+
Examples:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# Passing a Redis connection explicitly
|
114
|
+
FmRest.token_store = FmRest::TokenStore::Redis.new(redis: Redis.new, prefix: "my-fancy-prefix:")
|
81
115
|
|
82
|
-
|
116
|
+
# Passing options for Redis.new
|
117
|
+
FmRest.token_store = FmRest::TokenStore::Redis.new(prefix: "my-fancy-prefix:", host: "10.0.1.1", port: 6380, db: 15)
|
118
|
+
```
|
119
|
+
|
120
|
+
**NOTE:** redis-rb is not included as a gem dependency of fmrest-ruby, so you'll
|
121
|
+
have to add it to your Gemfile.
|
122
|
+
|
123
|
+
## Spyke support (ActiveRecord-like ORM)
|
83
124
|
|
84
125
|
[Spyke](https://github.com/balvig/spyke) is an ActiveRecord-like gem for
|
85
126
|
building REST models. fmrest-ruby has Spyke support out of the box, although
|
@@ -348,18 +389,24 @@ class Kitty < Spyke::Base
|
|
348
389
|
end
|
349
390
|
```
|
350
391
|
|
392
|
+
#### .limit
|
393
|
+
|
351
394
|
`.limit` sets the limit for get and find request:
|
352
395
|
|
353
396
|
```ruby
|
354
397
|
Kitty.limit(10)
|
355
398
|
```
|
356
399
|
|
400
|
+
#### .offset
|
401
|
+
|
357
402
|
`.offset` sets the offset for get and find requests:
|
358
403
|
|
359
404
|
```ruby
|
360
405
|
Kitty.offset(10)
|
361
406
|
```
|
362
407
|
|
408
|
+
#### .sort
|
409
|
+
|
363
410
|
`.sort` (or `.order`) sets sorting options for get and find requests:
|
364
411
|
|
365
412
|
```ruby
|
@@ -375,6 +422,8 @@ Kitty.sort(:name, :age!)
|
|
375
422
|
Kitty.sort(:name, :age__desc)
|
376
423
|
```
|
377
424
|
|
425
|
+
#### .portal
|
426
|
+
|
378
427
|
`.portal` (or `.includes`) sets the portals to fetch for get and find requests
|
379
428
|
(this recognizes portals defined with `has_portal`):
|
380
429
|
|
@@ -383,6 +432,8 @@ Kitty.portal(:toys)
|
|
383
432
|
Kitty.includes(:toys) # alias method
|
384
433
|
```
|
385
434
|
|
435
|
+
#### .query
|
436
|
+
|
386
437
|
`.query` sets query conditions for a find request (and supports attributes as
|
387
438
|
defined with `attributes`):
|
388
439
|
|
@@ -407,6 +458,8 @@ Kitty.query({ name: "Mr. Fluffers" }, { name: "Coronel Chai Latte" })
|
|
407
458
|
# JSON -> {"query": [{"CatName": "Mr. Fluffers"}, {"CatName": "Coronel Chai Latte"}]}
|
408
459
|
```
|
409
460
|
|
461
|
+
#### .omit
|
462
|
+
|
410
463
|
`.omit` works like `.query` but excludes matches:
|
411
464
|
|
412
465
|
```ruby
|
@@ -421,6 +474,8 @@ Kitty.query(name: "Captain Whiskers", omit: true)
|
|
421
474
|
# JSON -> {"query": [{"CatName": "Captain Whiskers", "omit": "true"}]}
|
422
475
|
```
|
423
476
|
|
477
|
+
#### Other notes on querying
|
478
|
+
|
424
479
|
You can chain all query methods together:
|
425
480
|
|
426
481
|
```ruby
|
@@ -463,6 +518,43 @@ instead of `POST ../:layout/_find`).
|
|
463
518
|
Kitty.find(89) # => <Kitty...>
|
464
519
|
```
|
465
520
|
|
521
|
+
### Container fields
|
522
|
+
|
523
|
+
You can define container fields on your model class with `container`:
|
524
|
+
|
525
|
+
```ruby
|
526
|
+
class Kitty < FmRest::Spyke::Base
|
527
|
+
container :photo, field_name: "Vet Card Photo ID"
|
528
|
+
end
|
529
|
+
```
|
530
|
+
|
531
|
+
`:field_name` specifies the original field in the FM layout and is optional, if
|
532
|
+
not given it will default to the name of your attribute (just `:photo` in this
|
533
|
+
example).
|
534
|
+
|
535
|
+
(Note that you don't need to define container fields with `attributes` in
|
536
|
+
addition to the `container` definition.)
|
537
|
+
|
538
|
+
This will provide you with the following instance methods:
|
539
|
+
|
540
|
+
```ruby
|
541
|
+
kitty = Kitty.new
|
542
|
+
|
543
|
+
kitty.photo.url # The URL of the container file on the FileMaker server
|
544
|
+
|
545
|
+
kitty.photo.download # Download the contents of the container as an IO object
|
546
|
+
|
547
|
+
kitty.photo.upload(filename_or_io) # Upload a file to the container
|
548
|
+
```
|
549
|
+
|
550
|
+
`upload` also accepts an options hash with the following options:
|
551
|
+
|
552
|
+
* `:repetition` - Sets the field repetition
|
553
|
+
* `:filename` - The filename to use when uploading (defaults to
|
554
|
+
`filename_or_io.original_filename` if available)
|
555
|
+
* `:content_type` - The MIME content type to use (defaults to
|
556
|
+
`application/octet-stream`)
|
557
|
+
|
466
558
|
## Logging
|
467
559
|
|
468
560
|
If using fmrest-ruby + Spyke in a Rails app pretty log output will be set up
|
@@ -510,11 +602,13 @@ end
|
|
510
602
|
|
511
603
|
## TODO
|
512
604
|
|
605
|
+
- [ ] Support for FM18 features
|
513
606
|
- [ ] Better/simpler-to-use core Ruby API
|
514
607
|
- [ ] Better API documentation and README
|
515
608
|
- [ ] Oauth support
|
516
609
|
- [ ] Support for portal limit and offset
|
517
|
-
- [
|
610
|
+
- [x] More options for token storage
|
611
|
+
- [x] Support for container fields
|
518
612
|
- [x] Optional logging
|
519
613
|
- [x] FmRest::Spyke::Base class for single inheritance (as alternative for mixin)
|
520
614
|
- [x] Specs
|
data/fmrest.gemspec
CHANGED
@@ -30,4 +30,7 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.add_development_dependency "webmock"
|
31
31
|
spec.add_development_dependency "multi_json"
|
32
32
|
spec.add_development_dependency "pry-byebug"
|
33
|
+
spec.add_development_dependency "activerecord"
|
34
|
+
spec.add_development_dependency "sqlite3", "~> 1.3.6"
|
35
|
+
spec.add_development_dependency "mock_redis"
|
33
36
|
end
|
data/lib/fmrest.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FmRest
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class APIError < Error
|
7
|
+
attr_reader :code
|
8
|
+
|
9
|
+
def initialize(code, message = nil)
|
10
|
+
@code = code
|
11
|
+
super("FileMaker Data API responded with error #{code}: #{message}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class APIError::UnknownError < APIError; end # error code -1
|
16
|
+
class APIError::ResourceMissingError < APIError; end # error codes 100..199
|
17
|
+
class APIError::RecordMissingError < APIError::ResourceMissingError; end
|
18
|
+
class APIError::AccountError < APIError; end # error codes 200..299
|
19
|
+
class APIError::LockError < APIError; end # error codes 300..399
|
20
|
+
class APIError::ParameterError < APIError; end # error codes 400..499
|
21
|
+
class APIError::ValidationError < APIError; end # error codes 500..599
|
22
|
+
class APIError::SystemError < APIError; end # error codes 800..899
|
23
|
+
class APIError::ScriptError < APIError; end # error codes 1200..1299
|
24
|
+
class APIError::ODBCError < APIError; end # error codes 1400..1499
|
25
|
+
|
26
|
+
class ContainerFieldError < Error; end
|
27
|
+
end
|
data/lib/fmrest/spyke.rb
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "spyke"
|
5
|
+
rescue LoadError => e
|
6
|
+
e.message << " (Did you include Spyke in your Gemfile?)" unless e.message.frozen?
|
7
|
+
raise e
|
8
|
+
end
|
9
|
+
|
1
10
|
require "fmrest/spyke/json_parser"
|
2
11
|
require "fmrest/spyke/model"
|
3
12
|
require "fmrest/spyke/base"
|
data/lib/fmrest/spyke/base.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FmRest
|
4
|
+
module Spyke
|
5
|
+
class ContainerField
|
6
|
+
|
7
|
+
# @return [String] the name of the container field
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# @param base [FmRest::Spyke::Base] the record this container belongs to
|
11
|
+
# @param name [Symbol] the name of the container field
|
12
|
+
def initialize(base, name)
|
13
|
+
@base = base
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String] the URL for the container
|
18
|
+
def url
|
19
|
+
@base.attributes[name]
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return (see FmRest::V1::ContainerFields#fetch_container_data)
|
23
|
+
def download
|
24
|
+
FmRest::V1.fetch_container_data(url)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param filename_or_io [String, IO] a path to the file to upload or an
|
28
|
+
# IO object
|
29
|
+
# @param options [Hash]
|
30
|
+
# @option options [Integer] :repetition (1) The repetition to pass to the
|
31
|
+
# upload URL
|
32
|
+
# @option (see FmRest::V1::ContainerFields#upload_container_data)
|
33
|
+
def upload(filename_or_io, options = {})
|
34
|
+
raise ArgumentError, "Record needs to be saved before uploading to a container field" unless @base.persisted?
|
35
|
+
|
36
|
+
response =
|
37
|
+
FmRest::V1.upload_container_data(
|
38
|
+
@base.class.connection,
|
39
|
+
upload_path(options[:repetition] || 1),
|
40
|
+
filename_or_io,
|
41
|
+
options
|
42
|
+
)
|
43
|
+
|
44
|
+
# Update mod id on record
|
45
|
+
@base.mod_id = response.body[:data][:mod_id]
|
46
|
+
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# @param repetition [Integer]
|
53
|
+
# @return [String] the path for uploading a file to the container
|
54
|
+
def upload_path(repetition)
|
55
|
+
FmRest::V1.container_field_path(@base.class.layout, @base.id, name, repetition)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,31 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FmRest
|
2
4
|
module Spyke
|
5
|
+
# Response Faraday middleware for converting FM API's response JSON into
|
6
|
+
# Spyke's expected format
|
3
7
|
class JsonParser < ::Faraday::Response::Middleware
|
4
8
|
SINGLE_RECORD_RE = %r(/records/\d+\Z).freeze
|
9
|
+
MULTIPLE_RECORDS_RE = %r(/records\Z).freeze
|
5
10
|
FIND_RECORDS_RE = %r(/_find\b).freeze
|
6
11
|
|
12
|
+
VALIDATION_ERROR_RANGE = 500..599
|
13
|
+
|
14
|
+
# @param app [#call]
|
15
|
+
# @param model [Class<FmRest::Spyke::Base>]
|
7
16
|
def initialize(app, model)
|
8
17
|
super(app)
|
9
18
|
@model = model
|
10
19
|
end
|
11
20
|
|
21
|
+
# @param env [Faraday::Env]
|
12
22
|
def on_complete(env)
|
13
23
|
json = parse_json(env.body)
|
14
24
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
prepare_save_response(json)
|
24
|
-
end
|
25
|
+
case
|
26
|
+
when single_record_request?(env)
|
27
|
+
env.body = prepare_single_record(json)
|
28
|
+
when multiple_records_request?(env), find_request?(env)
|
29
|
+
env.body = prepare_collection(json)
|
30
|
+
when create_request?(env), update_request?(env), delete_request?(env)
|
31
|
+
env.body = prepare_save_response(json)
|
32
|
+
end
|
25
33
|
end
|
26
34
|
|
27
35
|
private
|
28
36
|
|
37
|
+
# @param json [Hash]
|
38
|
+
# @return [Hash] the response in Spyke format
|
29
39
|
def prepare_save_response(json)
|
30
40
|
response = json[:response]
|
31
41
|
|
@@ -33,33 +43,53 @@ module FmRest
|
|
33
43
|
data[:mod_id] = response[:modId].to_i if response[:modId]
|
34
44
|
data[:id] = response[:recordId].to_i if response[:recordId]
|
35
45
|
|
36
|
-
|
46
|
+
build_base_hash(json, true).merge!(data: data)
|
37
47
|
end
|
38
48
|
|
49
|
+
# (see #prepare_save_response)
|
39
50
|
def prepare_single_record(json)
|
40
51
|
data =
|
41
52
|
json[:response][:data] &&
|
42
53
|
prepare_record_data(json[:response][:data].first)
|
43
54
|
|
44
|
-
|
55
|
+
build_base_hash(json).merge!(data: data)
|
45
56
|
end
|
46
57
|
|
58
|
+
# (see #prepare_save_response)
|
47
59
|
def prepare_collection(json)
|
48
60
|
data =
|
49
61
|
json[:response][:data] &&
|
50
62
|
json[:response][:data].map { |record_data| prepare_record_data(record_data) }
|
51
63
|
|
52
|
-
|
64
|
+
build_base_hash(json).merge!(data: data)
|
53
65
|
end
|
54
66
|
|
55
|
-
|
67
|
+
# @param json [Hash]
|
68
|
+
# @param include_errors [Boolean]
|
69
|
+
# @return [Hash] the skeleton structure for a Spyke-formatted response
|
70
|
+
def build_base_hash(json, include_errors = false)
|
56
71
|
{
|
57
72
|
metadata: { messages: json[:messages] },
|
58
|
-
errors: {}
|
73
|
+
errors: include_errors ? prepare_errors(json) : {}
|
59
74
|
}
|
60
75
|
end
|
61
76
|
|
62
|
-
#
|
77
|
+
# @param json [Hash]
|
78
|
+
# @return [Hash] the errors hash in Spyke format
|
79
|
+
def prepare_errors(json)
|
80
|
+
# Code 0 means "No Error"
|
81
|
+
# https://fmhelp.filemaker.com/help/17/fmp/en/index.html#page/FMP_Help/error-codes.html
|
82
|
+
return {} if json[:messages][0][:code].to_i == 0
|
83
|
+
|
84
|
+
json[:messages].each_with_object(base: []) do |message, hash|
|
85
|
+
# Only include validation errors
|
86
|
+
next unless VALIDATION_ERROR_RANGE.include?(message[:code].to_i)
|
87
|
+
|
88
|
+
hash[:base] << "#{message[:message]} (#{message[:code]})"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# `json_data` is expected in this format:
|
63
93
|
#
|
64
94
|
# {
|
65
95
|
# "fieldData": {
|
@@ -83,6 +113,8 @@ module FmRest
|
|
83
113
|
# "recordId": <Unique_internal_ID_for_this_record>
|
84
114
|
# }
|
85
115
|
#
|
116
|
+
# @param json_data [Hash]
|
117
|
+
# @return [Hash] the record data in Spyke format
|
86
118
|
def prepare_record_data(json_data)
|
87
119
|
out = { id: json_data[:recordId].to_i, mod_id: json_data[:modId].to_i }
|
88
120
|
out.merge!(json_data[:fieldData])
|
@@ -90,17 +122,19 @@ module FmRest
|
|
90
122
|
out
|
91
123
|
end
|
92
124
|
|
93
|
-
# Extracts recordId and strips the PortalName:: field prefix for each
|
125
|
+
# Extracts `recordId` and strips the `"PortalName::"` field prefix for each
|
94
126
|
# portal
|
95
127
|
#
|
96
|
-
# Sample json_portal_data
|
128
|
+
# Sample `json_portal_data`:
|
97
129
|
#
|
98
130
|
# "portalData": {
|
99
131
|
# "Orders":[
|
100
|
-
# { "Orders::DeliveryDate":"3/7/2017", "recordId":"23" }
|
132
|
+
# { "Orders::DeliveryDate": "3/7/2017", "recordId": "23" }
|
101
133
|
# ]
|
102
134
|
# }
|
103
135
|
#
|
136
|
+
# @param json_portal_data [Hash]
|
137
|
+
# @return [Hash] the portal data in Spyke format
|
104
138
|
def prepare_portal_data(json_portal_data)
|
105
139
|
json_portal_data.each_with_object({}) do |(portal_name, portal_records), out|
|
106
140
|
portal_options = @model.portal_options[portal_name.to_s] || {}
|
@@ -115,7 +149,7 @@ module FmRest
|
|
115
149
|
|
116
150
|
portal_fields.each do |k, v|
|
117
151
|
next if :recordId == k || :modId == k
|
118
|
-
attributes[k.to_s.gsub(prefix_matcher, "")] = v
|
152
|
+
attributes[k.to_s.gsub(prefix_matcher, "").to_sym] = v
|
119
153
|
end
|
120
154
|
|
121
155
|
attributes
|
@@ -123,14 +157,39 @@ module FmRest
|
|
123
157
|
end
|
124
158
|
end
|
125
159
|
|
126
|
-
|
127
|
-
|
160
|
+
# @param env [Faraday::Env]
|
161
|
+
# @return [Boolean]
|
162
|
+
def single_record_request?(env)
|
163
|
+
env.method == :get && env.url.path.match(SINGLE_RECORD_RE)
|
164
|
+
end
|
165
|
+
|
166
|
+
# (see #single_record_request?)
|
167
|
+
def multiple_records_request?(env)
|
168
|
+
env.method == :get && env.url.path.match(MULTIPLE_RECORDS_RE)
|
169
|
+
end
|
170
|
+
|
171
|
+
# (see #single_record_request?)
|
172
|
+
def find_request?(env)
|
173
|
+
env.method == :post && env.url.path.match(FIND_RECORDS_RE)
|
174
|
+
end
|
175
|
+
|
176
|
+
# (see #single_record_request?)
|
177
|
+
def update_request?(env)
|
178
|
+
env.method == :patch && env.url.path.match(SINGLE_RECORD_RE)
|
179
|
+
end
|
180
|
+
|
181
|
+
# (see #single_record_request?)
|
182
|
+
def create_request?(env)
|
183
|
+
env.method == :post && env.url.path.match(MULTIPLE_RECORDS_RE)
|
128
184
|
end
|
129
185
|
|
130
|
-
|
131
|
-
|
186
|
+
# (see #single_record_request?)
|
187
|
+
def delete_request?(env)
|
188
|
+
env.method == :delete && env.url.path.match(SINGLE_RECORD_RE)
|
132
189
|
end
|
133
190
|
|
191
|
+
# @param source [String] a JSON string
|
192
|
+
# @return [Hash] the parsed JSON
|
134
193
|
def parse_json(source)
|
135
194
|
if defined?(::MultiJson)
|
136
195
|
::MultiJson.load(source, symbolize_keys: true)
|