fmrest 0.11.0 → 0.14.0

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +32 -0
  4. data/README.md +228 -844
  5. metadata +71 -101
  6. data/.github/workflows/ci.yml +0 -33
  7. data/.gitignore +0 -26
  8. data/.rspec +0 -3
  9. data/.travis.yml +0 -5
  10. data/Gemfile +0 -3
  11. data/Rakefile +0 -6
  12. data/fmrest.gemspec +0 -38
  13. data/lib/fmrest.rb +0 -34
  14. data/lib/fmrest/connection_settings.rb +0 -124
  15. data/lib/fmrest/errors.rb +0 -30
  16. data/lib/fmrest/spyke.rb +0 -21
  17. data/lib/fmrest/spyke/base.rb +0 -23
  18. data/lib/fmrest/spyke/container_field.rb +0 -59
  19. data/lib/fmrest/spyke/model.rb +0 -36
  20. data/lib/fmrest/spyke/model/associations.rb +0 -82
  21. data/lib/fmrest/spyke/model/attributes.rb +0 -171
  22. data/lib/fmrest/spyke/model/auth.rb +0 -43
  23. data/lib/fmrest/spyke/model/connection.rb +0 -135
  24. data/lib/fmrest/spyke/model/container_fields.rb +0 -25
  25. data/lib/fmrest/spyke/model/global_fields.rb +0 -40
  26. data/lib/fmrest/spyke/model/http.rb +0 -37
  27. data/lib/fmrest/spyke/model/orm.rb +0 -212
  28. data/lib/fmrest/spyke/model/serialization.rb +0 -91
  29. data/lib/fmrest/spyke/model/uri.rb +0 -30
  30. data/lib/fmrest/spyke/portal.rb +0 -55
  31. data/lib/fmrest/spyke/relation.rb +0 -359
  32. data/lib/fmrest/spyke/spyke_formatter.rb +0 -273
  33. data/lib/fmrest/spyke/validation_error.rb +0 -25
  34. data/lib/fmrest/string_date.rb +0 -220
  35. data/lib/fmrest/token_store.rb +0 -12
  36. data/lib/fmrest/token_store/active_record.rb +0 -74
  37. data/lib/fmrest/token_store/base.rb +0 -25
  38. data/lib/fmrest/token_store/memory.rb +0 -26
  39. data/lib/fmrest/token_store/moneta.rb +0 -41
  40. data/lib/fmrest/token_store/redis.rb +0 -45
  41. data/lib/fmrest/v1.rb +0 -23
  42. data/lib/fmrest/v1/auth.rb +0 -30
  43. data/lib/fmrest/v1/connection.rb +0 -115
  44. data/lib/fmrest/v1/container_fields.rb +0 -114
  45. data/lib/fmrest/v1/dates.rb +0 -81
  46. data/lib/fmrest/v1/paths.rb +0 -47
  47. data/lib/fmrest/v1/raise_errors.rb +0 -59
  48. data/lib/fmrest/v1/token_session.rb +0 -134
  49. data/lib/fmrest/v1/token_store/active_record.rb +0 -13
  50. data/lib/fmrest/v1/token_store/memory.rb +0 -13
  51. data/lib/fmrest/v1/type_coercer.rb +0 -192
  52. data/lib/fmrest/v1/utils.rb +0 -94
  53. data/lib/fmrest/version.rb +0 -5
@@ -1,124 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- # Wrapper class for connection settings hash, with a number of purposes:
5
- #
6
- # * Provide indifferent access (base hash can have either string or symbol
7
- # keys)
8
- # * Method access
9
- # * Default values
10
- # * Basic validation
11
- # * Normalization (e.g. aliased settings)
12
- # * Useful error messages
13
- class ConnectionSettings
14
- class MissingSetting < ArgumentError; end
15
-
16
- PROPERTIES = %i(
17
- host
18
- database
19
- username
20
- password
21
- token
22
- token_store
23
- autologin
24
- ssl
25
- proxy
26
- log
27
- coerce_dates
28
- date_format
29
- timestamp_format
30
- time_format
31
- timezone
32
- ).freeze
33
-
34
- # NOTE: password intentionally left non-required since it's only really
35
- # needed when no token exists, and should only be required when logging in
36
- REQUIRED = %i(
37
- host
38
- database
39
- ).freeze
40
-
41
- DEFAULT_DATE_FORMAT = "MM/dd/yyyy"
42
- DEFAULT_TIME_FORMAT = "HH:mm:ss"
43
- DEFAULT_TIMESTAMP_FORMAT = "#{DEFAULT_DATE_FORMAT} #{DEFAULT_TIME_FORMAT}"
44
-
45
- DEFAULTS = {
46
- autologin: true,
47
- log: false,
48
- date_format: DEFAULT_DATE_FORMAT,
49
- time_format: DEFAULT_TIME_FORMAT,
50
- timestamp_format: DEFAULT_TIMESTAMP_FORMAT,
51
- coerce_dates: false
52
- }.freeze
53
-
54
- def self.wrap(settings, skip_validation: false)
55
- if settings.kind_of?(self)
56
- settings.validate unless skip_validation
57
- return settings
58
- end
59
- new(settings, skip_validation: skip_validation)
60
- end
61
-
62
- def initialize(settings, skip_validation: false)
63
- @settings = settings.to_h.dup
64
- normalize
65
- validate unless skip_validation
66
- end
67
-
68
- PROPERTIES.each do |p|
69
- define_method(p) do
70
- get(p)
71
- end
72
-
73
- define_method("#{p}!") do
74
- r = get(p)
75
- raise MissingSetting, "Missing required setting: `#{p}'" if r.nil?
76
- r
77
- end
78
-
79
- define_method("#{p}?") do
80
- !!get(p)
81
- end
82
- end
83
-
84
- def [](key)
85
- raise ArgumentError, "Unknown setting `#{key}'" unless PROPERTIES.include?(key.to_sym)
86
- get(key)
87
- end
88
-
89
- def to_h
90
- PROPERTIES.each_with_object({}) do |p, h|
91
- v = get(p)
92
- h[p] = v unless v == DEFAULTS[p]
93
- end
94
- end
95
-
96
- def merge(other, **keyword_args)
97
- other = self.class.wrap(other, skip_validation: true)
98
- self.class.new(to_h.merge(other.to_h), **keyword_args)
99
- end
100
-
101
- def validate
102
- missing = REQUIRED.select { |r| get(r).nil? }.map { |m| "`#{m}'" }
103
- raise MissingSetting, "Missing required setting(s): #{missing.join(', ')}" unless missing.empty?
104
-
105
- unless username? || token?
106
- raise MissingSetting, "A minimum of `username' or `token' are required to be able to establish a connection"
107
- end
108
- end
109
-
110
- private
111
-
112
- def get(key)
113
- return @settings[key.to_sym] if @settings.has_key?(key.to_sym)
114
- return @settings[key.to_s] if @settings.has_key?(key.to_s)
115
- DEFAULTS[key.to_sym]
116
- end
117
-
118
- def normalize
119
- if !get(:username) && account_name = get(:account_name)
120
- @settings[:username] = account_name
121
- end
122
- end
123
- end
124
- end
data/lib/fmrest/errors.rb DELETED
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- class Error < StandardError; end
5
-
6
- class APIError < Error
7
- attr_reader :code
8
-
9
- def initialize(code, message = nil)
10
- @code = code
11
- super("FileMaker Data API responded with error #{code}: #{message}")
12
- end
13
- end
14
-
15
- class APIError::UnknownError < APIError; end # error code -1
16
- class APIError::ResourceMissingError < APIError; end # error codes 100..199
17
- class APIError::RecordMissingError < APIError::ResourceMissingError; end
18
- class APIError::AccountError < APIError; end # error codes 200..299
19
- class APIError::LockError < APIError; end # error codes 300..399
20
- class APIError::ParameterError < APIError; end # error codes 400..499
21
- class APIError::NoMatchingRecordsError < APIError::ParameterError; end
22
- class APIError::ValidationError < APIError; end # error codes 500..599
23
- class APIError::SystemError < APIError; end # error codes 800..899
24
- class APIError::InvalidToken < APIError; end # error code 952
25
- class APIError::MaximumDataAPICallsExceeded < APIError; end # error code 953
26
- class APIError::ScriptError < APIError; end # error codes 1200..1299
27
- class APIError::ODBCError < APIError; end # error codes 1400..1499
28
-
29
- class ContainerFieldError < Error; end
30
- end
data/lib/fmrest/spyke.rb DELETED
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- begin
4
- require "spyke"
5
- rescue LoadError => e
6
- e.message << " (Did you include Spyke in your Gemfile?)" unless e.message.frozen?
7
- raise e
8
- end
9
-
10
- require "fmrest"
11
- require "fmrest/spyke/spyke_formatter"
12
- require "fmrest/spyke/model"
13
- require "fmrest/spyke/base"
14
-
15
- module FmRest
16
- module Spyke
17
- def self.included(base)
18
- base.include Model
19
- end
20
- end
21
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- class Base < ::Spyke::Base
6
- include FmRest::Spyke::Model
7
- end
8
-
9
- class << self
10
- def Base(config = nil)
11
- warn "[DEPRECATION] Inheriting from `FmRest::Spyke::Base(config)` is deprecated and will be removed, inherit from `FmRest::Spyke::Base` (without arguments) and use `fmrest_config=` instead"
12
-
13
- if config
14
- return Class.new(::FmRest::Spyke::Base) do
15
- self.fmrest_config = config
16
- end
17
- end
18
-
19
- ::FmRest::Spyke::Base
20
- end
21
- end
22
- end
23
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- class ContainerField
6
-
7
- # @return [String] the name of the container field
8
- attr_reader :name
9
-
10
- # @param base [FmRest::Spyke::Base] the record this container belongs to
11
- # @param name [Symbol] the name of the container field
12
- def initialize(base, name)
13
- @base = base
14
- @name = name
15
- end
16
-
17
- # @return [String] the URL for the container
18
- def url
19
- @base.attributes[name]
20
- end
21
-
22
- # @return (see FmRest::V1::ContainerFields#fetch_container_data)
23
- def download
24
- FmRest::V1.fetch_container_data(url, @base.class.connection)
25
- end
26
-
27
- # @param filename_or_io [String, IO] a path to the file to upload or an
28
- # IO object
29
- # @param options [Hash]
30
- # @option options [Integer] :repetition (1) The repetition to pass to the
31
- # upload URL
32
- # @option (see FmRest::V1::ContainerFields#upload_container_data)
33
- def upload(filename_or_io, options = {})
34
- raise ArgumentError, "Record needs to be saved before uploading to a container field" unless @base.persisted?
35
-
36
- response =
37
- FmRest::V1.upload_container_data(
38
- @base.class.connection,
39
- upload_path(options[:repetition] || 1),
40
- filename_or_io,
41
- options
42
- )
43
-
44
- # Update mod id on record
45
- @base.mod_id = response.body[:data][:mod_id]
46
-
47
- true
48
- end
49
-
50
- private
51
-
52
- # @param repetition [Integer]
53
- # @return [String] the path for uploading a file to the container
54
- def upload_path(repetition)
55
- FmRest::V1.container_field_path(@base.class.layout, @base.id, name, repetition)
56
- end
57
- end
58
- end
59
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/model/connection"
4
- require "fmrest/spyke/model/uri"
5
- require "fmrest/spyke/model/attributes"
6
- require "fmrest/spyke/model/serialization"
7
- require "fmrest/spyke/model/associations"
8
- require "fmrest/spyke/model/orm"
9
- require "fmrest/spyke/model/container_fields"
10
- require "fmrest/spyke/model/global_fields"
11
- require "fmrest/spyke/model/http"
12
- require "fmrest/spyke/model/auth"
13
-
14
- module FmRest
15
- module Spyke
16
- module Model
17
- extend ::ActiveSupport::Concern
18
-
19
- include Connection
20
- include Uri
21
- include Attributes
22
- include Serialization
23
- include Associations
24
- include Orm
25
- include ContainerFields
26
- include GlobalFields
27
- include Http
28
- include Auth
29
-
30
- included do
31
- # @return [Integer] the record's modId
32
- attr_accessor :mod_id
33
- end
34
- end
35
- end
36
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/portal"
4
-
5
- module FmRest
6
- module Spyke
7
- module Model
8
- module Associations
9
- extend ::ActiveSupport::Concern
10
-
11
- included do
12
- # Keep track of portal options by their FM keys as we could need it
13
- # to parse the portalData JSON in SpykeFormatter
14
- class_attribute :portal_options, instance_accessor: false, instance_predicate: false
15
-
16
- # class_attribute supports a :default option since ActiveSupport 5.2,
17
- # but we want to support previous versions too so we set the default
18
- # manually instead
19
- self.portal_options = {}.freeze
20
-
21
- class << self; private :portal_options=; end
22
- end
23
-
24
- class_methods do
25
- # Based on +has_many+, but creates a special Portal association
26
- # instead.
27
- #
28
- # Custom options:
29
- #
30
- # * <tt>:portal_key</tt> - The key used for the portal in the FM Data JSON portalData.
31
- # * <tt>:attribute_prefix</tt> - The prefix used for portal attributes in the FM Data JSON.
32
- #
33
- # Example:
34
- #
35
- # has_portal :jobs, portal_key: "JobsTable", attribute_prefix: "Job"
36
- #
37
- def has_portal(name, options = {})
38
- create_association(name, Portal, options)
39
-
40
- # Store options for SpykeFormatter to use if needed
41
- portal_key = options[:portal_key] || name
42
- self.portal_options = portal_options.merge(portal_key.to_s => options.dup.merge(name: name.to_s)).freeze
43
-
44
- define_method "#{name.to_s.singularize}_ids" do
45
- association(name).map(&:id)
46
- end
47
- end
48
- end
49
-
50
- # Override Spyke's association reader to keep a cache of loaded
51
- # portals. Spyke's default behavior is to reload the association
52
- # each time.
53
- #
54
- def association(name)
55
- @loaded_portals ||= {}
56
-
57
- if @loaded_portals.has_key?(name.to_sym)
58
- return @loaded_portals[name.to_sym]
59
- end
60
-
61
- super.tap do |assoc|
62
- next unless assoc.kind_of?(FmRest::Spyke::Portal)
63
- @loaded_portals[name.to_sym] = assoc
64
- end
65
- end
66
-
67
- def reload(*_)
68
- super.tap { @loaded_portals = nil }
69
- end
70
-
71
- def portals
72
- self.class.associations.each_with_object([]) do |(key, _), portals|
73
- candidate = association(key)
74
- next unless candidate.kind_of?(FmRest::Spyke::Portal)
75
- portals << candidate
76
- end
77
- end
78
- end
79
- end
80
- end
81
- end
82
-
@@ -1,171 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/model/orm"
4
-
5
- module FmRest
6
- module Spyke
7
- module Model
8
- module Attributes
9
- extend ::ActiveSupport::Concern
10
-
11
- include Orm # Needed to extend custom save and reload
12
-
13
- include ::ActiveModel::Dirty
14
- include ::ActiveModel::ForbiddenAttributesProtection
15
-
16
- included do
17
- # Prevent the creation of plain (no prefix/suffix) attribute methods
18
- # when calling ActiveModels' define_attribute_method, otherwise it
19
- # will define an `attribute` method which overrides the one provided
20
- # by Spyke
21
- self.attribute_method_matchers.shift
22
-
23
- # ActiveModel::Dirty methods for id
24
- define_attribute_method(:id)
25
-
26
- # Keep track of attribute mappings so we can get the FM field names
27
- # for changed attributes
28
- class_attribute :mapped_attributes, instance_writer: false, instance_predicate: false
29
-
30
- # class_attribute supports a :default option since ActiveSupport 5.2,
31
- # but we want to support previous versions too so we set the default
32
- # manually instead
33
- self.mapped_attributes = ::ActiveSupport::HashWithIndifferentAccess.new.freeze
34
-
35
- class << self; private :mapped_attributes=; end
36
- end
37
-
38
- class_methods do
39
- # Similar to Spyke::Base.attributes, but allows defining attribute
40
- # methods that map to FM attributes with different names.
41
- #
42
- # Example:
43
- #
44
- # class Person < Spyke::Base
45
- # include FmRest::Spyke::Model
46
- #
47
- # attributes first_name: "FstName", last_name: "LstName"
48
- # end
49
- #
50
- # p = Person.new
51
- # p.first_name = "Jojo"
52
- # p.attributes # => { "FstName" => "Jojo" }
53
- #
54
- def attributes(*attrs)
55
- if attrs.length == 1 && attrs.first.kind_of?(Hash)
56
- attrs.first.each { |from, to| _fmrest_define_attribute(from, to) }
57
- else
58
- attrs.each { |attr| _fmrest_define_attribute(attr, attr) }
59
- end
60
- end
61
-
62
- private
63
-
64
- # Override Spyke::Base.new_or_return (private), called whenever
65
- # loading records from the HTTP API, so we can reset dirty info on
66
- # freshly loaded records
67
- #
68
- # See: https://github.com/balvig/spyke/blob/master/lib/spyke/http.rb
69
- #
70
- def new_or_return(attributes_or_object, *_)
71
- # In case of an existing Spyke object return it as is so that we
72
- # don't accidentally remove dirty data from associations
73
- return super if attributes_or_object.is_a?(::Spyke::Base)
74
- super.tap do |record|
75
- # In ActiveModel 4.x #clear_changes_information is a private
76
- # method, so we need to call it with send() in that case, but
77
- # keep calling it normally for AM5+
78
- if record.respond_to?(:clear_changes_information)
79
- record.clear_changes_information
80
- else
81
- record.send(:clear_changes_information)
82
- end
83
- end
84
- end
85
-
86
- def _fmrest_attribute_methods_container
87
- @fmrest_attribute_methods_container ||= Module.new.tap { |mod| include mod }
88
- end
89
-
90
- def _fmrest_define_attribute(from, to)
91
- raise ArgumentError, "attribute name `id' is reserved for the recordId" if from.to_s == "id"
92
-
93
- # We use a setter here instead of injecting the hash key/value pair
94
- # directly with #[]= so that we don't change the mapped_attributes
95
- # hash on the parent class. The resulting hash is frozen for the
96
- # same reason.
97
- self.mapped_attributes = mapped_attributes.merge(from => to).freeze
98
-
99
- _fmrest_attribute_methods_container.module_eval do
100
- define_method(from) do
101
- attribute(to)
102
- end
103
-
104
- define_method(:"#{from}=") do |value|
105
- send("#{from}_will_change!") unless value == send(from)
106
- set_attribute(to, value)
107
- end
108
- end
109
-
110
- # Define ActiveModel::Dirty's methods
111
- define_attribute_method(from)
112
- end
113
- end
114
-
115
- def id=(value)
116
- id_will_change! unless value == id
117
- super
118
- end
119
-
120
- def reload(*args)
121
- super.tap { |r| clear_changes_information }
122
- end
123
-
124
- def save(*args)
125
- super.tap { |r| changes_applied_after_save if r }
126
- end
127
-
128
- # ActiveModel::Dirty since version 5.2 assumes that if there's an
129
- # @attributes instance variable set we must be using ActiveRecord, so
130
- # we override the instance variable name used by Spyke to avoid issues.
131
- #
132
- # TODO: Submit a pull request to Spyke so this isn't needed
133
- #
134
- def attributes
135
- @_spyke_attributes
136
- end
137
-
138
- # In addition to the comments above on `attributes`, this also adds
139
- # support for forbidden attributes
140
- #
141
- def attributes=(new_attributes)
142
- @_spyke_attributes ||= ::Spyke::Attributes.new(scope.params)
143
- use_setters(sanitize_for_mass_assignment(new_attributes)) if new_attributes && !new_attributes.empty?
144
- end
145
-
146
- private
147
-
148
- def changed_params
149
- attributes.to_params.slice(*mapped_changed)
150
- end
151
-
152
- def mapped_changed
153
- mapped_attributes.values_at(*changed)
154
- end
155
-
156
- # Use known mapped_attributes for inspect
157
- #
158
- def inspect_attributes
159
- mapped_attributes.except(primary_key).map do |k, v|
160
- "#{k}: #{attribute(v).inspect}"
161
- end.join(', ')
162
- end
163
-
164
- def changes_applied_after_save
165
- changes_applied
166
- portals.each(&:parent_changes_applied)
167
- end
168
- end
169
- end
170
- end
171
- end