app_rail-airtable 0.3.0 → 0.3.4

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: 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