fmrest 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.yardopts +1 -0
  4. data/README.md +101 -7
  5. data/fmrest.gemspec +3 -0
  6. data/lib/fmrest.rb +2 -0
  7. data/lib/fmrest/errors.rb +27 -0
  8. data/lib/fmrest/spyke.rb +9 -0
  9. data/lib/fmrest/spyke/base.rb +2 -0
  10. data/lib/fmrest/spyke/container_field.rb +59 -0
  11. data/lib/fmrest/spyke/json_parser.rb +83 -24
  12. data/lib/fmrest/spyke/model.rb +7 -0
  13. data/lib/fmrest/spyke/model/associations.rb +2 -0
  14. data/lib/fmrest/spyke/model/attributes.rb +14 -55
  15. data/lib/fmrest/spyke/model/connection.rb +2 -0
  16. data/lib/fmrest/spyke/model/container_fields.rb +25 -0
  17. data/lib/fmrest/spyke/model/orm.rb +72 -5
  18. data/lib/fmrest/spyke/model/serialization.rb +80 -0
  19. data/lib/fmrest/spyke/model/uri.rb +2 -0
  20. data/lib/fmrest/spyke/portal.rb +2 -0
  21. data/lib/fmrest/spyke/relation.rb +30 -14
  22. data/lib/fmrest/token_store.rb +6 -0
  23. data/lib/fmrest/token_store/active_record.rb +74 -0
  24. data/lib/fmrest/token_store/base.rb +25 -0
  25. data/lib/fmrest/token_store/memory.rb +26 -0
  26. data/lib/fmrest/token_store/redis.rb +45 -0
  27. data/lib/fmrest/v1.rb +10 -49
  28. data/lib/fmrest/v1/connection.rb +57 -0
  29. data/lib/fmrest/v1/container_fields.rb +73 -0
  30. data/lib/fmrest/v1/paths.rb +36 -0
  31. data/lib/fmrest/v1/raise_errors.rb +55 -0
  32. data/lib/fmrest/v1/token_session.rb +32 -12
  33. data/lib/fmrest/v1/token_store/active_record.rb +6 -66
  34. data/lib/fmrest/v1/token_store/memory.rb +6 -19
  35. data/lib/fmrest/v1/utils.rb +94 -0
  36. data/lib/fmrest/version.rb +3 -1
  37. metadata +60 -5
  38. data/lib/fmrest/v1/token_store.rb +0 -6
  39. data/lib/fmrest/v1/token_store/base.rb +0 -14
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FmRest
2
4
  module V1
3
5
  # FM Data API authentication middleware using the credentials strategy
4
6
  #
5
7
  class TokenSession < Faraday::Middleware
6
8
  HEADER_KEY = "Authorization".freeze
9
+ TOKEN_STORE_INTERFACE = [:load, :store, :delete].freeze
7
10
 
8
11
  def initialize(app, options = FmRest.config)
9
12
  super(app)
@@ -20,7 +23,7 @@ module FmRest
20
23
  @app.call(env).on_complete do |response_env|
21
24
  if response_env[:status] == 401 # Unauthorized
22
25
  env[:body] = request_body
23
- token_store.clear
26
+ token_store.delete(token_store_key)
24
27
  set_auth_header(env)
25
28
  return @app.call(env)
26
29
  end
@@ -38,11 +41,11 @@ module FmRest
38
41
  # otherwise raises an exception.
39
42
  #
40
43
  def token
41
- token = token_store.fetch
44
+ token = token_store.load(token_store_key)
42
45
  return token if token
43
46
 
44
47
  if token = request_token
45
- token_store.store(token)
48
+ token_store.store(token_store_key, token)
46
49
  return token
47
50
  end
48
51
 
@@ -61,19 +64,36 @@ module FmRest
61
64
  false
62
65
  end
63
66
 
64
- def token_store
65
- @token_store ||= token_store_class.new(@options.fetch(:host), @options.fetch(:database))
67
+ # The key to use to store a token, uses the format host:database
68
+ #
69
+ def token_store_key
70
+ @token_store_key ||=
71
+ begin
72
+ # Strip the host part to just the hostname (i.e. no scheme or port)
73
+ host = @options.fetch(:host)
74
+ host = URI(host).hostname if host.match?(/\Ahttps?:\/\//)
75
+ "#{host}:#{@options.fetch(:database)}"
76
+ end
66
77
  end
67
78
 
68
- def token_store_class
69
- FmRest.token_store ||
79
+ def token_store
80
+ @token_store ||=
70
81
  begin
71
- # TODO: Make this less ugly
72
- require "fmrest/v1/token_store/memory"
73
- TokenStore::Memory
82
+ if TOKEN_STORE_INTERFACE.all? { |method| token_store_option.respond_to?(method) }
83
+ token_store_option
84
+ elsif token_store_option.kind_of?(Class)
85
+ token_store_option.new
86
+ else
87
+ require "fmrest/token_store/memory"
88
+ TokenStore::Memory.new
89
+ end
74
90
  end
75
91
  end
76
92
 
93
+ def token_store_option
94
+ @options[:token_store] || FmRest.token_store
95
+ end
96
+
77
97
  def auth_connection
78
98
  @auth_connection ||= V1.base_connection(@options) do |conn|
79
99
  conn.basic_auth @options.fetch(:username), @options.fetch(:password)
@@ -82,8 +102,8 @@ module FmRest
82
102
  conn.response :logger, nil, bodies: true, headers: true
83
103
  end
84
104
 
85
- conn.response :json
86
- conn.adapter Faraday.default_adapter
105
+ conn.response :json
106
+ conn.adapter Faraday.default_adapter
87
107
  end
88
108
  end
89
109
  end
@@ -1,73 +1,13 @@
1
- require "fmrest/v1/token_store/base"
1
+ # frozen_string_literal: true
2
+
3
+ warn "FmRest::V1::TokenStore::ActiveRecord is deprecated, use FmRest::TokenStore::ActiveRecord instead"
4
+
5
+ require "fmrest/token_store/active_record"
2
6
 
3
7
  module FmRest
4
8
  module V1
5
9
  module TokenStore
6
- # Heavily inspired by Moneta's ActiveRecord store:
7
- #
8
- # https://github.com/minad/moneta/blob/master/lib/moneta/adapters/activerecord.rb
9
- #
10
- class ActiveRecord < Base
11
- DEFAULT_TABLE_NAME = "fmrest_session_tokens".freeze
12
-
13
- @connection_lock = ::Mutex.new
14
- class << self
15
- attr_reader :connection_lock
16
- end
17
-
18
- attr_reader :connection_pool, :model
19
-
20
- delegate :with_connection, to: :connection_pool
21
-
22
- def initialize(host, database, options = {})
23
- super
24
-
25
- @connection_pool = ::ActiveRecord::Base.connection_pool
26
-
27
- create_table
28
-
29
- @model = Class.new(::ActiveRecord::Base)
30
- @model.table_name = table_name
31
- end
32
-
33
- def clear
34
- model.where(scope: scope).delete_all
35
- end
36
-
37
- def fetch
38
- model.where(scope: scope).pluck(:token).first
39
- end
40
-
41
- def store(token)
42
- record = model.find_or_initialize_by(scope: scope)
43
- record.token = token
44
- record.save!
45
- token
46
- end
47
-
48
- private
49
-
50
- def create_table
51
- with_connection do |conn|
52
- return if conn.table_exists?(table_name)
53
-
54
- # Prevent multiple connections from attempting to create the table simultaneously.
55
- self.class.connection_lock.synchronize do
56
- conn.create_table(table_name, id: false) do |t|
57
- t.string :scope, null: false
58
- t.string :token, null: false
59
- t.datetime :updated_at
60
- end
61
- conn.add_index(table_name, :scope, unique: true)
62
- conn.add_index(table_name, [:scope, :token])
63
- end
64
- end
65
- end
66
-
67
- def table_name
68
- options[:table_name] || DEFAULT_TABLE_NAME
69
- end
70
- end
10
+ ActiveRecord = ::FmRest::TokenStore::ActiveRecord
71
11
  end
72
12
  end
73
13
  end
@@ -1,26 +1,13 @@
1
- require "fmrest/v1/token_store/base"
1
+ # frozen_string_literal: true
2
+
3
+ warn "FmRest::V1::TokenStore::Memory is deprecated, use FmRest::TokenStore::Memory instead"
4
+
5
+ require "fmrest/token_store/memory"
2
6
 
3
7
  module FmRest
4
8
  module V1
5
9
  module TokenStore
6
- class Memory < Base
7
- def initialize(host, database, options = {})
8
- super
9
- @tokens = {}
10
- end
11
-
12
- def clear
13
- @tokens.delete(scope)
14
- end
15
-
16
- def fetch
17
- @tokens[scope]
18
- end
19
-
20
- def store(token)
21
- @tokens[scope] = token
22
- end
23
- end
10
+ Memory = ::FmRest::TokenStore::Memory
24
11
  end
25
12
  end
26
13
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FmRest
4
+ module V1
5
+ module Utils
6
+ VALID_SCRIPT_KEYS = [:prerequest, :presort, :after].freeze
7
+
8
+ # Converts custom script options to a hash with the Data API's expected
9
+ # JSON script format.
10
+ #
11
+ # If script_options is a string or symbol it will be passed as the name
12
+ # of the script to execute (after the action, e.g. save).
13
+ #
14
+ # If script_options is an array the first element will be the name of the
15
+ # script to execute (after the action) and the second element (if any)
16
+ # will be its param value.
17
+ #
18
+ # If script_options is a hash it will expect to contain one or more of
19
+ # the following keys: :prerequest, :presort, :after
20
+ #
21
+ # Any of those keys should contain either a string/symbol or array, which
22
+ # will be treated as described above, except for their own script
23
+ # execution order (prerequest, presort or after action).
24
+ #
25
+ # Examples:
26
+ #
27
+ # convert_script_params("My Script")
28
+ # # => { "script": "My Script" }
29
+ #
30
+ # convert_script_params(["My Script", "the param"])
31
+ # # => { "script": "My Script", "script.param": "the param" }
32
+ #
33
+ # convert_script_params(after: "After Script", prerequest: "Prerequest Script")
34
+ # # => { "script": "After Script", "script.prerequest": "Prerequest Script" }
35
+ #
36
+ # convert_script_params(presort: ["Presort Script", "foo"], prerequest: "Prerequest Script")
37
+ # # => {
38
+ # # "script.presort": "After Script",
39
+ # # "script.presort.param": "foo",
40
+ # # "script.prerequest": "Prerequest Script"
41
+ # # }
42
+ #
43
+ def convert_script_params(script_options)
44
+ params = {}
45
+
46
+ case script_options
47
+ when String, Symbol
48
+ params[:script] = script_options.to_s
49
+
50
+ when Array
51
+ params.merge!(convert_script_arguments(script_options))
52
+
53
+ when Hash
54
+ script_options.each_key do |key|
55
+ next if VALID_SCRIPT_KEYS.include?(key)
56
+ raise ArgumentError, "Invalid script option #{key.inspect}"
57
+ end
58
+
59
+ if script_options.has_key?(:prerequest)
60
+ params.merge!(convert_script_arguments(script_options[:prerequest], :prerequest))
61
+ end
62
+
63
+ if script_options.has_key?(:presort)
64
+ params.merge!(convert_script_arguments(script_options[:presort], :presort))
65
+ end
66
+
67
+ if script_options.has_key?(:after)
68
+ params.merge!(convert_script_arguments(script_options[:after]))
69
+ end
70
+ end
71
+
72
+ params
73
+ end
74
+
75
+ private
76
+
77
+ def convert_script_arguments(script_arguments, suffix = nil)
78
+ base = suffix ? "script.#{suffix}".to_sym : :script
79
+
80
+ {}.tap do |params|
81
+ case script_arguments
82
+ when String, Symbol
83
+ params[base] = script_arguments.to_s
84
+ when Array
85
+ params[base] = script_arguments.first.to_s
86
+ params["#{base}.param".to_sym] = script_arguments[1] if script_arguments[1]
87
+ else
88
+ raise ArgumentError, "Script arguments are expected as a String, Symbol or Array"
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FmRest
2
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
3
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fmrest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pedro Carbajal
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-01-10 00:00:00.000000000 Z
11
+ date: 2019-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -148,6 +148,48 @@ dependencies:
148
148
  - - ">="
149
149
  - !ruby/object:Gem::Version
150
150
  version: '0'
151
+ - !ruby/object:Gem::Dependency
152
+ name: activerecord
153
+ requirement: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ type: :development
159
+ prerelease: false
160
+ version_requirements: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ - !ruby/object:Gem::Dependency
166
+ name: sqlite3
167
+ requirement: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: 1.3.6
172
+ type: :development
173
+ prerelease: false
174
+ version_requirements: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: 1.3.6
179
+ - !ruby/object:Gem::Dependency
180
+ name: mock_redis
181
+ requirement: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
151
193
  description: FileMaker Data API client using Faraday, with optional ActiveRecord-like
152
194
  ORM based on Spyke
153
195
  email:
@@ -159,6 +201,7 @@ files:
159
201
  - ".gitignore"
160
202
  - ".rspec"
161
203
  - ".travis.yml"
204
+ - ".yardopts"
162
205
  - CODE_OF_CONDUCT.md
163
206
  - Gemfile
164
207
  - LICENSE.txt
@@ -166,23 +209,35 @@ files:
166
209
  - Rakefile
167
210
  - fmrest.gemspec
168
211
  - lib/fmrest.rb
212
+ - lib/fmrest/errors.rb
169
213
  - lib/fmrest/spyke.rb
170
214
  - lib/fmrest/spyke/base.rb
215
+ - lib/fmrest/spyke/container_field.rb
171
216
  - lib/fmrest/spyke/json_parser.rb
172
217
  - lib/fmrest/spyke/model.rb
173
218
  - lib/fmrest/spyke/model/associations.rb
174
219
  - lib/fmrest/spyke/model/attributes.rb
175
220
  - lib/fmrest/spyke/model/connection.rb
221
+ - lib/fmrest/spyke/model/container_fields.rb
176
222
  - lib/fmrest/spyke/model/orm.rb
223
+ - lib/fmrest/spyke/model/serialization.rb
177
224
  - lib/fmrest/spyke/model/uri.rb
178
225
  - lib/fmrest/spyke/portal.rb
179
226
  - lib/fmrest/spyke/relation.rb
227
+ - lib/fmrest/token_store.rb
228
+ - lib/fmrest/token_store/active_record.rb
229
+ - lib/fmrest/token_store/base.rb
230
+ - lib/fmrest/token_store/memory.rb
231
+ - lib/fmrest/token_store/redis.rb
180
232
  - lib/fmrest/v1.rb
233
+ - lib/fmrest/v1/connection.rb
234
+ - lib/fmrest/v1/container_fields.rb
235
+ - lib/fmrest/v1/paths.rb
236
+ - lib/fmrest/v1/raise_errors.rb
181
237
  - lib/fmrest/v1/token_session.rb
182
- - lib/fmrest/v1/token_store.rb
183
238
  - lib/fmrest/v1/token_store/active_record.rb
184
- - lib/fmrest/v1/token_store/base.rb
185
239
  - lib/fmrest/v1/token_store/memory.rb
240
+ - lib/fmrest/v1/utils.rb
186
241
  - lib/fmrest/version.rb
187
242
  homepage: https://github.com/beezwax/fmrest-ruby
188
243
  licenses:
@@ -204,7 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
259
  version: '0'
205
260
  requirements: []
206
261
  rubyforge_project:
207
- rubygems_version: 2.7.7
262
+ rubygems_version: 2.7.8
208
263
  signing_key:
209
264
  specification_version: 4
210
265
  summary: FileMaker Data API client using Faraday
@@ -1,6 +0,0 @@
1
- module FmRest
2
- module V1
3
- module TokenStore
4
- end
5
- end
6
- end
@@ -1,14 +0,0 @@
1
- module FmRest
2
- module V1
3
- module TokenStore
4
- class Base
5
- attr_reader :scope, :options
6
-
7
- def initialize(host, database, options = {})
8
- @scope = "#{host.to_s}:#{database.to_s}"
9
- @options = options
10
- end
11
- end
12
- end
13
- end
14
- end