app_rail-airtable 0.3.6 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd8e78c130ea41fda991115396a84bd30971d1ca60f366988c84090bf1212e6d
4
- data.tar.gz: cec97d555c6459fa03306e6572fb2528609fd26aea9bb10554d0a199a32df42c
3
+ metadata.gz: 635c1b4e200ee31f032f974ca3da84df53b766825c10ac3d4c867ac5b1533c2b
4
+ data.tar.gz: 5949f7165d856d5bfab33fb42cf157b859346651c5b927aa548b098841a5f5a5
5
5
  SHA512:
6
- metadata.gz: '0391681d670ed7beb995a2061b9587ba069f84f2e5581187d9b03727619df97ed8b8a774094336ee4553d24ef1dd42dffd09818eeb89e26d65a3234281c7d142'
7
- data.tar.gz: 32a4b9189c31b95c378c3637d43729d592308830e137c161e843f326f5b05541c82ef3dcec29c3225d047c582f697e9ec784553fa7c412ca0c6951303e1fc6bc
6
+ metadata.gz: 352a2c5566fa8b24966d8ce524a6d9b8fc0ebc91b3559b79d0bff16fd9c5436a9bbe6a0893084f15c4fbdd071382da25b455e7b915a271a5a45fcf8c11f9f5c6
7
+ data.tar.gz: 2febff28b74cf2b538b6d8767fc308946d48f7e75bff35991d8e30e4ac29a6794e21d75d37e213ef51a3d9dcf791a078c4890ec62fd931904d7199f74fd54d69
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,48 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- app_rail-airtable (0.3.6)
4
+ app_rail-airtable (0.4.1)
5
5
  activesupport
6
6
  airrecord
7
7
  bcrypt
8
+ faraday (~> 2.2)
9
+ faraday-net_http_persistent (~> 2.0)
8
10
  sinatra
9
11
  thor
10
12
 
11
13
  GEM
12
14
  remote: https://rubygems.org/
13
15
  specs:
14
- activesupport (7.0.2.2)
16
+ activesupport (7.0.2.3)
15
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
16
18
  i18n (>= 1.6, < 2)
17
19
  minitest (>= 5.1)
18
20
  tzinfo (~> 2.0)
19
- airrecord (1.0.7)
20
- faraday (>= 0.10, < 2.0)
21
+ airrecord (1.0.9)
22
+ faraday (>= 0.10, < 3.0)
23
+ faraday-net_http_persistent
21
24
  net-http-persistent
22
- bcrypt (3.1.16)
25
+ bcrypt (3.1.17)
23
26
  byebug (11.1.3)
24
- concurrent-ruby (1.1.9)
27
+ concurrent-ruby (1.1.10)
25
28
  connection_pool (2.2.5)
26
29
  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)
30
+ faraday (2.2.0)
31
+ faraday-net_http (~> 2.0)
38
32
  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)
33
+ faraday-net_http (2.0.2)
34
+ faraday-net_http_persistent (2.0.1)
35
+ faraday-net_http
36
+ net-http-persistent (~> 4.0)
37
+ i18n (1.10.0)
51
38
  concurrent-ruby (~> 1.0)
52
39
  minitest (5.15.0)
53
- multipart-post (2.1.1)
54
40
  mustermann (1.1.1)
55
41
  ruby2_keywords (~> 0.0.1)
56
42
  net-http-persistent (4.0.1)
57
43
  connection_pool (~> 2.2)
58
44
  rack (2.2.3)
59
- rack-protection (2.1.0)
45
+ rack-protection (2.2.0)
60
46
  rack
61
47
  rack-test (1.1.0)
62
48
  rack (>= 1.0, < 3)
@@ -75,10 +61,10 @@ GEM
75
61
  rspec-support (~> 3.10.0)
76
62
  rspec-support (3.10.2)
77
63
  ruby2_keywords (0.0.5)
78
- sinatra (2.1.0)
64
+ sinatra (2.2.0)
79
65
  mustermann (~> 1.0)
80
66
  rack (~> 2.2)
81
- rack-protection (= 2.1.0)
67
+ rack-protection (= 2.2.0)
82
68
  tilt (~> 2.0)
83
69
  thor (1.1.0)
84
70
  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,39 @@
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']
27
29
  spec.add_dependency 'activesupport'
28
- spec.add_dependency 'sinatra'
29
30
  spec.add_dependency 'airrecord'
30
- spec.add_dependency 'thor'
31
31
  spec.add_dependency 'bcrypt'
32
-
33
- spec.add_development_dependency 'rspec'
32
+ spec.add_dependency 'faraday', '~> 2.2'
33
+ spec.add_dependency 'faraday-net_http_persistent', '~> 2.0' # workaround to make Faraday work after upgrading to 2.2.0
34
+ spec.add_dependency 'sinatra'
35
+ spec.add_dependency 'thor'
36
+
34
37
  spec.add_development_dependency 'rack-test'
35
- end
38
+ spec.add_development_dependency 'rspec'
39
+ 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