filemaker 0.0.19 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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}"
|