fm_store 0.1

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.
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Criterion
4
+ module Optional
5
+ # Tell FileMaker how many records in the found set to skip.
6
+ # Use together with +limit+ to page through the records
7
+ def skip(value = 20)
8
+ @options[:skip_records] = value
9
+ self
10
+ end
11
+
12
+ def limit(value = 20)
13
+ @options[:max_records] = value
14
+ self
15
+ end
16
+
17
+ # +ascend+ or +descend+
18
+ def order(field_and_orders)
19
+ sorts = field_and_orders.split(",").map(&:strip)
20
+ sort_field = []
21
+ sort_order = []
22
+
23
+ sorts.each do |s|
24
+ field, order = s.split(" ")
25
+ order = "asc" unless order
26
+
27
+ fm_name = klass.find_fm_name(field)
28
+
29
+ order = "ascend" if order.downcase == "asc"
30
+ order = "descend" if order.downcase == "desc"
31
+
32
+ sort_field << fm_name
33
+ sort_order << order
34
+ end
35
+
36
+ @options[:sort_field] = sort_field
37
+ @options[:sort_order] = sort_order
38
+
39
+ self
40
+ end
41
+
42
+ # logical_operator
43
+ # modification_id
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,20 @@
1
+ module Rfm
2
+ module Metadata
3
+ class Field
4
+ def coerce(value, resultset)
5
+ return nil if (value.nil? || value.empty?)
6
+ case self.result
7
+ when "text" then value
8
+ when "number" then BigDecimal.new(value)
9
+ when "date" then Date.strptime(value, resultset.date_format)
10
+ when "time" then DateTime.strptime("1/1/-4712 #{value}", "%m/%d/%Y #{resultset.time_format}")
11
+ when "timestamp" then DateTime.strptime(value, resultset.timestamp_format)
12
+ when "container" then URI.parse("#{resultset.server.scheme}://#{resultset.server.host_name}:#{resultset.server.port}#{value}")
13
+ else nil
14
+ end
15
+ rescue
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ class Field
4
+ attr_reader :name, :type, :fm_name, :searchable, :identity
5
+
6
+ def initialize(name, type, options = {})
7
+ @name = name
8
+ @fm_name = options[:fm_name] || name
9
+ @type = type
10
+ @searchable = options[:searchable] || false
11
+ @identity = options[:identity] || false
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Fields
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_inheritable_accessor :fields
8
+
9
+ self.fields = {}
10
+ end
11
+
12
+ module ClassMethods
13
+ # Defines all the fields that are available from the layout.
14
+ def field(name, type, options = {})
15
+ set_field(name.to_s, type, options)
16
+ end
17
+
18
+ protected
19
+
20
+ def set_field(name, type, options)
21
+ # We key FileMaker name rather then user specified name
22
+ fields[options[:fm_name] || name] = Field.new(name, type, options)
23
+ create_accessors(name)
24
+ end
25
+
26
+ def create_accessors(name)
27
+ define_method(name) { instance_variable_get("@#{name}") }
28
+ define_method("#{name}=") { |value| instance_variable_set("@#{name}", value) }
29
+ define_method("#{name}?") do
30
+ attr = instance_variable_get("@#{name}")
31
+ (@type == Boolean) ? attr == true : attr.present?
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Finders
4
+
5
+ # Criteria
6
+ [:where, :limit, :skip, :order, :exclude, :search, :id, :fm_id].each do |name|
7
+ define_method(name) do |*args|
8
+ criteria.send(name, *args)
9
+ end
10
+ end
11
+
12
+ [:in, :custom_query].each do |name|
13
+ define_method(name) do |*args|
14
+ criteria_query.send(name, *args)
15
+ end
16
+ end
17
+
18
+ def criteria
19
+ Criteria.new(self)
20
+ end
21
+
22
+ def criteria_query
23
+ Criteria.new(self, true)
24
+ end
25
+
26
+ # Will always return an array properly cast to the correct type
27
+ # def find(hash_or_record_id, options = {})
28
+ # where(hash_or_record_id, options)
29
+ # end
30
+
31
+ def total
32
+ criteria.paginate(:per_page => 1).total_entries
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,156 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Layout
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include FmStore::Components
8
+ self.include_root_in_json = false
9
+
10
+ cattr_accessor :layout, :database
11
+
12
+ attr_reader :new_record, :record_id, :mod_id
13
+
14
+ define_model_callbacks :create, :save, :update, :validation, :destroy
15
+ end
16
+
17
+ module ClassMethods
18
+ def set_layout(layout)
19
+ self.layout = layout
20
+ end
21
+
22
+ def set_database(database)
23
+ self.database = database
24
+ end
25
+
26
+ # Calling self.fields will ideally match here
27
+ # See FieldControl
28
+ def fm_fields
29
+ conn = Connection.establish_connection(self)
30
+ rs = conn.any.first.keys.inspect
31
+ end
32
+
33
+ # Return the real FileMaker, nil otherwise
34
+ def find_fm_name(attribute_name)
35
+ if fields.has_key?(attribute_name)
36
+ return attribute_name
37
+ else
38
+ f = fields.find { |a| a.last.name == attribute_name }
39
+
40
+ f.last.fm_name if f
41
+ end
42
+ end
43
+
44
+ def find_fm_type(attribute_name)
45
+ f = fields.find { |a| a.last.name == attribute_name }
46
+
47
+ f.last.type if f
48
+ end
49
+
50
+ def searchable_fields
51
+ fields.map(&:last).select(&:searchable).map(&:name)
52
+ end
53
+
54
+ def identity
55
+ fields.map(&:last).find(&:identity).try(:fm_name) || "-recid"
56
+ end
57
+
58
+ # Drop-down, for example
59
+ # http://host/fmi/xml/FMPXMLLAYOUT.xml?-db=jobs+&-lay=jobs&-view=
60
+ def value_lists
61
+ conn = Connection.establish_connection(self)
62
+ conn.value_lists
63
+ end
64
+
65
+ def first
66
+ limit(1).first
67
+ end
68
+ end
69
+
70
+ def initialize(attributes = {})
71
+ @associations = {}
72
+ @new_record = true
73
+ process(attributes)
74
+ end
75
+
76
+ def fm_attributes
77
+ attrs = {}
78
+
79
+ fields.each do |fm_attr, field|
80
+ ivar = send("#{field.name}")
81
+
82
+ type = field.type
83
+
84
+ if type == Date
85
+ ivar = ivar.strftime("%m/%d/%Y") if ivar
86
+ elsif type == DateTime
87
+ ivar = ivar.strftime("%m/%d/%Y %H:%M:%S") if ivar
88
+ elsif type == Time
89
+ ivar = ivar.strftime("%H:%M") if ivar
90
+ end
91
+
92
+ # case ivar
93
+ # when Date
94
+ # ivar = ivar.strftime("%m/%d/%Y")
95
+ # when DateTime
96
+ # ivar = ivar.strftime("%m/%d/%Y %H:%M:%S")
97
+ # when Time
98
+ # ivar = ivar.strftime("%H:%M")
99
+ # end
100
+
101
+ attrs[fm_attr] = ivar if ivar # ignore nil attributes
102
+ end
103
+
104
+ attrs
105
+ end
106
+
107
+ def attributes
108
+ @attributes = {}
109
+
110
+ fields.each do |fm_attr, field|
111
+ ivar = send("#{field.name}")
112
+ @attributes[field.name] = ivar
113
+ end
114
+
115
+ return @attributes
116
+ end
117
+
118
+ def reload
119
+ @associations = {}
120
+ self.class.id(id)
121
+ end
122
+
123
+ def id
124
+ self.class.identity == "-recid" ? @record_id : send(self.class.fields[self.class.identity].name)
125
+ end
126
+
127
+ def new_record?
128
+ @new_record
129
+ end
130
+
131
+ def to_param
132
+ id.to_s if id
133
+ end
134
+
135
+ # Require by ActiveModel
136
+ def to_model
137
+ self
138
+ end
139
+
140
+ def to_key
141
+ id if id
142
+ end
143
+
144
+ def persisted?
145
+ !new_record?
146
+ end
147
+
148
+ protected
149
+
150
+ def process(attributes)
151
+ attributes.each do |k, v|
152
+ send("#{k}=", v) if respond_to?(k)
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Paging
4
+ def paginate(opts = {})
5
+ options[:max_records] = opts[:per_page] || 30
6
+
7
+ if opts[:page]
8
+ options[:skip_records] = (opts[:page].to_i - 1) * options[:max_records].to_i
9
+ end
10
+
11
+ collection = execute(true)
12
+
13
+ WillPaginate::Collection.create(page, per_page, count) do |pager|
14
+ pager.replace(collection)
15
+ end
16
+ end
17
+
18
+ def page
19
+ skips, limits = options[:skip_records], options[:max_records]
20
+ (skips && limits) ? (skips + limits) / limits : 1
21
+ end
22
+
23
+ def per_page
24
+ (options[:max_records] || 30).to_i
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Persistence
4
+ extend ActiveSupport::Concern
5
+
6
+ # The place where all the persistence took place, like insert, update
7
+ module ClassMethods
8
+ def create(attributes = {})
9
+
10
+ end
11
+ end
12
+
13
+ # Instance methods
14
+ def save
15
+ create_or_update
16
+ end
17
+
18
+ def update_attributes(attributes = {})
19
+ if valid?
20
+ attrs = {}
21
+
22
+ attributes.each do |field, value|
23
+ field = field.to_s
24
+
25
+ fm_name = self.class.find_fm_name(field)
26
+ type = self.class.find_fm_type(field)
27
+
28
+ if fm_name
29
+ if type == Date
30
+ if value.blank?
31
+ value = '' # clear the date
32
+ else
33
+ value = value.strftime("%m/%d/%Y")
34
+ end
35
+ elsif type == DateTime
36
+ if value.blank?
37
+ value = ''
38
+ else
39
+ value = value.strftime("%m/%d/%Y %H:%M:%S")
40
+ end
41
+ elsif type == Time
42
+ if value.blank?
43
+ value = ''
44
+ else
45
+ value = value.strftime("%H:%M")
46
+ end
47
+ end
48
+
49
+ attrs[fm_name] = value
50
+ end
51
+ end
52
+
53
+ run_callbacks(:save) do
54
+ conn = Connection.establish_connection(self.class)
55
+ result = conn.edit(@record_id, attrs)
56
+
57
+ return FmStore::Builders::Single.build(result, self.class)
58
+ end;self # just in case
59
+ else
60
+ false
61
+ end
62
+ end
63
+
64
+ # Throws Rfm::Error::RecordAccessDeniedError if no permission to delete
65
+ def destroy
66
+ run_callbacks(:destroy) do
67
+ unless @record_id.nil?
68
+ conn = Connection.establish_connection(self.class)
69
+ conn.delete(@record_id)
70
+ end
71
+ end
72
+ end
73
+
74
+ alias :delete :destroy
75
+
76
+ protected
77
+
78
+ # Will always return +self+
79
+ def create_or_update
80
+ result = new_record? ? create : update
81
+ end
82
+
83
+ def create
84
+ if valid?
85
+ run_callbacks(:save) do
86
+ conn = Connection.establish_connection(self.class)
87
+ result = conn.create(self.fm_attributes)
88
+
89
+ @record_id = result[0].record_id
90
+ @new_record = false
91
+ end; self
92
+ else
93
+ false
94
+ end
95
+ end
96
+
97
+ def update
98
+ if valid?
99
+ run_callbacks(:save) do
100
+ conn = Connection.establish_connection(self.class)
101
+ result = conn.edit(@record_id, self.fm_attributes)
102
+ end; self
103
+ else
104
+ false
105
+ end
106
+ end
107
+
108
+ end
109
+ end