filemaker 0.0.19 → 1.0.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 +5 -5
- data/.editorconfig +14 -0
- data/.travis.yml +1 -2
- data/README.md +24 -10
- data/filemaker.gemspec +1 -4
- data/lib/filemaker.rb +14 -16
- data/lib/filemaker/configuration.rb +6 -1
- data/lib/filemaker/errors.rb +1 -0
- data/lib/filemaker/layout.rb +1 -2
- data/lib/filemaker/metadata/field.rb +6 -4
- data/lib/filemaker/model.rb +28 -17
- data/lib/filemaker/model/batches.rb +12 -1
- data/lib/filemaker/model/builder.rb +9 -4
- data/lib/filemaker/model/components.rb +8 -0
- data/lib/filemaker/model/field.rb +35 -40
- data/lib/filemaker/model/fields.rb +25 -22
- data/lib/filemaker/model/pagination.rb +1 -0
- data/lib/filemaker/model/persistable.rb +8 -8
- data/lib/filemaker/model/selectable.rb +1 -1
- data/lib/filemaker/model/type.rb +31 -0
- data/lib/filemaker/model/types/big_decimal.rb +22 -0
- data/lib/filemaker/model/types/date.rb +24 -0
- data/lib/filemaker/model/types/email.rb +5 -23
- data/lib/filemaker/model/types/integer.rb +22 -0
- data/lib/filemaker/model/types/text.rb +19 -0
- data/lib/filemaker/model/types/time.rb +22 -0
- data/lib/filemaker/record.rb +1 -1
- data/lib/filemaker/server.rb +38 -36
- data/lib/filemaker/store/database_store.rb +1 -1
- data/lib/filemaker/store/layout_store.rb +1 -1
- data/lib/filemaker/store/script_store.rb +1 -1
- data/lib/filemaker/version.rb +1 -1
- data/spec/filemaker/layout_spec.rb +1 -1
- data/spec/filemaker/metadata/field_spec.rb +16 -16
- data/spec/filemaker/model/builder_spec.rb +5 -0
- data/spec/filemaker/model/criteria_spec.rb +8 -7
- data/spec/filemaker/model/types_spec.rb +103 -0
- data/spec/filemaker/model_spec.rb +4 -13
- data/spec/filemaker/record_spec.rb +1 -1
- data/spec/filemaker/server_spec.rb +7 -9
- data/spec/filemaker/store/database_store_spec.rb +1 -1
- data/spec/filemaker/store/layout_store_spec.rb +1 -1
- data/spec/filemaker/store/script_store_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -0
- data/spec/support/models.rb +0 -1
- data/spec/support/xml_loader.rb +8 -20
- metadata +14 -48
- data/lib/filemaker/model/types/attachment.rb +0 -102
@@ -19,18 +19,6 @@ module Filemaker
|
|
19
19
|
module Fields
|
20
20
|
extend ActiveSupport::Concern
|
21
21
|
|
22
|
-
TYPE_MAPPINGS = {
|
23
|
-
string: String,
|
24
|
-
text: String,
|
25
|
-
date: Date,
|
26
|
-
datetime: DateTime,
|
27
|
-
money: BigDecimal,
|
28
|
-
number: BigDecimal,
|
29
|
-
integer: Integer,
|
30
|
-
email: Filemaker::Model::Types::Email,
|
31
|
-
object: Filemaker::Model::Types::Attachment
|
32
|
-
}.freeze
|
33
|
-
|
34
22
|
included do
|
35
23
|
class_attribute :fields, :identity
|
36
24
|
self.fields = {}
|
@@ -41,7 +29,7 @@ module Filemaker
|
|
41
29
|
def apply_defaults
|
42
30
|
attribute_names.each do |name|
|
43
31
|
field = fields[name]
|
44
|
-
|
32
|
+
instance_variable_set("@#{name}", field.default_value)
|
45
33
|
end
|
46
34
|
end
|
47
35
|
|
@@ -53,50 +41,65 @@ module Filemaker
|
|
53
41
|
fields.values.map(&:fm_name)
|
54
42
|
end
|
55
43
|
|
44
|
+
def attributes
|
45
|
+
fields.keys.each_with_object({}) do |field, hash|
|
46
|
+
# Attributes must be strings, not symbols - See
|
47
|
+
# http://api.rubyonrails.org/classes/ActiveModel/Serialization.html
|
48
|
+
hash[field.to_s] = instance_variable_get("@#{field}")
|
49
|
+
|
50
|
+
# If we use public_send(field) will encounter Stack Too Deep
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
56
54
|
module ClassMethods
|
57
55
|
def attribute_names
|
58
56
|
fields.keys
|
59
57
|
end
|
60
58
|
|
61
|
-
|
59
|
+
Filemaker::Model::Type.registry.each_key do |type|
|
62
60
|
define_method(type) do |*args|
|
63
|
-
# TODO: It will be good if we can accept lambda also
|
64
61
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
65
62
|
field_names = args
|
66
63
|
|
67
64
|
field_names.each do |name|
|
68
|
-
add_field(name,
|
65
|
+
add_field(name, Filemaker::Model::Type.registry[type], options)
|
69
66
|
create_accessors(name)
|
70
67
|
end
|
71
68
|
end
|
72
69
|
end
|
73
70
|
|
74
71
|
def add_field(name, type, options)
|
75
|
-
name = name.to_s
|
72
|
+
name = name.to_s.freeze
|
76
73
|
fields[name] = Filemaker::Model::Field.new(name, type, options)
|
77
74
|
self.identity = fields[name] if options[:identity]
|
78
75
|
end
|
79
76
|
|
80
77
|
def create_accessors(name)
|
81
|
-
|
78
|
+
# Normalize it so ActiveModel::Serialization can work
|
79
|
+
name = name.to_s
|
82
80
|
|
83
81
|
define_attribute_methods name
|
84
82
|
|
85
83
|
# Reader
|
86
84
|
define_method(name) do
|
87
|
-
|
85
|
+
instance_variable_get("@#{name}")
|
88
86
|
end
|
89
87
|
|
90
88
|
# Writer - We try to map to the correct type, if not we just return
|
91
89
|
# original.
|
92
90
|
define_method("#{name}=") do |value|
|
93
|
-
|
94
|
-
|
91
|
+
new_value = fields[name].serialize_for_update(value)
|
92
|
+
|
93
|
+
public_send("#{name}_will_change!") \
|
94
|
+
if new_value != public_send(name)
|
95
|
+
|
96
|
+
instance_variable_set("@#{name}", new_value)
|
95
97
|
end
|
96
98
|
|
97
99
|
# Predicate
|
98
100
|
define_method("#{name}?") do
|
99
|
-
|
101
|
+
# See ActiveRecord::AttributeMethods::Query implementation
|
102
|
+
public_send(name) == true || public_send(name).present?
|
100
103
|
end
|
101
104
|
end
|
102
105
|
|
@@ -28,6 +28,7 @@ module Filemaker
|
|
28
28
|
options = {}
|
29
29
|
yield options if block_given?
|
30
30
|
resultset = api.new(fm_attributes, options)
|
31
|
+
changes_applied
|
31
32
|
replace_new_data(resultset)
|
32
33
|
end
|
33
34
|
self
|
@@ -71,18 +72,17 @@ module Filemaker
|
|
71
72
|
|
72
73
|
# If value is nil, we convert to empty string so it will get pick up by
|
73
74
|
# `fm_attributes`
|
74
|
-
def assign_attributes(new_attributes)
|
75
|
-
|
75
|
+
# def assign_attributes(new_attributes)
|
76
|
+
# return if new_attributes.blank?
|
76
77
|
|
77
|
-
|
78
|
-
|
78
|
+
# new_attributes.each_pair do |key, value|
|
79
|
+
# next unless respond_to?("#{key}=")
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
end
|
81
|
+
# public_send("#{key}=", (value || ''))
|
82
|
+
# end
|
83
|
+
# end
|
83
84
|
|
84
85
|
def reload!
|
85
|
-
reset_changes
|
86
86
|
resultset = api.find(record_id)
|
87
87
|
replace_new_data(resultset)
|
88
88
|
self
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'filemaker/model/types/text'
|
2
|
+
require 'filemaker/model/types/date'
|
3
|
+
require 'filemaker/model/types/time'
|
4
|
+
require 'filemaker/model/types/big_decimal'
|
5
|
+
require 'filemaker/model/types/integer'
|
6
|
+
require 'filemaker/model/types/email'
|
7
|
+
|
8
|
+
module Filemaker
|
9
|
+
module Model
|
10
|
+
module Type
|
11
|
+
@registry = {}
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :registry
|
15
|
+
|
16
|
+
def register(type_name, klass)
|
17
|
+
registry[type_name] = klass
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
register(:string, Filemaker::Model::Types::Text)
|
22
|
+
register(:text, Filemaker::Model::Types::Text)
|
23
|
+
register(:date, Filemaker::Model::Types::Date)
|
24
|
+
register(:datetime, Filemaker::Model::Types::Time)
|
25
|
+
register(:money, Filemaker::Model::Types::BigDecimal)
|
26
|
+
register(:number, Filemaker::Model::Types::BigDecimal)
|
27
|
+
register(:integer, Filemaker::Model::Types::Integer)
|
28
|
+
register(:email, Filemaker::Model::Types::Email)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Filemaker
|
2
|
+
module Model
|
3
|
+
module Types
|
4
|
+
class BigDecimal
|
5
|
+
def self.__filemaker_cast_to_ruby_object(value)
|
6
|
+
return value if value.is_a?(::BigDecimal)
|
7
|
+
BigDecimal(value.to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.__filemaker_serialize_for_update(value)
|
11
|
+
return value if value.is_a?(::BigDecimal)
|
12
|
+
BigDecimal(value.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.__filemaker_serialize_for_query(value)
|
16
|
+
return value if value.is_a?(::BigDecimal)
|
17
|
+
BigDecimal(value.to_s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Filemaker
|
2
|
+
module Model
|
3
|
+
module Types
|
4
|
+
class Date
|
5
|
+
def self.__filemaker_cast_to_ruby_object(value)
|
6
|
+
return value if value.is_a?(::Date)
|
7
|
+
::Date.parse(value.to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.__filemaker_serialize_for_update(value)
|
11
|
+
return value if value.is_a?(::Date)
|
12
|
+
::Date.parse(value.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.__filemaker_serialize_for_query(value)
|
16
|
+
# If we are doing date range query like
|
17
|
+
# Model.where(date: '12/2018')
|
18
|
+
return value if value.is_a?(::Date) || value.is_a?(String)
|
19
|
+
::Date.parse(value.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -2,37 +2,19 @@ module Filemaker
|
|
2
2
|
module Model
|
3
3
|
module Types
|
4
4
|
class Email
|
5
|
-
def
|
6
|
-
|
7
|
-
# nesting deeply
|
8
|
-
@value = value.nil? ? nil : value.to_s
|
9
|
-
end
|
10
|
-
|
11
|
-
def value
|
12
|
-
email = @value&.strip&.split(%r{,|\(|\/|\s})
|
5
|
+
def self.__filemaker_cast_to_ruby_object(value)
|
6
|
+
email = value&.strip&.split(%r{,|\(|\/|\s})
|
13
7
|
&.reject(&:empty?)&.first&.downcase
|
14
8
|
&.gsub(/[\uFF20\uFE6B\u0040]/, '@')
|
15
9
|
|
16
10
|
email&.include?('@') ? email : nil
|
17
11
|
end
|
18
12
|
|
19
|
-
def value
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_s
|
24
|
-
value
|
25
|
-
end
|
26
|
-
|
27
|
-
def ==(other)
|
28
|
-
to_s == other.to_s
|
13
|
+
def self.__filemaker_serialize_for_update(value)
|
14
|
+
__filemaker_cast_to_ruby_object(value)
|
29
15
|
end
|
30
|
-
alias eql? ==
|
31
16
|
|
32
|
-
|
33
|
-
# email, we need to escape at-sign. Note the single-quote escaping!
|
34
|
-
# e.g. 'a@host.com' will become 'a\\@host.com'
|
35
|
-
def to_query
|
17
|
+
def self.__filemaker_serialize_for_query(value)
|
36
18
|
value.gsub('@', '\@')
|
37
19
|
end
|
38
20
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Filemaker
|
2
|
+
module Model
|
3
|
+
module Types
|
4
|
+
class Integer
|
5
|
+
def self.__filemaker_cast_to_ruby_object(value)
|
6
|
+
return value if value.is_a?(::Integer)
|
7
|
+
value.to_i
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.__filemaker_serialize_for_update(value)
|
11
|
+
return value if value.is_a?(::Integer)
|
12
|
+
value.to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.__filemaker_serialize_for_query(value)
|
16
|
+
return value if value.is_a?(::Integer)
|
17
|
+
value.to_i
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Filemaker
|
2
|
+
module Model
|
3
|
+
module Types
|
4
|
+
class Text
|
5
|
+
def self.__filemaker_cast_to_ruby_object(value)
|
6
|
+
value.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.__filemaker_serialize_for_update(value)
|
10
|
+
value.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.__filemaker_serialize_for_query(value)
|
14
|
+
value.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Filemaker
|
2
|
+
module Model
|
3
|
+
module Types
|
4
|
+
class Time
|
5
|
+
def self.__filemaker_cast_to_ruby_object(value)
|
6
|
+
return value if value.is_a?(::Time)
|
7
|
+
::Time.parse(value.to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.__filemaker_serialize_for_update(value)
|
11
|
+
return value if value.is_a?(::Time)
|
12
|
+
::Time.parse(value.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.__filemaker_serialize_for_query(value)
|
16
|
+
return value if value.is_a?(::Time) || value.is_a?(String)
|
17
|
+
::Time.parse(value.to_s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/filemaker/record.rb
CHANGED
data/lib/filemaker/server.rb
CHANGED
@@ -1,29 +1,25 @@
|
|
1
|
-
require '
|
2
|
-
require 'typhoeus/adapters/faraday'
|
1
|
+
require 'typhoeus'
|
3
2
|
require 'filemaker/configuration'
|
4
3
|
|
5
4
|
module Filemaker
|
6
5
|
class Server
|
7
6
|
extend Forwardable
|
8
7
|
|
9
|
-
# @return [Faraday::Connection] the HTTP connection
|
10
|
-
attr_reader :connection
|
11
|
-
|
12
8
|
# @return [Filemaker::Store::DatabaseStore] the database store
|
13
9
|
attr_reader :databases
|
14
10
|
alias database databases
|
15
11
|
alias db databases
|
16
12
|
|
17
|
-
def_delegators :@config, :host, :url, :
|
13
|
+
def_delegators :@config, :host, :url, :endpoint, :log
|
18
14
|
def_delegators :@config, :account_name, :password
|
15
|
+
def_delegators :@config, :ssl_verifypeer, :ssl_verifyhost, :ssl, :timeout
|
19
16
|
|
20
|
-
def initialize
|
17
|
+
def initialize
|
21
18
|
@config = Configuration.new
|
22
19
|
yield @config if block_given?
|
23
20
|
raise ArgumentError, 'Missing config block' if @config.not_configurable?
|
24
21
|
|
25
22
|
@databases = Store::DatabaseStore.new(self)
|
26
|
-
@connection = get_connection(options)
|
27
23
|
end
|
28
24
|
|
29
25
|
# @api private
|
@@ -33,8 +29,8 @@ module Filemaker
|
|
33
29
|
# Also we want to pass in timeout option so we can ignore timeout for really
|
34
30
|
# long requests
|
35
31
|
#
|
36
|
-
# @return [Array]
|
37
|
-
def perform_request(
|
32
|
+
# @return [Array] response and request params Hash
|
33
|
+
def perform_request(action, args, options = {})
|
38
34
|
params = serialize_args(args)
|
39
35
|
.merge(expand_options(options))
|
40
36
|
.merge({ action => '' })
|
@@ -45,28 +41,36 @@ module Filemaker
|
|
45
41
|
log_action(params)
|
46
42
|
|
47
43
|
# yield params if block_given?
|
48
|
-
response = @connection.public_send(method, endpoint, params)
|
49
44
|
|
50
|
-
|
45
|
+
response = get_typhoeus_connection(params)
|
46
|
+
|
47
|
+
http_status = "#{response.response_code}:#{response.return_code}"
|
48
|
+
|
49
|
+
case response.response_code
|
51
50
|
when 200
|
52
51
|
[response, params]
|
53
52
|
when 401
|
54
53
|
raise Errors::AuthenticationError,
|
55
|
-
"[#{
|
54
|
+
"[#{http_status}] Authentication failed."
|
56
55
|
when 0
|
57
|
-
|
58
|
-
|
56
|
+
if response.return_code == :operation_timedout
|
57
|
+
raise Errors::HttpTimeoutError,
|
58
|
+
"[#{http_status}] Current timeout value is #{timeout}"
|
59
|
+
else
|
60
|
+
raise Errors::CommunicationError,
|
61
|
+
"[#{http_status}] Empty response."
|
62
|
+
end
|
59
63
|
when 404
|
60
64
|
raise Errors::CommunicationError,
|
61
|
-
"[#{
|
65
|
+
"[#{http_status}] Not found"
|
62
66
|
when 302
|
63
67
|
raise Errors::CommunicationError,
|
64
|
-
"[#{
|
68
|
+
"[#{http_status}] Redirect not supported"
|
65
69
|
when 502
|
66
70
|
raise Errors::CommunicationError,
|
67
|
-
"[#{
|
71
|
+
"[#{http_status}] Bad gateway. Too many records."
|
68
72
|
else
|
69
|
-
msg = "Unknown response
|
73
|
+
msg = "Unknown response code = #{http_status}"
|
70
74
|
raise Errors::CommunicationError, msg
|
71
75
|
end
|
72
76
|
end
|
@@ -77,21 +81,22 @@ module Filemaker
|
|
77
81
|
|
78
82
|
private
|
79
83
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
84
|
+
def get_typhoeus_connection(body)
|
85
|
+
request = Typhoeus::Request.new(
|
86
|
+
"#{url}#{endpoint}",
|
87
|
+
method: :post,
|
88
|
+
ssl_verifypeer: ssl_verifypeer,
|
89
|
+
ssl_verifyhost: ssl_verifyhost,
|
90
|
+
userpwd: "#{account_name}:#{password}",
|
91
|
+
body: body,
|
92
|
+
timeout: timeout || 0
|
93
|
+
)
|
94
|
+
|
95
|
+
request.run
|
92
96
|
end
|
93
97
|
|
94
98
|
# {"-db"=>"mydb", "-lay"=>"mylay", "email"=>"a@b.com", "updated_at": Date}
|
99
|
+
# Take Ruby type and serialize into a form FileMaker can understand
|
95
100
|
def serialize_args(args)
|
96
101
|
return {} if args.nil?
|
97
102
|
|
@@ -103,8 +108,6 @@ module Filemaker
|
|
103
108
|
args[key] = value.strftime('%m/%d/%Y')
|
104
109
|
when Time
|
105
110
|
args[key] = value.strftime('%H:%M')
|
106
|
-
when Filemaker::Model::Types::Email
|
107
|
-
args[key] = value.to_query
|
108
111
|
else
|
109
112
|
# Especially for range operator (...), we want to output as String
|
110
113
|
args[key] = value.to_s
|
@@ -196,10 +199,9 @@ module Filemaker
|
|
196
199
|
curl_ssl_option = ''
|
197
200
|
auth = ''
|
198
201
|
|
199
|
-
curl_ssl_option = ' -k'
|
202
|
+
curl_ssl_option = ' -k' unless ssl_verifypeer
|
200
203
|
|
201
|
-
auth = " -
|
202
|
-
has_auth
|
204
|
+
auth = " -u #{account_name}:[FILTERED]" if has_auth
|
203
205
|
|
204
206
|
# warn 'Pretty print like so: `curl XXX | xmllint --format -`'
|
205
207
|
warn "curl -XGET '#{full_url}'#{curl_ssl_option} -i#{auth}"
|