fmrest 0.12.0 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -0
  3. data/CHANGELOG.md +31 -0
  4. data/README.md +252 -851
  5. metadata +71 -108
  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