fmrest 0.10.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +38 -0
  4. data/README.md +194 -763
  5. metadata +70 -97
  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/fmrest.gemspec +0 -38
  12. data/lib/fmrest.rb +0 -29
  13. data/lib/fmrest/errors.rb +0 -28
  14. data/lib/fmrest/spyke.rb +0 -21
  15. data/lib/fmrest/spyke/base.rb +0 -23
  16. data/lib/fmrest/spyke/container_field.rb +0 -59
  17. data/lib/fmrest/spyke/model.rb +0 -36
  18. data/lib/fmrest/spyke/model/associations.rb +0 -82
  19. data/lib/fmrest/spyke/model/attributes.rb +0 -171
  20. data/lib/fmrest/spyke/model/auth.rb +0 -35
  21. data/lib/fmrest/spyke/model/connection.rb +0 -74
  22. data/lib/fmrest/spyke/model/container_fields.rb +0 -25
  23. data/lib/fmrest/spyke/model/global_fields.rb +0 -40
  24. data/lib/fmrest/spyke/model/http.rb +0 -37
  25. data/lib/fmrest/spyke/model/orm.rb +0 -212
  26. data/lib/fmrest/spyke/model/serialization.rb +0 -91
  27. data/lib/fmrest/spyke/model/uri.rb +0 -30
  28. data/lib/fmrest/spyke/portal.rb +0 -55
  29. data/lib/fmrest/spyke/relation.rb +0 -359
  30. data/lib/fmrest/spyke/spyke_formatter.rb +0 -273
  31. data/lib/fmrest/spyke/validation_error.rb +0 -25
  32. data/lib/fmrest/string_date.rb +0 -220
  33. data/lib/fmrest/token_store.rb +0 -6
  34. data/lib/fmrest/token_store/active_record.rb +0 -74
  35. data/lib/fmrest/token_store/base.rb +0 -25
  36. data/lib/fmrest/token_store/memory.rb +0 -26
  37. data/lib/fmrest/token_store/moneta.rb +0 -41
  38. data/lib/fmrest/token_store/redis.rb +0 -45
  39. data/lib/fmrest/v1.rb +0 -21
  40. data/lib/fmrest/v1/connection.rb +0 -89
  41. data/lib/fmrest/v1/container_fields.rb +0 -114
  42. data/lib/fmrest/v1/dates.rb +0 -81
  43. data/lib/fmrest/v1/paths.rb +0 -47
  44. data/lib/fmrest/v1/raise_errors.rb +0 -57
  45. data/lib/fmrest/v1/token_session.rb +0 -142
  46. data/lib/fmrest/v1/token_store/active_record.rb +0 -13
  47. data/lib/fmrest/v1/token_store/memory.rb +0 -13
  48. data/lib/fmrest/v1/type_coercer.rb +0 -192
  49. data/lib/fmrest/v1/utils.rb +0 -95
  50. data/lib/fmrest/version.rb +0 -5
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/container_field"
4
-
5
- module FmRest
6
- module Spyke
7
- module Model
8
- module ContainerFields
9
- extend ::ActiveSupport::Concern
10
-
11
- class_methods do
12
- def container(name, options = {})
13
- field_name = options[:field_name] || name
14
-
15
- define_method(name) do
16
- @container_fields ||= {}
17
- @container_fields[name.to_sym] ||= ContainerField.new(self, field_name)
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- module Model
6
- module GlobalFields
7
- extend ::ActiveSupport::Concern
8
-
9
- FULLY_QUALIFIED_FIELD_NAME_MATCHER = /\A[^:]+::[^:]+\Z/.freeze
10
-
11
- class_methods do
12
- def set_globals(values_hash)
13
- connection.patch(FmRest::V1.globals_path, {
14
- globalFields: normalize_globals_hash(values_hash)
15
- })
16
- end
17
-
18
- private
19
-
20
- def normalize_globals_hash(hash)
21
- hash.each_with_object({}) do |(k, v), normalized|
22
- if v.kind_of?(Hash)
23
- v.each do |k2, v2|
24
- normalized["#{k}::#{k2}"] = v2
25
- end
26
- next
27
- end
28
-
29
- unless FULLY_QUALIFIED_FIELD_NAME_MATCHER === k.to_s
30
- raise ArgumentError, "global fields must be given in fully qualified format (table name::field name)"
31
- end
32
-
33
- normalized[k] = v
34
- end
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/relation"
4
-
5
- module FmRest
6
- module Spyke
7
- module Model
8
- module Http
9
- extend ::ActiveSupport::Concern
10
-
11
- class_methods do
12
-
13
- # Override Spyke's request method to keep a thread-local copy of the
14
- # last request's metadata, so that we can access things like script
15
- # execution results after a save, etc.
16
-
17
-
18
- def request(*args)
19
- super.tap do |r|
20
- Thread.current[last_request_metadata_key] = r.metadata
21
- end
22
- end
23
-
24
- def last_request_metadata(key: last_request_metadata_key)
25
- Thread.current[key]
26
- end
27
-
28
- private
29
-
30
- def last_request_metadata_key
31
- "#{to_s}.last_request_metadata"
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,212 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fmrest/spyke/relation"
4
- require "fmrest/spyke/validation_error"
5
-
6
- module FmRest
7
- module Spyke
8
- module Model
9
- module Orm
10
- extend ::ActiveSupport::Concern
11
-
12
- included do
13
- # Allow overriding FM's default limit (by default it's 100)
14
- class_attribute :default_limit, instance_accessor: false, instance_predicate: false
15
-
16
- class_attribute :default_sort, instance_accessor: false, instance_predicate: false
17
-
18
- # Whether to raise an FmRest::APIError::NoMatchingRecordsError when a
19
- # _find request has no results
20
- class_attribute :raise_on_no_matching_records, instance_accessor: false, instance_predicate: false
21
- end
22
-
23
- class_methods do
24
- # Methods delegated to FmRest::Spyke::Relation
25
- delegate :limit, :offset, :sort, :order, :query, :omit, :portal,
26
- :portals, :includes, :with_all_portals, :without_portals,
27
- :script, :find_one, :first, :any, :find_some,
28
- :find_in_batches, :find_each, to: :all
29
-
30
- def all
31
- # Use FmRest's Relation instead of Spyke's vanilla one
32
- current_scope || Relation.new(self, uri: uri)
33
- end
34
-
35
- # Extended fetch to allow properly setting limit, offset and other
36
- # options, as well as using the appropriate HTTP method/URL depending
37
- # on whether there's a query present in the current scope, e.g.:
38
- #
39
- # Person.query(first_name: "Stefan").fetch # POST .../_find
40
- #
41
- def fetch
42
- if current_scope.has_query?
43
- scope = extend_scope_with_fm_params(current_scope, prefixed: false)
44
- scope = scope.where(query: scope.query_params)
45
- scope = scope.with(FmRest::V1::find_path(layout))
46
- else
47
- scope = extend_scope_with_fm_params(current_scope, prefixed: true)
48
- end
49
-
50
- previous, self.current_scope = current_scope, scope
51
-
52
- # The DAPI returns a 401 "No records match the request" error when
53
- # nothing matches a _find request, so we need to catch it in order
54
- # to provide sane behavior (i.e. return an empty resultset)
55
- begin
56
- current_scope.has_query? ? scoped_request(:post) : super
57
- rescue FmRest::APIError::NoMatchingRecordsError => e
58
- raise e if raise_on_no_matching_records
59
- ::Spyke::Result.new({})
60
- end
61
- ensure
62
- self.current_scope = previous
63
- end
64
-
65
- # API-error-raising version of #create
66
- #
67
- def create!(attributes = {})
68
- new(attributes).tap(&:save!)
69
- end
70
-
71
- def execute_script(script_name, param: nil)
72
- params = {}
73
- params = {"script.param" => param} unless param.nil?
74
- request(:get, FmRest::V1::script_path(layout, script_name), params)
75
- end
76
-
77
- private
78
-
79
- def extend_scope_with_fm_params(scope, prefixed: false)
80
- prefix = prefixed ? "_" : nil
81
-
82
- where_options = {}
83
-
84
- where_options["#{prefix}limit"] = scope.limit_value if scope.limit_value
85
- where_options["#{prefix}offset"] = scope.offset_value if scope.offset_value
86
-
87
- if scope.sort_params.present?
88
- where_options["#{prefix}sort"] =
89
- prefixed ? scope.sort_params.to_json : scope.sort_params
90
- end
91
-
92
- unless scope.included_portals.nil?
93
- where_options["portal"] =
94
- prefixed ? scope.included_portals.to_json : scope.included_portals
95
- end
96
-
97
- if scope.portal_params.present?
98
- scope.portal_params.each do |portal_param, value|
99
- where_options["#{prefix}#{portal_param}"] = value
100
- end
101
- end
102
-
103
- if scope.script_params.present?
104
- where_options.merge!(scope.script_params)
105
- end
106
-
107
- scope.where(where_options)
108
- end
109
- end
110
-
111
- # Overwrite Spyke's save to provide a number of features:
112
- #
113
- # * Validations
114
- # * Data API scripts execution
115
- # * Refresh of dirty attributes
116
- #
117
- def save(options = {})
118
- callback = persisted? ? :update : :create
119
-
120
- return false unless perform_save_validations(callback, options)
121
- return false unless perform_save_persistence(callback, options)
122
-
123
- true
124
- end
125
-
126
- def save!(options = {})
127
- save(options.merge(raise_validation_errors: true))
128
- end
129
-
130
- # Overwrite Spyke's destroy to provide Data API script execution
131
- #
132
- def destroy(options = {})
133
- # For whatever reason the Data API wants the script params as query
134
- # string params for DELETE requests, making this more complicated
135
- # than it should be
136
- script_query_string = if options.has_key?(:script)
137
- "?" + Faraday::Utils.build_query(FmRest::V1.convert_script_params(options[:script]))
138
- else
139
- ""
140
- end
141
-
142
- self.attributes = delete(uri.to_s + script_query_string)
143
- end
144
-
145
- # API-error-raising version of #update
146
- #
147
- def update!(new_attributes, options = {})
148
- self.attributes = new_attributes
149
- save!(options)
150
- end
151
-
152
- def reload(options = {})
153
- scope = self.class
154
- scope = scope.script(options[:script]) if options.has_key?(:script)
155
- reloaded = scope.find(id)
156
- self.attributes = reloaded.attributes
157
- self.mod_id = reloaded.mod_id
158
- end
159
-
160
- # ActiveModel 5+ implements this method, so we only needed if we're in
161
- # the older AM4
162
- if ActiveModel::VERSION::MAJOR == 4
163
- def validate!(context = nil)
164
- valid?(context) || raise_validation_error
165
- end
166
- end
167
-
168
- private
169
-
170
- def perform_save_validations(context, options)
171
- return true if options[:validate] == false
172
- options[:raise_validation_errors] ? validate!(context) : validate(context)
173
- end
174
-
175
- def perform_save_persistence(callback, options)
176
- run_callbacks :save do
177
- run_callbacks(callback) do
178
-
179
- begin
180
- send self.class.method_for(callback), build_params_for_save(options)
181
-
182
- rescue APIError::ValidationError => e
183
- if options[:raise_validation_errors]
184
- raise e
185
- else
186
- return false
187
- end
188
- end
189
-
190
- end
191
- end
192
-
193
- true
194
- end
195
-
196
- def build_params_for_save(options)
197
- to_params.tap do |params|
198
- if options.has_key?(:script)
199
- params.merge!(FmRest::V1.convert_script_params(options[:script]))
200
- end
201
- end
202
- end
203
-
204
- # Overwrite ActiveModel's raise_validation_error to use our own class
205
- #
206
- def raise_validation_error # :doc:
207
- raise(ValidationError.new(self))
208
- end
209
- end
210
- end
211
- end
212
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- module Model
6
- module Serialization
7
- FM_DATE_FORMAT = "%m/%d/%Y"
8
- FM_DATETIME_FORMAT = "#{FM_DATE_FORMAT} %H:%M:%S"
9
-
10
- # Override Spyke's to_params to return FM Data API's expected JSON
11
- # format, and including only modified fields
12
- #
13
- def to_params
14
- params = {
15
- fieldData: serialize_values!(changed_params_not_embedded_in_url)
16
- }
17
-
18
- params[:modId] = mod_id if mod_id
19
-
20
- portal_data = serialize_portals
21
- params[:portalData] = portal_data unless portal_data.empty?
22
-
23
- params
24
- end
25
-
26
- protected
27
-
28
- def serialize_for_portal(portal)
29
- params =
30
- changed_params.except(:id).transform_keys do |key|
31
- "#{portal.attribute_prefix}::#{key}"
32
- end
33
-
34
- params[:recordId] = id if id
35
- params[:modId] = mod_id if mod_id
36
-
37
- serialize_values!(params)
38
- end
39
-
40
- private
41
-
42
- def serialize_portals
43
- portal_data = {}
44
-
45
- portals.each do |portal|
46
- portal.each do |portal_record|
47
- next unless portal_record.changed?
48
- portal_params = portal_data[portal.portal_key] ||= []
49
- portal_params << portal_record.serialize_for_portal(portal)
50
- end
51
- end
52
-
53
- portal_data
54
- end
55
-
56
- def changed_params_not_embedded_in_url
57
- params_not_embedded_in_url.slice(*mapped_changed)
58
- end
59
-
60
- # Modifies the given hash in-place encoding non-string values (e.g.
61
- # dates) to their string representation when appropriate.
62
- #
63
- def serialize_values!(params)
64
- params.transform_values! do |value|
65
- case value
66
- when DateTime, Time, FmRest::StringDateTime
67
- convert_datetime_timezone(value.to_datetime).strftime(FM_DATETIME_FORMAT)
68
- when Date, FmRest::StringDate
69
- value.strftime(FM_DATE_FORMAT)
70
- else
71
- value
72
- end
73
- end
74
-
75
- params
76
- end
77
-
78
- def convert_datetime_timezone(dt)
79
- case fmrest_config.fetch(:timezone, nil)
80
- when :utc, "utc"
81
- dt.new_offset(0)
82
- when :local, "local"
83
- dt.new_offset(FmRest::V1.local_offset_for_datetime(dt))
84
- when nil
85
- dt
86
- end
87
- end
88
- end
89
- end
90
- end
91
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- module Model
6
- module Uri
7
- extend ::ActiveSupport::Concern
8
-
9
- class_methods do
10
- # Accessor for FM layout (helps with building the URI)
11
- #
12
- def layout(layout = nil)
13
- @layout = layout if layout
14
- @layout ||= model_name.name
15
- end
16
-
17
- # Extend uri acccessor to default to FM Data schema
18
- #
19
- def uri(uri_template = nil)
20
- if @uri.nil? && uri_template.nil?
21
- return FmRest::V1.record_path(layout) + "(/:id)"
22
- end
23
-
24
- super
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FmRest
4
- module Spyke
5
- # Extend Spyke's HasMany association with custom options
6
- #
7
- class Portal < ::Spyke::Associations::HasMany
8
- def initialize(*args)
9
- super
10
-
11
- # Portals are always embedded, so no special URI
12
- @options[:uri] = ""
13
- end
14
-
15
- def portal_key
16
- return @options[:portal_key] if @options[:portal_key]
17
- name
18
- end
19
-
20
- def attribute_prefix
21
- @options[:attribute_prefix] || portal_key
22
- end
23
-
24
- def parent_changes_applied
25
- each do |record|
26
- record.changes_applied
27
- # Saving portal data doesn't provide new modIds for the
28
- # portal records, so we clear them instead. We can still save
29
- # portal data without a mod_id (it's optional in FM Data API)
30
- record.mod_id = nil
31
- end
32
- end
33
-
34
- private
35
-
36
- # Spyke::Associations::HasMany#initialize calls primary_key to build the
37
- # default URI, which causes a NameError, so this is here just to prevent
38
- # that. We don't care what it returns as we override the URI with nil
39
- # anyway
40
- def primary_key; end
41
-
42
- # Make sure the association doesn't try to fetch records through URI
43
- def uri; nil; end
44
-
45
- def embedded_data
46
- parent.attributes[portal_key]
47
- end
48
-
49
- def add_to_parent(record)
50
- find_some << record
51
- record
52
- end
53
- end
54
- end
55
- end