fmrest 0.12.0 → 0.13.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +11 -0
  4. data/README.md +175 -809
  5. metadata +61 -109
  6. data/.gitignore +0 -26
  7. data/.rspec +0 -3
  8. data/.travis.yml +0 -5
  9. data/Gemfile +0 -3
  10. data/Rakefile +0 -6
  11. data/UPGRADING +0 -15
  12. data/fmrest.gemspec +0 -49
  13. data/lib/fmrest.rb +0 -36
  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 -9
  18. data/lib/fmrest/spyke/container_field.rb +0 -59
  19. data/lib/fmrest/spyke/model.rb +0 -33
  20. data/lib/fmrest/spyke/model/associations.rb +0 -86
  21. data/lib/fmrest/spyke/model/attributes.rb +0 -163
  22. data/lib/fmrest/spyke/model/auth.rb +0 -43
  23. data/lib/fmrest/spyke/model/connection.rb +0 -163
  24. data/lib/fmrest/spyke/model/container_fields.rb +0 -40
  25. data/lib/fmrest/spyke/model/global_fields.rb +0 -40
  26. data/lib/fmrest/spyke/model/http.rb +0 -77
  27. data/lib/fmrest/spyke/model/orm.rb +0 -256
  28. data/lib/fmrest/spyke/model/record_id.rb +0 -78
  29. data/lib/fmrest/spyke/model/serialization.rb +0 -99
  30. data/lib/fmrest/spyke/model/uri.rb +0 -29
  31. data/lib/fmrest/spyke/portal.rb +0 -55
  32. data/lib/fmrest/spyke/relation.rb +0 -359
  33. data/lib/fmrest/spyke/spyke_formatter.rb +0 -273
  34. data/lib/fmrest/spyke/validation_error.rb +0 -25
  35. data/lib/fmrest/string_date.rb +0 -220
  36. data/lib/fmrest/token_store.rb +0 -12
  37. data/lib/fmrest/token_store/active_record.rb +0 -74
  38. data/lib/fmrest/token_store/base.rb +0 -25
  39. data/lib/fmrest/token_store/memory.rb +0 -26
  40. data/lib/fmrest/token_store/moneta.rb +0 -41
  41. data/lib/fmrest/token_store/null.rb +0 -20
  42. data/lib/fmrest/token_store/redis.rb +0 -45
  43. data/lib/fmrest/v1.rb +0 -23
  44. data/lib/fmrest/v1/auth.rb +0 -30
  45. data/lib/fmrest/v1/connection.rb +0 -115
  46. data/lib/fmrest/v1/container_fields.rb +0 -114
  47. data/lib/fmrest/v1/dates.rb +0 -81
  48. data/lib/fmrest/v1/paths.rb +0 -47
  49. data/lib/fmrest/v1/raise_errors.rb +0 -57
  50. data/lib/fmrest/v1/token_session.rb +0 -133
  51. data/lib/fmrest/v1/token_store/active_record.rb +0 -13
  52. data/lib/fmrest/v1/token_store/memory.rb +0 -13
  53. data/lib/fmrest/v1/type_coercer.rb +0 -192
  54. data/lib/fmrest/v1/utils.rb +0 -94
  55. 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,9 +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
- end
9
- 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.__record_id, name, repetition)
56
- end
57
- end
58
- end
59
- end
@@ -1,33 +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/record_id"
6
- require "fmrest/spyke/model/attributes"
7
- require "fmrest/spyke/model/serialization"
8
- require "fmrest/spyke/model/associations"
9
- require "fmrest/spyke/model/orm"
10
- require "fmrest/spyke/model/container_fields"
11
- require "fmrest/spyke/model/global_fields"
12
- require "fmrest/spyke/model/http"
13
- require "fmrest/spyke/model/auth"
14
-
15
- module FmRest
16
- module Spyke
17
- module Model
18
- extend ::ActiveSupport::Concern
19
-
20
- include Connection
21
- include URI
22
- include RecordID
23
- include Attributes
24
- include Serialization
25
- include Associations
26
- include Orm
27
- include ContainerFields
28
- include GlobalFields
29
- include Http
30
- include Auth
31
- end
32
- end
33
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/portal"
4
-
5
- module FmRest
6
- module Spyke
7
- module Model
8
- # This module adds portal support to Spyke models.
9
- #
10
- module Associations
11
- extend ::ActiveSupport::Concern
12
-
13
- included do
14
- # Keep track of portal options by their FM keys as we could need it
15
- # to parse the portalData JSON in SpykeFormatter
16
- class_attribute :portal_options, instance_accessor: false, instance_predicate: false
17
-
18
- # class_attribute supports a :default option since ActiveSupport 5.2,
19
- # but we want to support previous versions too so we set the default
20
- # manually instead
21
- self.portal_options = {}.freeze
22
-
23
- class << self; private :portal_options=; end
24
- end
25
-
26
- class_methods do
27
- # Based on `has_many`, but creates a special Portal association
28
- # instead.
29
- #
30
- # @option :portal_key [String] The key used for the portal in the FM
31
- # Data JSON portalData
32
- # @option :attribute_prefix [String] The prefix used for portal
33
- # attributes in the FM Data JSON
34
- #
35
- # @example
36
- # class Person < FmRest::Spyke::Base
37
- # has_portal :jobs, portal_key: "JobsTable", attribute_prefix: "Job"
38
- # end
39
- #
40
- def has_portal(name, options = {})
41
- create_association(name, Portal, options)
42
-
43
- # Store options for SpykeFormatter to use if needed
44
- portal_key = options[:portal_key] || name
45
- self.portal_options = portal_options.merge(portal_key.to_s => options.dup.merge(name: name.to_s)).freeze
46
-
47
- define_method "#{name.to_s.singularize}_ids" do
48
- association(name).map(&:id)
49
- end
50
- end
51
- end
52
-
53
- # Spyke override -- Keep a cache of loaded portals. Spyke's default
54
- # behavior is to reload the association each time.
55
- #
56
- def association(name)
57
- @loaded_portals ||= {}
58
-
59
- if @loaded_portals.has_key?(name.to_sym)
60
- return @loaded_portals[name.to_sym]
61
- end
62
-
63
- super.tap do |assoc|
64
- next unless assoc.kind_of?(FmRest::Spyke::Portal)
65
- @loaded_portals[name.to_sym] = assoc
66
- end
67
- end
68
-
69
- # Spyke override -- Add portals awareness
70
- #
71
- def reload(*_)
72
- super.tap { @loaded_portals = nil }
73
- end
74
-
75
- def portals
76
- self.class.associations.each_with_object([]) do |(key, _), portals|
77
- candidate = association(key)
78
- next unless candidate.kind_of?(FmRest::Spyke::Portal)
79
- portals << candidate
80
- end
81
- end
82
- end
83
- end
84
- end
85
- end
86
-
@@ -1,163 +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
- # Extends Spyke models with support for mapped attributes,
9
- # `ActiveModel::Dirty` and forbidden attributes (e.g. Rails'
10
- # `params.permit`).
11
- #
12
- module Attributes
13
- extend ::ActiveSupport::Concern
14
-
15
- include Orm # Needed to extend custom save and reload
16
-
17
- include ::ActiveModel::Dirty
18
- include ::ActiveModel::ForbiddenAttributesProtection
19
-
20
- included do
21
- # Prevent the creation of plain (no prefix/suffix) attribute methods
22
- # when calling ActiveModels' define_attribute_method, otherwise it
23
- # will define an `attribute` method which overrides the one provided
24
- # by Spyke
25
- self.attribute_method_matchers.shift
26
-
27
- # Keep track of attribute mappings so we can get the FM field names
28
- # for changed attributes
29
- class_attribute :mapped_attributes, instance_writer: false, instance_predicate: false
30
-
31
- # class_attribute supports a :default option since ActiveSupport 5.2,
32
- # but we want to support previous versions too so we set the default
33
- # manually instead
34
- self.mapped_attributes = ::ActiveSupport::HashWithIndifferentAccess.new.freeze
35
-
36
- class << self; private :mapped_attributes=; end
37
- end
38
-
39
- class_methods do
40
- # Spyke override
41
- #
42
- # Similar to Spyke::Base.attributes, but allows defining attribute
43
- # methods that map to FM attributes with different names.
44
- #
45
- # @example
46
- #
47
- # class Person < Spyke::Base
48
- # include FmRest::Spyke::Model
49
- #
50
- # attributes first_name: "FstName", last_name: "LstName"
51
- # end
52
- #
53
- # p = Person.new
54
- # p.first_name = "Jojo"
55
- # p.attributes # => { "FstName" => "Jojo" }
56
- #
57
- def attributes(*attrs)
58
- if attrs.length == 1 && attrs.first.kind_of?(Hash)
59
- attrs.first.each { |from, to| _fmrest_define_attribute(from, to) }
60
- else
61
- attrs.each { |attr| _fmrest_define_attribute(attr, attr) }
62
- end
63
- end
64
-
65
- private
66
-
67
- # Spyke override (private)
68
- #
69
- # Called whenever loading records from the HTTP API, so we can reset
70
- # dirty info on freshly loaded records
71
- #
72
- # See: https://github.com/balvig/spyke/blob/master/lib/spyke/http.rb
73
- #
74
- def new_or_return(attributes_or_object, *_)
75
- # In case of an existing Spyke object return it as is so that we
76
- # don't accidentally remove dirty data from associations
77
- return super if attributes_or_object.is_a?(::Spyke::Base)
78
- super.tap do |record|
79
- # In ActiveModel 4.x #clear_changes_information is a private
80
- # method, so we need to call it with send() in that case, but
81
- # keep calling it normally for AM5+
82
- if record.respond_to?(:clear_changes_information)
83
- record.clear_changes_information
84
- else
85
- record.send(:clear_changes_information)
86
- end
87
- end
88
- end
89
-
90
- def _fmrest_attribute_methods_container
91
- @fmrest_attribute_methods_container ||= Module.new.tap { |mod| include mod }
92
- end
93
-
94
- def _fmrest_define_attribute(from, to)
95
- # We use a setter here instead of injecting the hash key/value pair
96
- # directly with #[]= so that we don't change the mapped_attributes
97
- # hash on the parent class. The resulting hash is frozen for the
98
- # same reason.
99
- self.mapped_attributes = mapped_attributes.merge(from => to).freeze
100
-
101
- _fmrest_attribute_methods_container.module_eval do
102
- define_method(from) do
103
- attribute(to)
104
- end
105
-
106
- define_method(:"#{from}=") do |value|
107
- send("#{from}_will_change!") unless value == send(from)
108
- set_attribute(to, value)
109
- end
110
- end
111
-
112
- # Define ActiveModel::Dirty's methods
113
- define_attribute_method(from)
114
- end
115
- end
116
-
117
- # Spyke override -- Adds AM::Dirty support
118
- #
119
- def reload(*args)
120
- super.tap { |r| clear_changes_information }
121
- end
122
-
123
- # Spyke override -- Adds AM::Dirty support
124
- #
125
- def save(*args)
126
- super.tap { |r| changes_applied_after_save if r }
127
- end
128
-
129
- # Spyke override -- Adds support for forbidden attributes (i.e. Rails'
130
- # `params.permit`, etc.)
131
- #
132
- def attributes=(new_attributes)
133
- @spyke_attributes ||= ::Spyke::Attributes.new(scope.params)
134
- return unless new_attributes && !new_attributes.empty?
135
- use_setters(sanitize_for_mass_assignment(new_attributes))
136
- end
137
-
138
- private
139
-
140
- def changed_params
141
- attributes.to_params.slice(*mapped_changed)
142
- end
143
-
144
- def mapped_changed
145
- mapped_attributes.values_at(*changed)
146
- end
147
-
148
- # Spyke override (private) -- Use known mapped_attributes for inspect
149
- #
150
- def inspect_attributes
151
- mapped_attributes.except(primary_key).map do |k, v|
152
- "#{k}: #{attribute(v).inspect}"
153
- end.join(', ')
154
- end
155
-
156
- def changes_applied_after_save
157
- changes_applied
158
- portals.each(&:parent_changes_applied)
159
- end
160
- end
161
- end
162
- end
163
- end