app_rail-airtable 0.3.7 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8efd41eb515627b2880f01a8c995cf40e32ce614a4c7f0ae89305c824f25419e
4
- data.tar.gz: e1636ef443d7071ffd9132d0af76c4eeddff51bd3c6e2554a631e17ebaa8429d
3
+ metadata.gz: 8833767e5aaef630b9844c1c3ac80add7a97c138275cfbe5552e558828c35259
4
+ data.tar.gz: 9e2b370dafcbd5afe8b962827eb9fc1fc766b99ad44bf6e05b4f538fa984f189
5
5
  SHA512:
6
- metadata.gz: 70f1a2b229234f404c9ead70cb0af42726d79c0e68d283e8a2b817f9f3921f9bde747de9afcc4b5bf2c13916e95ce18e7029e94fc6d306e1fc3b57805c5cd9f5
7
- data.tar.gz: 25eb20b69b8942ca1f14f6341b7dbc0356f3c9e2a5856a4f09aa9487efb6dcb1881f55e6612c7e13dc7bb1dad47aef07989802b8bbdcb62331238f9be7475cc3
6
+ metadata.gz: 420520ee23ebf384020c12a666ab03a59e2078dcf28ec6dd6961f8a040e15f329cc85af0e766d5604b16ee2fd79da8109485cd21f280786bccb81924bd062e86
7
+ data.tar.gz: e38daf7b1a1cac662e314fd256a8fbcdcb84d96cb2963aa22f17cdf5639384e2ff9365e7e1e61b542fe169a27081b17f258bce90c4807e821f52594d9e727741
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # 0.4.0
2
+
3
+ ### Breaking changes
4
+ * `AppRail::Airtable::Sinatra` `.resources` and `.authenticable_resources` `only` argument defaults to `[:index, :show, :create, :update]` instead of `[:index, :show, :create]`
5
+
6
+ ### New features
7
+ * Add `Authenticatable#initialize` method to initialize an Airrecord record without persisting it to airtable
8
+ * Add `update` route (`PUT`), which points to the corresponding model method `self.update_as_json`
9
+
data/Gemfile CHANGED
@@ -1,9 +1,11 @@
1
- source "https://rubygems.org"
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 "rake", "~> 12.0"
7
- gem "rspec", "~> 3.0"
8
- gem "thor"
9
- gem "byebug"
8
+ gem 'byebug'
9
+ gem 'rake', '~> 12.0'
10
+ gem 'rspec', '~> 3.0'
11
+ gem 'thor'
data/Gemfile.lock CHANGED
@@ -1,62 +1,50 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- app_rail-airtable (0.3.7)
4
+ app_rail-airtable (0.4.2)
5
5
  activesupport
6
6
  airrecord
7
+ app_rail-steps
7
8
  bcrypt
9
+ faraday (~> 2.2)
10
+ faraday-net_http_persistent (~> 2.0)
8
11
  sinatra
9
12
  thor
10
13
 
11
14
  GEM
12
15
  remote: https://rubygems.org/
13
16
  specs:
14
- activesupport (7.0.2.2)
17
+ activesupport (7.0.2.3)
15
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
16
19
  i18n (>= 1.6, < 2)
17
20
  minitest (>= 5.1)
18
21
  tzinfo (~> 2.0)
19
- airrecord (1.0.7)
20
- faraday (>= 0.10, < 2.0)
22
+ airrecord (1.0.9)
23
+ faraday (>= 0.10, < 3.0)
24
+ faraday-net_http_persistent
21
25
  net-http-persistent
22
- bcrypt (3.1.16)
26
+ app_rail-steps (0.2.0)
27
+ bcrypt (3.1.17)
23
28
  byebug (11.1.3)
24
- concurrent-ruby (1.1.9)
29
+ concurrent-ruby (1.1.10)
25
30
  connection_pool (2.2.5)
26
31
  diff-lcs (1.4.4)
27
- faraday (1.9.3)
28
- faraday-em_http (~> 1.0)
29
- faraday-em_synchrony (~> 1.0)
30
- faraday-excon (~> 1.1)
31
- faraday-httpclient (~> 1.0)
32
- faraday-multipart (~> 1.0)
33
- faraday-net_http (~> 1.0)
34
- faraday-net_http_persistent (~> 1.0)
35
- faraday-patron (~> 1.0)
36
- faraday-rack (~> 1.0)
37
- faraday-retry (~> 1.0)
32
+ faraday (2.2.0)
33
+ faraday-net_http (~> 2.0)
38
34
  ruby2_keywords (>= 0.0.4)
39
- faraday-em_http (1.0.0)
40
- faraday-em_synchrony (1.0.0)
41
- faraday-excon (1.1.0)
42
- faraday-httpclient (1.0.1)
43
- faraday-multipart (1.0.3)
44
- multipart-post (>= 1.2, < 3)
45
- faraday-net_http (1.0.1)
46
- faraday-net_http_persistent (1.2.0)
47
- faraday-patron (1.0.0)
48
- faraday-rack (1.0.0)
49
- faraday-retry (1.0.3)
50
- i18n (1.9.1)
35
+ faraday-net_http (2.0.2)
36
+ faraday-net_http_persistent (2.0.1)
37
+ faraday-net_http
38
+ net-http-persistent (~> 4.0)
39
+ i18n (1.10.0)
51
40
  concurrent-ruby (~> 1.0)
52
41
  minitest (5.15.0)
53
- multipart-post (2.1.1)
54
42
  mustermann (1.1.1)
55
43
  ruby2_keywords (~> 0.0.1)
56
44
  net-http-persistent (4.0.1)
57
45
  connection_pool (~> 2.2)
58
46
  rack (2.2.3)
59
- rack-protection (2.1.0)
47
+ rack-protection (2.2.0)
60
48
  rack
61
49
  rack-test (1.1.0)
62
50
  rack (>= 1.0, < 3)
@@ -75,10 +63,10 @@ GEM
75
63
  rspec-support (~> 3.10.0)
76
64
  rspec-support (3.10.2)
77
65
  ruby2_keywords (0.0.5)
78
- sinatra (2.1.0)
66
+ sinatra (2.2.0)
79
67
  mustermann (~> 1.0)
80
68
  rack (~> 2.2)
81
- rack-protection (= 2.1.0)
69
+ rack-protection (= 2.2.0)
82
70
  tilt (~> 2.0)
83
71
  thor (1.1.0)
84
72
  tilt (2.0.10)
data/README.md CHANGED
@@ -2,10 +2,6 @@
2
2
 
3
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.
4
4
 
5
- ## Quick Start
6
-
7
- Visit [App Rail Airtable Template](https://github.com/FutureWorkshops/AppRailAirtableTemplate) and follow instructions.
8
-
9
5
  ## Installation
10
6
 
11
7
  Add this line to your application's Gemfile:
@@ -16,7 +12,7 @@ gem 'app_rail-airtable'
16
12
 
17
13
  ## Usage
18
14
 
19
- App Rail Airtable has two important concepts, models and servers. The [App Rail Petshop](https://github.com/FutureWorkshops/ar-petshop-airtable) provides useful examples.
15
+ App Rail Airtable has two important concepts, models and servers.
20
16
 
21
17
  ### Models
22
18
  App Rail Airtable makes the following assumptions
@@ -32,6 +28,7 @@ To provide support for routes, models can implement
32
28
  * ar_list_item (index)
33
29
  * ar_stack (show)
34
30
  * self.create_as_json (create)
31
+ * self.update_as_json (update)
35
32
 
36
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.
37
34
 
@@ -45,10 +42,47 @@ Creates routes that map to a table. It delegates from the route to a model metho
45
42
  * `index` to `ar_list_item`
46
43
  * `show` to `ar_stack`
47
44
  * `create` to `self.create_as_json`
45
+ * `update` to `self.update_as_json`
48
46
 
49
47
  **authenticable_resources(name, , only:)**
50
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).
51
49
 
50
+ ### Generator
51
+
52
+ ```bash
53
+ bin/ara_generator <output_directory> <airtable_api_key> <airtable_base_id> [<json_schema>]
54
+ ```
55
+
56
+ #### Example
57
+
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
+ ```
61
+
62
+ You can find more examples of schemas in [/examples/schemas](/examples/schemas)
63
+
64
+ ## Debugging
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
+ ```
52
86
  ## License
53
87
 
54
88
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -1,35 +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 = "app_rail-airtable"
6
+ spec.name = 'app_rail-airtable'
5
7
  spec.version = AppRail::Airtable::VERSION
6
- spec.authors = ["Matt Brooke-Smith"]
7
- spec.email = ["matt@futureworkshops.com"]
8
+ spec.authors = ['Matt Brooke-Smith']
9
+ spec.email = ['matt@futureworkshops.com']
8
10
 
9
- spec.summary = %q{Gem to help building App Rail servers using Airtable as a backend}
10
- spec.homepage = "https://github.com/FutureWorkshops/app_rail-airtable"
11
- spec.license = "MIT"
12
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
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["homepage_uri"] = spec.homepage
15
- spec.metadata["source_code_uri"] = "https://github.com/FutureWorkshops/app_rail-airtable"
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 = Dir.chdir(File.expand_path('..', __FILE__)) do
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
+
23
25
  spec.bindir = 'bin'
24
26
  spec.executables << 'ara_generator'
25
-
26
- spec.require_paths = ["lib"]
27
+
28
+ spec.require_paths = ['lib']
29
+ spec.add_dependency 'app_rail-steps'
27
30
  spec.add_dependency 'activesupport'
28
- spec.add_dependency 'sinatra'
29
31
  spec.add_dependency 'airrecord'
30
- spec.add_dependency 'thor'
31
32
  spec.add_dependency 'bcrypt'
32
-
33
- spec.add_development_dependency 'rspec'
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
+
34
38
  spec.add_development_dependency 'rack-test'
35
- end
39
+ spec.add_development_dependency 'rspec'
40
+ end
data/bin/ara_generator CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
- require "bundler/setup"
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
3
5
  require 'app_rail/airtable/generator'
4
6
  require 'byebug'
5
7
 
6
- AppRail::Airtable::Generator.start(ARGV)
8
+ AppRail::Airtable::Generator.start(ARGV)
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "app_rail/airtable"
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 "irb"
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
+ }
@@ -1,64 +1,67 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airrecord'
4
+ require 'app_rail-steps'
2
5
  require 'active_support/core_ext/string/inflections'
3
6
 
4
7
  module AppRail
5
8
  module Airtable
6
9
  class ApplicationRecord < Airrecord::Table
7
10
  include ActiveSupport::Inflector
8
-
11
+ include AppRail::Steps::Displayable
12
+
9
13
  def self.base_key
10
- ENV.fetch("AIRTABLE_BASE_ID")
14
+ ENV.fetch('AIRTABLE_BASE_ID')
11
15
  end
12
-
16
+
13
17
  def self.table_name
14
- self.name.pluralize
18
+ name.pluralize
15
19
  end
16
-
20
+
17
21
  def self.airtable_attr(*attributes)
18
22
  attributes.each do |attribute|
19
23
  define_method(attribute.to_s.snake_case) { self[attribute] }
20
24
  define_method("#{attribute.to_s.snake_case}=") { |value| self[attribute] = value }
21
25
  end
22
26
  end
23
-
27
+
24
28
  # Step utilities
25
29
  def self.ar_list_item(text:, detail_text: nil, image: nil, sf_symbol: nil, material_icon: nil)
26
30
  define_method(:ar_list_item_as_json) do
27
31
  {
28
- id: self.id,
29
- text: method_value(text).to_s,
30
- detailText: method_value(detail_text).to_s,
31
- imageURL: method_value(image),
32
- sfSymbolName: method_value(sf_symbol),
32
+ id: id,
33
+ text: method_value(text).to_s,
34
+ detailText: method_value(detail_text).to_s,
35
+ imageURL: method_value(image),
36
+ sfSymbolName: method_value(sf_symbol),
33
37
  materialIconName: method_value(material_icon)
34
38
  }.compact
35
39
  end
36
40
  end
37
-
41
+
38
42
  def self.ar_stack(text_items:)
39
43
  define_method(:ar_stack_as_json) do
40
- text_items.map{|ti| {type: :text, label: ti, text: self[ti].to_s} }
44
+ text_items.map { |ti| { type: :text, label: ti, text: self[ti].to_s } }
41
45
  end
42
46
  end
43
-
47
+
44
48
  # Customisable behaviour
45
-
49
+
46
50
  # Override to provide custom sorting or filtering for index
47
51
  def self.index(user:)
48
52
  all
49
53
  end
50
-
54
+
51
55
  private
52
-
56
+
53
57
  def method_value(name)
54
58
  send(name) if name.present?
55
59
  end
56
-
60
+
57
61
  # size is either :small, :large or :full
58
62
  def image(name, index: 0, size: :full)
59
- self[name][index]["thumbnails"][size.to_s]["url"] if self[name] && self[name].length > index
63
+ self[name][index]['thumbnails'][size.to_s]['url'] if self[name] && self[name].length > index
60
64
  end
61
-
62
65
  end
63
66
  end
64
- end
67
+ end