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.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/CHANGELOG.md +32 -0
- data/README.md +228 -844
- metadata +71 -101
- data/.github/workflows/ci.yml +0 -33
- data/.gitignore +0 -26
- data/.rspec +0 -3
- data/.travis.yml +0 -5
- data/Gemfile +0 -3
- data/Rakefile +0 -6
- data/fmrest.gemspec +0 -38
- data/lib/fmrest.rb +0 -34
- data/lib/fmrest/connection_settings.rb +0 -124
- data/lib/fmrest/errors.rb +0 -30
- data/lib/fmrest/spyke.rb +0 -21
- data/lib/fmrest/spyke/base.rb +0 -23
- data/lib/fmrest/spyke/container_field.rb +0 -59
- data/lib/fmrest/spyke/model.rb +0 -36
- data/lib/fmrest/spyke/model/associations.rb +0 -82
- data/lib/fmrest/spyke/model/attributes.rb +0 -171
- data/lib/fmrest/spyke/model/auth.rb +0 -43
- data/lib/fmrest/spyke/model/connection.rb +0 -135
- data/lib/fmrest/spyke/model/container_fields.rb +0 -25
- data/lib/fmrest/spyke/model/global_fields.rb +0 -40
- data/lib/fmrest/spyke/model/http.rb +0 -37
- data/lib/fmrest/spyke/model/orm.rb +0 -212
- data/lib/fmrest/spyke/model/serialization.rb +0 -91
- data/lib/fmrest/spyke/model/uri.rb +0 -30
- data/lib/fmrest/spyke/portal.rb +0 -55
- data/lib/fmrest/spyke/relation.rb +0 -359
- data/lib/fmrest/spyke/spyke_formatter.rb +0 -273
- data/lib/fmrest/spyke/validation_error.rb +0 -25
- data/lib/fmrest/string_date.rb +0 -220
- data/lib/fmrest/token_store.rb +0 -12
- data/lib/fmrest/token_store/active_record.rb +0 -74
- data/lib/fmrest/token_store/base.rb +0 -25
- data/lib/fmrest/token_store/memory.rb +0 -26
- data/lib/fmrest/token_store/moneta.rb +0 -41
- data/lib/fmrest/token_store/redis.rb +0 -45
- data/lib/fmrest/v1.rb +0 -23
- data/lib/fmrest/v1/auth.rb +0 -30
- data/lib/fmrest/v1/connection.rb +0 -115
- data/lib/fmrest/v1/container_fields.rb +0 -114
- data/lib/fmrest/v1/dates.rb +0 -81
- data/lib/fmrest/v1/paths.rb +0 -47
- data/lib/fmrest/v1/raise_errors.rb +0 -59
- data/lib/fmrest/v1/token_session.rb +0 -134
- data/lib/fmrest/v1/token_store/active_record.rb +0 -13
- data/lib/fmrest/v1/token_store/memory.rb +0 -13
- data/lib/fmrest/v1/type_coercer.rb +0 -192
- data/lib/fmrest/v1/utils.rb +0 -94
- 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
|
data/lib/fmrest/spyke/base.rb
DELETED
@@ -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
|
data/lib/fmrest/spyke/model.rb
DELETED
@@ -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
|