app_rail-airtable 0.1.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +7 -3
- data/Gemfile.lock +33 -36
- data/README.md +63 -15
- data/Rakefile +5 -3
- data/app_rail-airtable.gemspec +26 -15
- data/bin/ara_generator +8 -0
- data/bin/console +4 -3
- data/examples/schemas/daily_logs.json +86 -0
- data/examples/schemas/locations.json +85 -0
- data/examples/schemas/users.json +51 -0
- data/lib/app_rail/airtable/application_record.rb +43 -25
- data/lib/app_rail/airtable/authenticatable.rb +78 -0
- data/lib/app_rail/airtable/authentication_helpers.rb +38 -0
- data/lib/app_rail/airtable/generator.rb +75 -0
- data/lib/app_rail/airtable/sinatra.rb +106 -18
- data/lib/app_rail/airtable/string_ext.rb +7 -0
- data/lib/app_rail/airtable/version.rb +3 -1
- data/lib/app_rail/airtable.rb +9 -3
- data/templates/project/.env.tt +2 -0
- data/templates/project/.gitignore +19 -0
- data/templates/project/Gemfile +24 -0
- data/templates/project/app.json +17 -0
- data/templates/project/boot.rb +8 -0
- data/templates/project/config.ru +6 -0
- data/templates/project/lib/server.rb.tt +68 -0
- data/templates/project/spec/server_spec.rb.tt +128 -0
- data/templates/project/spec/spec_helper.rb +114 -0
- metadata +96 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 208213f6a53029eb29b4d98ed9b6d4f9bb7bb0375d3a5de3866615c8701ec3d9
|
4
|
+
data.tar.gz: 5aea3eda06422225f87170aa3b1d8a8a3310123fb151a554b29ad8059db72847
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 975099138a2b7aef7ae085a09d7ace624f6b7eba9c892691807505ec44f8322dbf8b3a474113ecb3053b6d8243264db97531607ec2cfc3b4ad8907c253242dc0
|
7
|
+
data.tar.gz: 2b07588a9c2c375a0b4574a4500921c40f306c45bf153fd530520b7874716f4fbdb3b4cd56ade60c5dba9827b097052833e8827b4b564a83303af47bb4373af7
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# 0.4.6
|
2
|
+
* Add authenticatable_resource POST / DELETE `/<resource>/session` routes for sign in and sign out
|
3
|
+
* Simplify sign in methods & remove Authenticatable#create_session_as_json
|
4
|
+
|
5
|
+
# 0.4.0
|
6
|
+
|
7
|
+
### Breaking changes
|
8
|
+
* `AppRail::Airtable::Sinatra` `.resources` and `.authenticable_resources` `only` argument defaults to `[:index, :show, :create, :update]` instead of `[:index, :show, :create]`
|
9
|
+
|
10
|
+
### New features
|
11
|
+
* Add `Authenticatable#initialize` method to initialize an Airrecord record without persisting it to airtable
|
12
|
+
* Add `update` route (`PUT`), which points to the corresponding model method `self.update_as_json`
|
13
|
+
|
data/Gemfile
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
2
4
|
|
3
5
|
# Specify your gem's dependencies in app_rail-airtable.gemspec
|
4
6
|
gemspec
|
5
7
|
|
6
|
-
gem
|
7
|
-
gem
|
8
|
+
gem 'byebug'
|
9
|
+
gem 'rake', '~> 12.0'
|
10
|
+
gem 'rspec', '~> 3.0'
|
11
|
+
gem 'thor'
|
data/Gemfile.lock
CHANGED
@@ -1,55 +1,50 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
app_rail-airtable (0.
|
4
|
+
app_rail-airtable (0.5.0)
|
5
5
|
activesupport
|
6
6
|
airrecord
|
7
|
+
app_rail-steps
|
8
|
+
bcrypt
|
9
|
+
faraday (~> 2.2)
|
10
|
+
faraday-net_http_persistent (~> 2.0)
|
7
11
|
sinatra
|
12
|
+
thor
|
8
13
|
|
9
14
|
GEM
|
10
15
|
remote: https://rubygems.org/
|
11
16
|
specs:
|
12
|
-
activesupport (
|
17
|
+
activesupport (7.0.4)
|
13
18
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
19
|
i18n (>= 1.6, < 2)
|
15
20
|
minitest (>= 5.1)
|
16
21
|
tzinfo (~> 2.0)
|
17
|
-
|
18
|
-
|
19
|
-
faraday
|
22
|
+
airrecord (1.0.10)
|
23
|
+
faraday (>= 1.0, < 3.0)
|
24
|
+
faraday-net_http_persistent
|
20
25
|
net-http-persistent
|
21
|
-
|
22
|
-
|
26
|
+
app_rail-steps (0.2.17)
|
27
|
+
bcrypt (3.1.18)
|
28
|
+
byebug (11.1.3)
|
29
|
+
concurrent-ruby (1.1.10)
|
30
|
+
connection_pool (2.3.0)
|
23
31
|
diff-lcs (1.4.4)
|
24
|
-
faraday (
|
25
|
-
faraday-
|
26
|
-
faraday-em_synchrony (~> 1.0)
|
27
|
-
faraday-excon (~> 1.1)
|
28
|
-
faraday-httpclient (~> 1.0.1)
|
29
|
-
faraday-net_http (~> 1.0)
|
30
|
-
faraday-net_http_persistent (~> 1.1)
|
31
|
-
faraday-patron (~> 1.0)
|
32
|
-
faraday-rack (~> 1.0)
|
33
|
-
multipart-post (>= 1.2, < 3)
|
32
|
+
faraday (2.6.0)
|
33
|
+
faraday-net_http (>= 2.0, < 3.1)
|
34
34
|
ruby2_keywords (>= 0.0.4)
|
35
|
-
faraday-
|
36
|
-
faraday-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
faraday-net_http_persistent (1.2.0)
|
41
|
-
faraday-patron (1.0.0)
|
42
|
-
faraday-rack (1.0.0)
|
43
|
-
i18n (1.8.10)
|
35
|
+
faraday-net_http (3.0.1)
|
36
|
+
faraday-net_http_persistent (2.1.0)
|
37
|
+
faraday (~> 2.5)
|
38
|
+
net-http-persistent (~> 4.0)
|
39
|
+
i18n (1.12.0)
|
44
40
|
concurrent-ruby (~> 1.0)
|
45
|
-
minitest (5.
|
46
|
-
|
47
|
-
mustermann (1.1.1)
|
41
|
+
minitest (5.16.3)
|
42
|
+
mustermann (2.0.2)
|
48
43
|
ruby2_keywords (~> 0.0.1)
|
49
44
|
net-http-persistent (4.0.1)
|
50
45
|
connection_pool (~> 2.2)
|
51
46
|
rack (2.2.3)
|
52
|
-
rack-protection (2.
|
47
|
+
rack-protection (2.2.2)
|
53
48
|
rack
|
54
49
|
rack-test (1.1.0)
|
55
50
|
rack (>= 1.0, < 3)
|
@@ -68,24 +63,26 @@ GEM
|
|
68
63
|
rspec-support (~> 3.10.0)
|
69
64
|
rspec-support (3.10.2)
|
70
65
|
ruby2_keywords (0.0.5)
|
71
|
-
sinatra (2.
|
72
|
-
mustermann (~>
|
66
|
+
sinatra (2.2.2)
|
67
|
+
mustermann (~> 2.0)
|
73
68
|
rack (~> 2.2)
|
74
|
-
rack-protection (= 2.
|
69
|
+
rack-protection (= 2.2.2)
|
75
70
|
tilt (~> 2.0)
|
76
|
-
|
77
|
-
|
71
|
+
thor (1.1.0)
|
72
|
+
tilt (2.0.11)
|
73
|
+
tzinfo (2.0.5)
|
78
74
|
concurrent-ruby (~> 1.0)
|
79
|
-
zeitwerk (2.4.2)
|
80
75
|
|
81
76
|
PLATFORMS
|
82
77
|
ruby
|
83
78
|
|
84
79
|
DEPENDENCIES
|
85
80
|
app_rail-airtable!
|
81
|
+
byebug
|
86
82
|
rack-test
|
87
83
|
rake (~> 12.0)
|
88
84
|
rspec (~> 3.0)
|
85
|
+
thor
|
89
86
|
|
90
87
|
BUNDLED WITH
|
91
88
|
2.1.4
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# App Rail Airtable
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
App Rail Airtable is a micro-framework based on Sinatra and [Airrecord](https://github.com/sirupsen/airrecord) which faciliates use of Airtable as a backend for App Rail Apps. You can deploy a template repo to Heroku for fast start.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -12,29 +10,79 @@ Add this line to your application's Gemfile:
|
|
12
10
|
gem 'app_rail-airtable'
|
13
11
|
```
|
14
12
|
|
15
|
-
|
13
|
+
## Usage
|
16
14
|
|
17
|
-
|
15
|
+
App Rail Airtable has two important concepts, models and servers.
|
18
16
|
|
19
|
-
|
17
|
+
### Models
|
18
|
+
App Rail Airtable makes the following assumptions
|
19
|
+
1. Your App will connect to a single Airtable Base
|
20
|
+
2. Your Airtable Base implements a normalised datamodel
|
21
|
+
3. Your tables in Airtable are plural versions of the models you create in Ruby
|
20
22
|
|
21
|
-
|
23
|
+
**API keys have account scope in Airtable so it is recommended to create a new account for each project so that API keys do not leak data, e.g. bob+<project name>@gmail.com. You can share the project with your main account so you don't need to login to separate accounts for each project.**
|
22
24
|
|
23
|
-
|
25
|
+
You should create a model class for each table you want to use in your App. Model classes should extend `AppRail::Airtable::ApplicationRecord` and define [table name](https://github.com/sirupsen/airrecord#table) and [associations](https://github.com/sirupsen/airrecord#associations)
|
26
|
+
|
27
|
+
To provide support for routes, models can implement
|
28
|
+
* ar_list_item (index)
|
29
|
+
* ar_stack (show)
|
30
|
+
* self.create_as_json (create)
|
31
|
+
* self.update_as_json (update)
|
32
|
+
|
33
|
+
In order to support authentication you should create a class (normally `User`) and inherit from `AppRail::Airtable::AuthenticationRecord`. Your table needs `Email`, `Password Hash` and `Access Token` columns.
|
34
|
+
|
35
|
+
### Servers
|
36
|
+
You should create a single server which extends `AppRail::Airtable::Sinatra`.
|
37
|
+
|
38
|
+
App Rail Airtable provides two helpers to generate routes
|
39
|
+
|
40
|
+
**resources(name, only:)**
|
41
|
+
Creates routes that map to a table. It delegates from the route to a model method
|
42
|
+
* `index` to `ar_list_item`
|
43
|
+
* `show` to `ar_stack`
|
44
|
+
* `create` to `self.create_as_json`
|
45
|
+
* `update` to `self.update_as_json`
|
24
46
|
|
25
|
-
|
47
|
+
**authenticable_resources(name, , only:)**
|
48
|
+
Acts as `resources` but also takes a block of routes. Those nested routes will all call the `authenticate!` helper method before running. The `authenticate!` helper will call a lookup helper `find_authenticatable_resource(access_token:)` with the bearer token found in the `HTTP_AUTHORIZATION` header, which should be used to look up the correct object or return nil if none is found (resulting in a 401 response).
|
26
49
|
|
27
|
-
|
50
|
+
### Generator
|
28
51
|
|
29
|
-
|
52
|
+
```bash
|
53
|
+
bin/ara_generator <output_directory> <airtable_api_key> <airtable_base_id> [<json_schema>]
|
54
|
+
```
|
30
55
|
|
31
|
-
|
56
|
+
#### Example
|
32
57
|
|
33
|
-
|
58
|
+
```bash
|
59
|
+
bin/ara_generator ./test key1234567890 appwertyuiop '{"tables": [{"name": "DailyLogs", "fields": [{"name": "Date", "type": "date"}, {"name": "Score", "type": "integer"}], "associations": [{"name": "User", "type": "belongs_to", "model": "User"}], "ar_class_methods": [{"name": "ar_list_item", "properties": [{"type": "text", "value": "date"}, {"type": "detail_text", "value": "score"}]}], "ar_instance_methods": [{"name": "ar_stack_item", "properties": [{"type": "text", "label": "Date", "value": "date"}, {"type": "button", "label": "Score", "value": "score", "modalWorkflow": "", "style": "primary", "onSuccess": "forward", "sfSymbolName": ""}]}]}, {"name": "Users", "fields": [{"name": "Email", "type": "string"}, {"name": "Password Hash", "type": "string"}, {"name": "Access Token", "type": "string"}], "associations": [{"name": "DailyLogs", "type": "has_many", "model": "DailyLog"}], "authenticatable": "True"}]}'
|
60
|
+
```
|
34
61
|
|
35
|
-
|
62
|
+
You can find more examples of schemas in [/examples/schemas](/examples/schemas)
|
36
63
|
|
64
|
+
## Debugging
|
37
65
|
|
66
|
+
### Listing Existing Routes
|
67
|
+
To get a complete list of the available routes in your Sinatra application, run the following code snippet:
|
68
|
+
```ruby
|
69
|
+
Server.routes.map do |method, routes|
|
70
|
+
routes.map { |r| r.first.to_s }.map do |route|
|
71
|
+
"#{method.rjust(7, ' ')} #{route}"
|
72
|
+
end
|
73
|
+
end.flatten.sort.each do |route|
|
74
|
+
puts route
|
75
|
+
end
|
76
|
+
```
|
77
|
+
Output:
|
78
|
+
```
|
79
|
+
GET /mock_items
|
80
|
+
GET /mock_items/:id
|
81
|
+
PUT /mock_items/:id
|
82
|
+
HEAD /mock_items
|
83
|
+
HEAD /mock_items/:id
|
84
|
+
POST /mock_items
|
85
|
+
```
|
38
86
|
## License
|
39
87
|
|
40
88
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
data/app_rail-airtable.gemspec
CHANGED
@@ -1,29 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'lib/app_rail/airtable/version'
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name =
|
6
|
+
spec.name = 'app_rail-airtable'
|
5
7
|
spec.version = AppRail::Airtable::VERSION
|
6
|
-
spec.authors = [
|
7
|
-
spec.email = [
|
8
|
+
spec.authors = ['Matt Brooke-Smith']
|
9
|
+
spec.email = ['matt@futureworkshops.com']
|
8
10
|
|
9
|
-
spec.summary =
|
10
|
-
spec.homepage =
|
11
|
-
spec.license =
|
12
|
-
spec.required_ruby_version = Gem::Requirement.new(
|
11
|
+
spec.summary = 'Gem to help building App Rail servers using Airtable as a backend'
|
12
|
+
spec.homepage = 'https://github.com/FutureWorkshops/app_rail-airtable'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
13
15
|
|
14
|
-
spec.metadata[
|
15
|
-
spec.metadata[
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/FutureWorkshops/app_rail-airtable'
|
16
18
|
|
17
19
|
# Specify which files should be added to the gem when it is released.
|
18
20
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
-
spec.files
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
20
22
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
23
|
end
|
22
|
-
|
24
|
+
|
25
|
+
spec.bindir = 'bin'
|
26
|
+
spec.executables << 'ara_generator'
|
27
|
+
|
28
|
+
spec.require_paths = ['lib']
|
23
29
|
spec.add_dependency 'activesupport'
|
24
|
-
spec.add_dependency 'sinatra'
|
25
30
|
spec.add_dependency 'airrecord'
|
26
|
-
|
27
|
-
spec.
|
31
|
+
spec.add_dependency 'app_rail-steps'
|
32
|
+
spec.add_dependency 'bcrypt'
|
33
|
+
spec.add_dependency 'faraday', '~> 2.2'
|
34
|
+
spec.add_dependency 'faraday-net_http_persistent', '~> 2.0' # workaround to make Faraday work after upgrading to 2.2.0
|
35
|
+
spec.add_dependency 'sinatra'
|
36
|
+
spec.add_dependency 'thor'
|
37
|
+
|
28
38
|
spec.add_development_dependency 'rack-test'
|
29
|
-
|
39
|
+
spec.add_development_dependency 'rspec'
|
40
|
+
end
|
data/bin/ara_generator
ADDED
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'app_rail/airtable'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "app_rail/airtable"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'irb'
|
14
15
|
IRB.start(__FILE__)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
{
|
2
|
+
"tables": [
|
3
|
+
{
|
4
|
+
"name": "DailyLogs",
|
5
|
+
"fields": [
|
6
|
+
{
|
7
|
+
"name": "Date",
|
8
|
+
"type": "date"
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"name": "Score",
|
12
|
+
"type": "integer"
|
13
|
+
}
|
14
|
+
],
|
15
|
+
"associations": [
|
16
|
+
{
|
17
|
+
"name": "User",
|
18
|
+
"type": "belongs_to",
|
19
|
+
"model": "User"
|
20
|
+
}
|
21
|
+
],
|
22
|
+
"ar_class_methods": [
|
23
|
+
{
|
24
|
+
"name": "ar_list_item",
|
25
|
+
"properties": [
|
26
|
+
{
|
27
|
+
"type": "text",
|
28
|
+
"value": "date"
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"type": "detail_text",
|
32
|
+
"value": "score"
|
33
|
+
}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
],
|
37
|
+
"ar_instance_methods": [
|
38
|
+
{
|
39
|
+
"name": "ar_stack_item",
|
40
|
+
"properties": [
|
41
|
+
{
|
42
|
+
"type": "text",
|
43
|
+
"label": "Date",
|
44
|
+
"value": "date"
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"type": "button",
|
48
|
+
"label": "Score",
|
49
|
+
"value": "score",
|
50
|
+
"modalWorkflow": "",
|
51
|
+
"style": "primary",
|
52
|
+
"onSuccess": "forward",
|
53
|
+
"sfSymbolName": ""
|
54
|
+
}
|
55
|
+
]
|
56
|
+
}
|
57
|
+
],
|
58
|
+
"authenticatable": "True"
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"name": "Users",
|
62
|
+
"fields": [
|
63
|
+
{
|
64
|
+
"name": "Email",
|
65
|
+
"type": "string"
|
66
|
+
},
|
67
|
+
{
|
68
|
+
"name": "Password Hash",
|
69
|
+
"type": "string"
|
70
|
+
},
|
71
|
+
{
|
72
|
+
"name": "Access Token",
|
73
|
+
"type": "string"
|
74
|
+
}
|
75
|
+
],
|
76
|
+
"associations": [
|
77
|
+
{
|
78
|
+
"name": "DailyLogs",
|
79
|
+
"type": "has_many",
|
80
|
+
"model": "DailyLog"
|
81
|
+
}
|
82
|
+
],
|
83
|
+
"authenticatable": "True"
|
84
|
+
}
|
85
|
+
]
|
86
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
{
|
2
|
+
"tables": [
|
3
|
+
{
|
4
|
+
"name": "DailyLogs",
|
5
|
+
"fields": [
|
6
|
+
{
|
7
|
+
"name": "Date",
|
8
|
+
"type": "date"
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"name": "Score",
|
12
|
+
"type": "integer"
|
13
|
+
}
|
14
|
+
],
|
15
|
+
"associations": [
|
16
|
+
{
|
17
|
+
"name": "User",
|
18
|
+
"type": "belongs_to",
|
19
|
+
"model": "User"
|
20
|
+
}
|
21
|
+
],
|
22
|
+
"ar_class_methods": [
|
23
|
+
{
|
24
|
+
"name": "ar_list_item",
|
25
|
+
"properties": [
|
26
|
+
{
|
27
|
+
"type": "text",
|
28
|
+
"value": "date"
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"type": "detail_text",
|
32
|
+
"value": "score"
|
33
|
+
}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
],
|
37
|
+
"ar_instance_methods": [
|
38
|
+
{
|
39
|
+
"name": "ar_stack_item",
|
40
|
+
"properties": [
|
41
|
+
{
|
42
|
+
"type": "text",
|
43
|
+
"label": "Date",
|
44
|
+
"value": "date"
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"type": "button",
|
48
|
+
"label": "Score",
|
49
|
+
"value": "score",
|
50
|
+
"modalWorkflow": "",
|
51
|
+
"style": "primary",
|
52
|
+
"onSuccess": "forward",
|
53
|
+
"sfSymbolName": ""
|
54
|
+
}
|
55
|
+
]
|
56
|
+
}
|
57
|
+
]
|
58
|
+
},
|
59
|
+
{
|
60
|
+
"name": "Users",
|
61
|
+
"fields": [
|
62
|
+
{
|
63
|
+
"name": "Email",
|
64
|
+
"type": "string"
|
65
|
+
},
|
66
|
+
{
|
67
|
+
"name": "Password Hash",
|
68
|
+
"type": "string"
|
69
|
+
},
|
70
|
+
{
|
71
|
+
"name": "Access Token",
|
72
|
+
"type": "string"
|
73
|
+
}
|
74
|
+
],
|
75
|
+
"associations": [
|
76
|
+
{
|
77
|
+
"name": "DailyLogs",
|
78
|
+
"type": "has_many",
|
79
|
+
"model": "DailyLog"
|
80
|
+
}
|
81
|
+
],
|
82
|
+
"authenticatable": "True"
|
83
|
+
}
|
84
|
+
]
|
85
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
{
|
2
|
+
"tables": [
|
3
|
+
{
|
4
|
+
"name": "Users",
|
5
|
+
"fields": [
|
6
|
+
{
|
7
|
+
"name": "Forename",
|
8
|
+
"type": "string"
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"name": "Surname",
|
12
|
+
"type": "string"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"name": "NHS Number",
|
16
|
+
"type": "integer"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"name": "Date of Birth",
|
20
|
+
"type": "date"
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"name": "Vaccine",
|
24
|
+
"type": "string"
|
25
|
+
}
|
26
|
+
],
|
27
|
+
"associations": [
|
28
|
+
{
|
29
|
+
"name": "Vaccination Appointments",
|
30
|
+
"type": "has_many",
|
31
|
+
"model": "VaccinationAppointment"
|
32
|
+
}
|
33
|
+
],
|
34
|
+
"ar_class_methods": [
|
35
|
+
{
|
36
|
+
"name": "ar_list_item",
|
37
|
+
"properties": [
|
38
|
+
{
|
39
|
+
"type": "text",
|
40
|
+
"value": "forename"
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"type": "detail_text",
|
44
|
+
"value": "surname"
|
45
|
+
}
|
46
|
+
]
|
47
|
+
}
|
48
|
+
]
|
49
|
+
}
|
50
|
+
]
|
51
|
+
}
|