app_rail-airtable 0.3.1 → 0.3.5

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: 15aed8edd922ec35c782dd42d03b7beb8e6a797b4bbc68a32f7db968b905c92c
4
- data.tar.gz: d864fd4adc4f4fdf2c96d7751e9c7ab7005d629ce0d4109f8221f3173796c7e1
3
+ metadata.gz: b2222da161b570e211092bd349e148d95a4101bfb927ec04af296b6bf3037adf
4
+ data.tar.gz: 8d040716239e2321007c4f4d534002f42ba0f5ee8995739be252365911949330
5
5
  SHA512:
6
- metadata.gz: 6876774d54ef6775fdebc8dc435a33f44ce4a298ac420031c369dcc049e7298d79a204bff276f7f19dba0e4322cce0aee7b089b142ad2d55fdbeeb477f55adc7
7
- data.tar.gz: f77814cfdecf5792443e48cc22be7faf05a533c9f798597f39a65c2cd311763a77a1e729e906b746e6c4a7331a9fd42b1a5456b2d2205102be64badfba8ac556
6
+ metadata.gz: b3844eb828e7aefe01844aedd2d934c72d9956d32e68f2f37747513d65947912b453e7f02f5ff7efae502fec6a9fbf66996d603ceabfe6c3fe352a6d628c5433
7
+ data.tar.gz: 142a3e8d514df35ffd9d52d3904ae370c3be998437115aad786d4770c3664fd933dd337468441c665e7e32eb85a5a66b0bb849447254934f09b1ee13e7f06533
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- app_rail-airtable (0.3.1)
4
+ app_rail-airtable (0.3.5)
5
5
  activesupport
6
6
  airrecord
7
7
  bcrypt
@@ -11,12 +11,11 @@ PATH
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- activesupport (6.1.4.1)
14
+ activesupport (7.0.1)
15
15
  concurrent-ruby (~> 1.0, >= 1.0.2)
16
16
  i18n (>= 1.6, < 2)
17
17
  minitest (>= 5.1)
18
18
  tzinfo (~> 2.0)
19
- zeitwerk (~> 2.3)
20
19
  airrecord (1.0.7)
21
20
  faraday (>= 0.10, < 2.0)
22
21
  net-http-persistent
@@ -25,28 +24,32 @@ GEM
25
24
  concurrent-ruby (1.1.9)
26
25
  connection_pool (2.2.5)
27
26
  diff-lcs (1.4.4)
28
- faraday (1.8.0)
27
+ faraday (1.9.3)
29
28
  faraday-em_http (~> 1.0)
30
29
  faraday-em_synchrony (~> 1.0)
31
30
  faraday-excon (~> 1.1)
32
- faraday-httpclient (~> 1.0.1)
31
+ faraday-httpclient (~> 1.0)
32
+ faraday-multipart (~> 1.0)
33
33
  faraday-net_http (~> 1.0)
34
- faraday-net_http_persistent (~> 1.1)
34
+ faraday-net_http_persistent (~> 1.0)
35
35
  faraday-patron (~> 1.0)
36
36
  faraday-rack (~> 1.0)
37
- multipart-post (>= 1.2, < 3)
37
+ faraday-retry (~> 1.0)
38
38
  ruby2_keywords (>= 0.0.4)
39
39
  faraday-em_http (1.0.0)
40
40
  faraday-em_synchrony (1.0.0)
41
41
  faraday-excon (1.1.0)
42
42
  faraday-httpclient (1.0.1)
43
+ faraday-multipart (1.0.3)
44
+ multipart-post (>= 1.2, < 3)
43
45
  faraday-net_http (1.0.1)
44
46
  faraday-net_http_persistent (1.2.0)
45
47
  faraday-patron (1.0.0)
46
48
  faraday-rack (1.0.0)
47
- i18n (1.8.10)
49
+ faraday-retry (1.0.3)
50
+ i18n (1.8.11)
48
51
  concurrent-ruby (~> 1.0)
49
- minitest (5.14.4)
52
+ minitest (5.15.0)
50
53
  multipart-post (2.1.1)
51
54
  mustermann (1.1.1)
52
55
  ruby2_keywords (~> 0.0.1)
@@ -81,7 +84,6 @@ GEM
81
84
  tilt (2.0.10)
82
85
  tzinfo (2.0.4)
83
86
  concurrent-ruby (~> 1.0)
84
- zeitwerk (2.4.2)
85
87
 
86
88
  PLATFORMS
87
89
  ruby
@@ -19,6 +19,10 @@ Gem::Specification.new do |spec|
19
19
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
20
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
21
  end
22
+
23
+ spec.bindir = 'bin'
24
+ spec.executables << 'ara_generator'
25
+
22
26
  spec.require_paths = ["lib"]
23
27
  spec.add_dependency 'activesupport'
24
28
  spec.add_dependency 'sinatra'
@@ -17,6 +17,7 @@ module AppRail
17
17
  def self.airtable_attr(*attributes)
18
18
  attributes.each do |attribute|
19
19
  define_method(attribute.to_s.snake_case) { self[attribute] }
20
+ define_method("#{attribute.to_s.snake_case}=") { |value| self[attribute] = value }
20
21
  end
21
22
  end
22
23
 
@@ -19,6 +19,8 @@ module AppRail
19
19
 
20
20
  def create_session_as_json(email:, password:)
21
21
  user = find_by_email_and_password(email, password)
22
+ return nil unless user
23
+
22
24
  user["Access Token"] = next_access_token
23
25
  user.save
24
26
  user&.oauth_session
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/string/inflections'
2
4
  require 'app_rail/airtable/string_ext'
3
5
  require 'thor'
@@ -11,21 +13,48 @@ module AppRail
11
13
  argument :output_directory
12
14
  argument :api_key
13
15
  argument :base_id
14
- argument :tables
16
+ argument :schema
15
17
 
16
18
  def self.source_root
17
- File.join(File.dirname(__FILE__), "..", "..", "..")
19
+ File.join(File.dirname(__FILE__), '..', '..', '..')
18
20
  end
19
21
 
20
22
  def create_project
21
- # Build tables
22
- @table_definitions = tables.split(",").map {|t| Airrecord.table(api_key, base_id, t.strip) }
23
+ validate_airtable_schema
23
24
  directory('templates/project', output_directory)
24
25
  end
25
-
26
+
26
27
  def self.exit_on_failure?
27
28
  true
28
29
  end
30
+
31
+ private
32
+
33
+ def validate_airtable_schema
34
+ tables.each do |table|
35
+ table_name = table['name']
36
+ table_fields = table['fields'].concat(table['associations']).map { |field| field['name'] }.compact
37
+
38
+ airtable_table = find_table(table_name)
39
+ compare_fields(table_fields, airtable_table)
40
+ end
41
+ end
42
+
43
+ def tables
44
+ @tables ||= JSON.parse(schema)['tables']
45
+ end
46
+
47
+ def find_table(table_name)
48
+ Airrecord.table(api_key, base_id, table_name)
49
+ end
50
+
51
+ def compare_fields(table_fields, airtable_table)
52
+ airtable_table.records.first.fields.each_key do |key|
53
+ next if table_fields.include? key
54
+
55
+ raise "ERROR! The key \"#{key}\" is not present in the schema definition."
56
+ end
57
+ end
29
58
  end
30
59
  end
31
- end
60
+ end
@@ -1,5 +1,5 @@
1
1
  module AppRail
2
2
  module Airtable
3
- VERSION = "0.3.1"
3
+ VERSION = "0.3.5"
4
4
  end
5
5
  end
@@ -5,17 +5,17 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
5
 
6
6
  ruby '2.7.4'
7
7
 
8
- gem 'app_rail-airtable'
8
+ gem 'app_rail-airtable', '~> 0.3.3'
9
9
 
10
10
  group :development do
11
- gem 'dotenv'
11
+ gem 'dotenv'
12
12
  end
13
13
 
14
14
  group :test do
15
- gem "rspec"
16
- gem "rack-test"
15
+ gem 'rack-test'
16
+ gem 'rspec'
17
17
  end
18
18
 
19
19
  group :development, :test do
20
- gem 'byebug'
21
- end
20
+ gem 'byebug'
21
+ end
@@ -10,10 +10,14 @@ GEM
10
10
  airrecord (1.0.7)
11
11
  faraday (>= 0.10, < 2.0)
12
12
  net-http-persistent
13
- app_rail-airtable (0.2.2)
13
+ app_rail-airtable (0.3.3)
14
14
  activesupport
15
15
  airrecord
16
+ bcrypt
16
17
  sinatra
18
+ thor
19
+ bcrypt (3.1.16)
20
+ byebug (11.1.3)
17
21
  concurrent-ruby (1.1.9)
18
22
  connection_pool (2.2.5)
19
23
  diff-lcs (1.4.4)
@@ -37,7 +41,7 @@ GEM
37
41
  faraday-net_http_persistent (1.2.0)
38
42
  faraday-patron (1.0.0)
39
43
  faraday-rack (1.0.0)
40
- i18n (1.8.10)
44
+ i18n (1.8.11)
41
45
  concurrent-ruby (~> 1.0)
42
46
  minitest (5.14.4)
43
47
  multipart-post (2.1.1)
@@ -62,23 +66,25 @@ GEM
62
66
  rspec-mocks (3.10.2)
63
67
  diff-lcs (>= 1.2.0, < 2.0)
64
68
  rspec-support (~> 3.10.0)
65
- rspec-support (3.10.2)
69
+ rspec-support (3.10.3)
66
70
  ruby2_keywords (0.0.5)
67
71
  sinatra (2.1.0)
68
72
  mustermann (~> 1.0)
69
73
  rack (~> 2.2)
70
74
  rack-protection (= 2.1.0)
71
75
  tilt (~> 2.0)
76
+ thor (1.1.0)
72
77
  tilt (2.0.10)
73
78
  tzinfo (2.0.4)
74
79
  concurrent-ruby (~> 1.0)
75
- zeitwerk (2.4.2)
80
+ zeitwerk (2.5.1)
76
81
 
77
82
  PLATFORMS
78
83
  ruby
79
84
 
80
85
  DEPENDENCIES
81
- app_rail-airtable
86
+ app_rail-airtable (~> 0.3.3)
87
+ byebug
82
88
  dotenv
83
89
  rack-test
84
90
  rspec
@@ -1,15 +1,62 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'app_rail/airtable'
4
+ require 'active_support/inflector'
5
+
6
+ <% @tables.each do |table| -%>
7
+ <% keys = table['fields'].map { |field| field['name'] if field['type'] != 'association' }.compact -%>
8
+ class <%= table['name'].singularize %> < AppRail::Airtable::ApplicationRecord
9
+ <% if table['authenticatable'] == 'True' -%>
10
+ include AppRail::Airtable::Authenticatable
2
11
 
3
- <% @table_definitions.each do |td| -%>
4
- <% keys = td.all.first.fields.keys -%>
5
- class <%= td.table_name.singularize %> < AppRail::Airtable::ApplicationRecord
12
+ <% end -%>
6
13
  airtable_attr <%= keys.map{|f| "\"#{f}\""}.join(", ") %>
7
- ar_list_item text: :<%= keys.first.snake_case %>
14
+ <% table['ar_class_methods']&.each do |method| -%>
15
+ <%= method['name'] %> <%= method['properties'].map {|prop| "#{prop['type']}: :#{prop['value']}" }.join(', ') %>
16
+ <% end -%>
17
+
18
+ <% table['associations']&.each do |association| -%>
19
+ <% word_ending_method = association['type'] == 'has_many' ? :pluralize : :singularize -%>
20
+ <%= association['type'] %> <%= ":#{association['model'].snake_case.send(word_ending_method)}, class: \"#{association['model'].camelize.singularize}\", column: \"#{association['model'].camelize.send(word_ending_method)}\"" %>
21
+ <% end -%>
22
+ <% if table['authenticatable'] == 'True' -%>
23
+
24
+ def self.create_as_json(params:, current_user:)
25
+ record = <%= table['name'].singularize %>.create(email: params["payload"]["email"], password: params["payload"]["password"])
26
+ { response: { id: record.id }, oauth_session: record.oauth_session}
27
+ end
28
+ <% end -%>
8
29
  end
9
30
 
10
31
  <% end -%>
32
+
33
+ <% authenticatable_table = @tables.select { |table| table['authenticatable'] == 'True' }&.first -%>
34
+ <% authenticatable_resource = authenticatable_table['name'] if authenticatable_table -%>
11
35
  class Server < AppRail::Airtable::Sinatra
12
- <% @table_definitions.each do |td| -%>
13
- resources :<%= td.table_name.snake_case %>
36
+ <% if authenticatable_resource -%>
37
+ <% model_name = authenticatable_resource.singularize -%>
38
+ helpers do
39
+ def find_authenticatable_resource(access_token:)
40
+ <%= model_name %>.find_by_access_token(access_token)
41
+ end
42
+ end
43
+
44
+ authenticatable_resources :<%= authenticatable_resource.snake_case %> do
45
+ <% @tables.each do |table| -%>
46
+ <% next if table['name'] == authenticatable_resource -%>
47
+ resources :<%= table['name'].snake_case %>
48
+ <% end -%>
49
+ end
50
+
51
+ post "/sessions" do
52
+ oauth_session = <%= model_name %>.create_session_as_json(email: params["username"], password: params["password"])
53
+ halt 401 unless oauth_session
54
+
55
+ oauth_session.to_json
56
+ end
57
+ <% else -%>
58
+ <% @tables.each do |table| -%>
59
+ resources :<%= table['name'].snake_case %>
60
+ <% end -%>
14
61
  <% end -%>
15
62
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% authenticatable_table = @tables.select { |table| table['authenticatable'] == 'True' }&.first -%>
4
+ <% authenticatable_resource = authenticatable_table['name'] if authenticatable_table -%>
5
+ require 'spec_helper'
6
+
7
+ RSpec.describe Server do
8
+ let(:json_response) { JSON.parse(response.body, symbolize_names: true) }
9
+ let(:access_token) { SecureRandom.hex }
10
+ let(:auth_headers) { { 'HTTP_AUTHORIZATION' => "Bearer: #{access_token}" } }
11
+
12
+ def app
13
+ Server
14
+ end
15
+
16
+ <% if authenticatable_resource -%>
17
+ <% auth_model_name = authenticatable_resource.singularize -%>
18
+ <% auth_path_name = authenticatable_resource.snake_case.pluralize -%>
19
+ describe 'POST /<%= auth_path_name %>' do
20
+ let(:params) { { payload: { email: 'test@test.com', password: 'Secret000' } } }
21
+ let(:response) { post "/<%= auth_path_name %>", params.to_json }
22
+ let(:mock_resource) { double(<%= auth_model_name %>) }
23
+
24
+ before { allow_any_instance_of(<%= auth_model_name %>).to receive(:create) { mock_resource } }
25
+
26
+ it { expect(response.status).to eq 201 }
27
+ it { expect(json_response[:oauth_session][:access_token]).to_not be_nil }
28
+ end
29
+
30
+ describe 'POST /sessions' do
31
+ let(:params) { { username: 'test@test.com', password: 'Secret000' } }
32
+ let(:response) { post "/sessions", params }
33
+
34
+ context 'ok' do
35
+ let(:oauth_session) { { access_token: SecureRandom.hex } }
36
+
37
+ before { allow(<%= auth_model_name %>).to receive(:create_session_as_json).with({ email: params[:username], password: params[:password] }) { oauth_session } }
38
+
39
+ it { expect(response.status).to eq 200 }
40
+ it { expect(json_response[:access_token]).to_not be_nil }
41
+ end
42
+
43
+ context 'invalid' do
44
+ before { allow(<%= auth_model_name %>).to receive(:create_session_as_json).with({ email: params[:username], password: params[:password] }) { nil } }
45
+
46
+ it { expect(response.status).to eq 401 }
47
+ it { expect(response.body).to eq '' }
48
+ end
49
+ end
50
+
51
+ <% @tables.each do |table| -%>
52
+ <% next if table['name'] == authenticatable_resource -%>
53
+ <% model_name = table['name'].singularize -%>
54
+ <% path_name = table['name'].snake_case.pluralize -%>
55
+ describe 'GET /<%= path_name %>' do
56
+ let(:params) { {} }
57
+ let(:response) { get '/<%= path_name %>', params.to_json, auth_headers}
58
+ let(:mock_auth_resource) { double(<%= auth_model_name %>) }
59
+ let(:mock_resource) { double(<%= model_name %>, id: '1') }
60
+ let(:resource_to_json) { { id: '1' } }
61
+
62
+ before do
63
+ allow(<%= auth_model_name %>).to receive(:find_by_access_token).with(access_token) { mock_auth_resource }
64
+ allow(<%= model_name %>).to receive(:all) { [mock_resource] }
65
+ allow(mock_resource).to receive(:ar_list_item_as_json) { resource_to_json }
66
+ end
67
+
68
+ it { expect(response.status).to eq 200 }
69
+ it { expect(json_response).to eq [resource_to_json] }
70
+ end
71
+
72
+ <% end -%>
73
+ <% else -%>
74
+ <% @tables.each do |table| -%>
75
+ <% model_name = table['name'].singularize -%>
76
+ <% path_name = table['name'].snake_case.pluralize -%>
77
+ describe 'GET /<%= path_name %>' do
78
+ let(:params) { {} }
79
+ let(:response) { get '/<%= path_name %>', params.to_json, auth_headers}
80
+ let(:mock_resource) { double(<%= model_name %>, id: '1') }
81
+ let(:resource_to_json) { { id: '1' } }
82
+
83
+ before do
84
+ allow(<%= model_name %>).to receive(:all) { [mock_resource] }
85
+ allow(mock_resource).to receive(:ar_list_item_as_json) { resource_to_json }
86
+ end
87
+
88
+ it { expect(response.status).to eq 200 }
89
+ it { expect(json_response).to eq [resource_to_json] }
90
+ end
91
+
92
+ <% end -%>
93
+ <% end -%>
94
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: app_rail-airtable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Brooke-Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-12 00:00:00.000000000 Z
11
+ date: 2022-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -111,7 +111,8 @@ dependencies:
111
111
  description:
112
112
  email:
113
113
  - matt@futureworkshops.com
114
- executables: []
114
+ executables:
115
+ - ara_generator
115
116
  extensions: []
116
117
  extra_rdoc_files: []
117
118
  files:
@@ -143,7 +144,7 @@ files:
143
144
  - templates/project/app.json
144
145
  - templates/project/config.ru
145
146
  - templates/project/lib/server.rb.tt
146
- - templates/project/spec/server_spec.rb
147
+ - templates/project/spec/server_spec.rb.tt
147
148
  - templates/project/spec/spec_helper.rb
148
149
  homepage: https://github.com/FutureWorkshops/app_rail-airtable
149
150
  licenses:
@@ -1,9 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe Server do
4
-
5
- def app
6
- Server
7
- end
8
-
9
- end