fmrest 0.1.0 → 0.2.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/.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)
|