app_rail-airtable 0.3.0 → 0.3.4

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: 592d97e9fc6dbe4177847fdbfbc5c003620d179caee7887efb23f91d5f736ca7
4
- data.tar.gz: 985e3ac9e23f4fe318c4944c2c3720a656faead5b5de3e3149fe58013aaa173c
3
+ metadata.gz: 66fa38f57c975b6de65ce8ab4548421e8063e50774245f37c9ed38ef13802747
4
+ data.tar.gz: a13a5199edac840e26ed2e70676c3eff4f36c000c4af80cdf710cf3a47fad56a
5
5
  SHA512:
6
- metadata.gz: 80ae0eea7fbd168f1b469a5bfc9255940f0c62875d232427fd654d123e3b02aab822646cf6fa23313ca3fe7e653d0d73935927b8c6dff74dec8a01acf5edd431
7
- data.tar.gz: 83e8736f2dc7dc42d276c45a83d8597c98d1f38faee8cf09e581f19256f22f64de81fa34a1acf9f290f9e4638a4bc00f14e25787d29bf2507d2bc3bbba44a13b
6
+ metadata.gz: 002c107e9dd70908b323f836240ec3c4253f9fa8393a2ce2312812d189804618fe0070a0a47485833111299ec6a8d0e6168ccc427d953eb6137cf367bc708e61
7
+ data.tar.gz: b5b4ff1a93cb0608a08a64bc2197f7b2961a5eadc876a9564d2d3a5e285ddf38b4185d03d68ba5c894f90ad974e32de93c0852b86a1dd34c28388e6b364003d3
data/.gitignore CHANGED
@@ -1,3 +1,7 @@
1
+ .DS_STORE
2
+ *.swp
3
+ *.rbc
4
+ *.sass-cache
1
5
  /.bundle/
2
6
  /.yardoc
3
7
  /_yardoc/
@@ -10,3 +14,6 @@
10
14
  # rspec failure tracking
11
15
  .rspec_status
12
16
  .byebug_history
17
+
18
+ # dotenv
19
+ .env
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- app_rail-airtable (0.3.0)
4
+ app_rail-airtable (0.3.4)
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
 
@@ -12,13 +12,15 @@ module AppRail
12
12
 
13
13
  module ClassMethods
14
14
  def create(email:, password:)
15
- user = User.new("Email" => email, "Password Hash" => password_hash(password), "Access Token" => access_token)
15
+ user = User.new("Email" => email, "Password Hash" => password_hash(password), "Access Token" => next_access_token)
16
16
  user.create
17
17
  user
18
18
  end
19
19
 
20
20
  def create_session_as_json(email:, password:)
21
21
  user = find_by_email_and_password(email, password)
22
+ user["Access Token"] = next_access_token
23
+ user.save
22
24
  user&.oauth_session
23
25
  end
24
26
 
@@ -35,7 +37,7 @@ module AppRail
35
37
  BCrypt::Password.create(password)
36
38
  end
37
39
 
38
- def access_token
40
+ def next_access_token
39
41
  SecureRandom.hex
40
42
  end
41
43
  end
@@ -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
@@ -44,6 +44,8 @@ module AppRail
44
44
  helpers AppRail::Airtable::AuthenticationHelpers
45
45
 
46
46
  resources(name, only: only)
47
+
48
+ return unless block_given?
47
49
  @@authenticated_route = true
48
50
  yield
49
51
  @@authenticated_route = false
@@ -1,5 +1,5 @@
1
1
  module AppRail
2
2
  module Airtable
3
- VERSION = "0.3.0"
3
+ VERSION = "0.3.4"
4
4
  end
5
5
  end
@@ -2,7 +2,18 @@
2
2
  *.swp
3
3
  *.rbc
4
4
  *.sass-cache
5
- /pkg
6
- /coverage
7
- .yardoc
8
- /doc
5
+ /.bundle/
6
+ /.yardoc
7
+ /_yardoc/
8
+ /coverage/
9
+ /doc/
10
+ /pkg/
11
+ /spec/reports/
12
+ /tmp/
13
+
14
+ # rspec failure tracking
15
+ .rspec_status
16
+ .byebug_history
17
+
18
+ # dotenv
19
+ .env
@@ -5,11 +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
- # dev
11
- gem 'dotenv'
10
+ group :development do
11
+ gem 'dotenv'
12
+ end
12
13
 
13
- # test
14
- gem "rspec"
15
- gem "rack-test"
14
+ group :test do
15
+ gem 'rack-test'
16
+ gem 'rspec'
17
+ end
18
+
19
+ group :development, :test do
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,5 +1,11 @@
1
1
  $stdout.sync = true
2
2
 
3
- require 'dotenv/load'
3
+ ENV['RACK_ENV'] ||= 'development'
4
+ require 'dotenv/load' if ENV['RACK_ENV'] == 'development'
5
+ require 'bundler'
6
+ Bundler.require(:default, ENV['RACK_ENV'].to_sym)
7
+
8
+ require 'dotenv/load' if ENV['RACK_ENV'] == 'development'
9
+
4
10
  require './lib/server'
5
11
  run Server
@@ -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
@@ -23,7 +23,6 @@ require "rack/test"
23
23
  require "app_rail/airtable"
24
24
 
25
25
  require './lib/server'
26
- require './lib/item_recommender'
27
26
 
28
27
  RSpec.configure do |config|
29
28
  # rspec-expectations config goes here. You can use an alternate
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.0
4
+ version: 0.3.4
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-09 00:00:00.000000000 Z
11
+ date: 2022-01-19 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