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,25 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Builders
4
+ class Single
5
+ def self.build(records, model)
6
+ record = records.first
7
+
8
+ fm_fields = record.keys
9
+
10
+ obj = model.new
11
+
12
+ fm_fields.each do |fm_field|
13
+ field = model.fields[fm_field] # Field
14
+ obj.instance_variable_set("@#{field.name}", record[fm_field])
15
+ end
16
+
17
+ obj.instance_variable_set("@new_record", false)
18
+ obj.instance_variable_set("@mod_id", record.mod_id)
19
+ obj.instance_variable_set("@record_id", record.record_id)
20
+
21
+ return obj
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Components
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include FmStore::Fields
8
+ include FmStore::Associations
9
+ include FmStore::Persistence
10
+ include ActiveModel::Validations
11
+ include ActiveModel::Serializers::JSON
12
+ include ActiveModel::Serializers::Xml
13
+
14
+ extend ActiveModel::Callbacks
15
+ extend ActiveModel::Naming
16
+ extend FmStore::Finders
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ class Config
4
+ include Singleton
5
+
6
+ attr_accessor :host, :account_name, :password, :ssl, :log_actions
7
+
8
+ def initialize
9
+ @ssl = false
10
+ @log_actions = true
11
+ end
12
+
13
+ def set_settings(settings)
14
+ settings.each_pair do |name, value|
15
+ send("#{name}=", value) if respond_to?(name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ class Connection
4
+ # Returns an Rfm::Layout which further data request can begin
5
+ def self.establish_connection(layout)
6
+ layout_name = layout.layout
7
+ database_name = layout.database
8
+
9
+ # Get the config from fm_store.yml
10
+ config = FmStore::Config.instance
11
+
12
+ server = Rfm::Server.new({
13
+ :host => config.host,
14
+ :account_name => config.account_name,
15
+ :password => config.password,
16
+ :ssl => config.ssl,
17
+ :log_actions => config.log_actions
18
+ })
19
+
20
+ server[database_name][layout_name]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+ require 'fm_store/criterion/inclusion'
3
+ require 'fm_store/criterion/exclusion'
4
+ require 'fm_store/criterion/optional'
5
+ require 'fm_store/paging'
6
+
7
+ module FmStore
8
+ class FmReader
9
+ include Paging
10
+
11
+ attr_reader :criteria
12
+
13
+ delegate :klass, :params, :options, :find_query, :to => :criteria
14
+
15
+ def initialize(criteria)
16
+ @criteria = criteria
17
+ end
18
+
19
+ def count
20
+ @count ||= 0
21
+ end
22
+
23
+ # This is the method to really get down and grab the records
24
+ # The criteria find_query flag will be set to true if it is a -findquery
25
+ def execute(paginating = false)
26
+ ActiveSupport::Notifications.instrument(:fm_store_execute, :model_name => klass.to_s, :params => params, :options => options) do
27
+ conn = Connection.establish_connection(klass)
28
+
29
+ if find_query
30
+ # We will be using the -findquery command
31
+ rs = conn.send(:get_records, "-findquery", params, options)
32
+ else
33
+ rs = conn.find(params, options)
34
+ end
35
+
36
+ @count = rs.foundset_count if paginating
37
+
38
+ FmStore::Builders::Collection.build(rs, klass)
39
+ end
40
+ end
41
+ end
42
+
43
+ class Criteria
44
+ include Criterion::Inclusion
45
+ include Criterion::Exclusion
46
+ include Criterion::Optional
47
+ include Enumerable
48
+
49
+ attr_reader :klass, :params, :options, :raw_params
50
+ attr_accessor :find_query
51
+
52
+ delegate :paginate, :to => :reader
53
+
54
+ def initialize(klass, find_query = false)
55
+ @params, @options, @klass, @find_query = {}, {}, klass, find_query
56
+
57
+ if find_query
58
+ @key_values = {}
59
+ @query_map = []
60
+ @counter = 0
61
+ end
62
+ end
63
+
64
+ def reader
65
+ @reader ||= FmReader.new(self)
66
+ end
67
+
68
+ def each(&block)
69
+ reader.execute.each { |record| yield record } if block_given?
70
+ end
71
+
72
+ def to_s
73
+ "#{params.inspect}, #{options.inspect}"
74
+ end
75
+
76
+ def total
77
+ original_max_record = options[:max_records]
78
+ count = paginate(:per_page => 1).total_entries
79
+ limit(original_max_record)
80
+
81
+ return count
82
+ end
83
+
84
+ def all
85
+ reader.execute
86
+ end
87
+
88
+ protected
89
+
90
+ def update_params(params)
91
+ @params.merge!(params)
92
+ end
93
+
94
+ def update_options(options)
95
+ @options.merge!(options)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Criterion
4
+ module Exclusion
5
+ def exclude(params = {})
6
+ accepted_params = {}
7
+
8
+ params.each do |field, value|
9
+ field = field.to_s
10
+
11
+ fm_name = klass.find_fm_name(field)
12
+
13
+ if fm_name
14
+ accepted_params["#{fm_name}.op"] = "neq"
15
+ accepted_params[fm_name] = value
16
+ end
17
+ end
18
+
19
+ update_params(accepted_params)
20
+ self
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,255 @@
1
+ # encoding: utf-8
2
+ module FmStore
3
+ module Criterion
4
+ module Inclusion
5
+ # Adds a +where+ criterion. Do not specify option here. Do it using
6
+ # skip, limit, order, etc
7
+ # Returns: <tt>self</tt>
8
+ def where(params = {}, logical_and = true)
9
+ accepted_params = {}
10
+
11
+ params.each do |field, value|
12
+ field = field.to_s # just to normalize it
13
+
14
+ with_operator = field.split(".")
15
+
16
+ if with_operator.size == 2
17
+ fm_name = klass.find_fm_name(with_operator.first)
18
+
19
+ if fm_name
20
+ accepted_params[fm_name] = value
21
+ accepted_params["#{fm_name}.op"] = with_operator.last
22
+ end
23
+ else
24
+ fm_name = klass.find_fm_name(field)
25
+ accepted_params[fm_name] = value if fm_name
26
+ end
27
+ end
28
+
29
+ accepted_params["-lop"] = "or" unless logical_and
30
+
31
+ update_params(accepted_params)
32
+ self
33
+ end
34
+
35
+ def search(params = {})
36
+ current_page = params[:page] || 1
37
+
38
+ if @params.size.zero?
39
+ if params[:q].present?
40
+ query = params[:q]
41
+
42
+ p = klass.searchable_fields.inject({}) { |h, name| h[name] = query; h }
43
+ where(p, false).paginate(:page => current_page) # Logical OR
44
+ else
45
+ where.paginate(:page => current_page)
46
+ end
47
+ else
48
+ # we have constraint, but it can be from +where+ or +in+
49
+ # current implementation only take into account +where+
50
+
51
+ if params[:q].present?
52
+ c = find_query ? @raw_params : @params # we need to get the raw params here
53
+ query = params[:q]
54
+
55
+ p = klass.searchable_fields.inject({}) { |h, name| h[name] = query; h }
56
+
57
+ accepted_params = {}
58
+ p.each do |field, value|
59
+ field = field.to_s
60
+
61
+ fm_name = klass.find_fm_name(field)
62
+ accepted_params[fm_name] = value if fm_name
63
+ end
64
+
65
+ self.find_query = true
66
+ final = assemble_constraint_query(c, accepted_params)
67
+ update_params(final)
68
+ paginate(:page => current_page)
69
+ else
70
+ if find_query
71
+ self.in(@raw_params).paginate(:page => current_page)
72
+ else
73
+ where(@params).paginate(:page => current_page)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def id(record_id)
80
+ return nil if record_id.blank?
81
+
82
+ if klass.identity == "-recid"
83
+ update_params("-recid" => record_id)
84
+ else
85
+ update_params(klass.identity => "=#{record_id}")
86
+ end
87
+
88
+ self.limit(1).first
89
+ end
90
+
91
+ def fm_id(record_id)
92
+ update_params("-recid" => record_id)
93
+
94
+ self.limit(1).first
95
+ end
96
+
97
+ # -query
98
+ # Job.in("status" => ["open", "pending"], :category => ["Account", "IT"])
99
+ # Operator not allowed in -findquery query command, so do not write this
100
+ # Job.in("status.eq" => ["closed", "pending"])
101
+ def in(params = {})
102
+ accepted_params = {}
103
+
104
+ params.each do |field, value|
105
+ field = field.to_s
106
+
107
+ # Convert to an Array if it is String
108
+ value = Array(value) if value.is_a?(String)
109
+
110
+ fm_name = klass.find_fm_name(field)
111
+ accepted_params[fm_name] = value if fm_name
112
+ end
113
+
114
+ @raw_params = accepted_params
115
+
116
+ update_params(assemble_query(accepted_params))
117
+ self
118
+ end
119
+
120
+ def custom_query(params = {})
121
+ update_params(params)
122
+ self
123
+ end
124
+
125
+ protected
126
+
127
+ # This will build constraint AND and OR query
128
+
129
+ def assemble_constraint_query(constraint, query)
130
+ key_values, query_map = build_constraint_key_values(constraint, query)
131
+ key_values.merge("-query" => query_translate(array_mix(query_map)))
132
+ end
133
+
134
+ def build_constraint_key_values(c, q)
135
+ key_values = {}
136
+ c_map = []
137
+ q_map = []
138
+ counter = 0
139
+
140
+ # Process the constraint first
141
+ c.each_with_index do |ha,i|
142
+ ha[1] = Array(ha[1]) if ha[1].is_a?(String)
143
+ query_tag = []
144
+ ha[1].each do |v|
145
+ key_values["-q#{counter}"] = ha[0]
146
+ key_values["-q#{counter}.value"] = v
147
+ query_tag << "q#{counter}"
148
+ counter += 1
149
+ end
150
+ c_map << query_tag
151
+ end
152
+
153
+ # c.each do |k, v|
154
+ # v = Array(v) if v.is_a?(String)
155
+ #
156
+ # v.each do |constraint|
157
+ # key_values["-q#{counter}"] = k
158
+ # key_values["-q#{counter}.value"] = constraint
159
+ # c_map << "q#{counter}"
160
+ # counter += 1
161
+ # end
162
+ # end
163
+
164
+
165
+ # Then user's query
166
+ q.each_with_index do |ha,i|
167
+ # ha[1] = ha[1].split(/\s|,/).select(&:present?) if ha[1].is_a?(String)
168
+ ha[1] = Array(ha[1]) if ha[1].is_a?(String)
169
+ query_tag = []
170
+ ha[1].each do |v|
171
+ key_values["-q#{counter}"] = ha[0]
172
+ key_values["-q#{counter}.value"] = v
173
+ query_tag << "q#{counter}"
174
+ counter += 1
175
+ end
176
+ q_map << query_tag
177
+ end
178
+
179
+ # q.each do |k, v|
180
+ # v = v.split(/\s|,/).select(&:present?) if v.is_a?(String)
181
+ #
182
+ # v.each do |query|
183
+ # key_values["-q#{counter}"] = k
184
+ # key_values["-q#{counter}.value"] = query
185
+ # q_map << "q#{counter}"
186
+ # counter += 1
187
+ # end
188
+ # end
189
+
190
+ return key_values, (c_map + [q_map])
191
+ end
192
+
193
+ def constraint_array_mix(ary, line=[], rslt=[])
194
+ # final = []
195
+ #
196
+ # ary.last.each do |query|
197
+ # tmp = ary.first.dup
198
+ # tmp << query
199
+ # final << tmp
200
+ # end
201
+ #
202
+ # final
203
+
204
+ ary[0].to_a.each_with_index do |v,i|
205
+ constraint_array_mix(ary[1,ary.size], (line + [v]), rslt)
206
+ rslt << (line + [v]) if ary.size == 1
207
+ end
208
+ return rslt
209
+ end
210
+
211
+ # These methods are taken from http://pastie.org/914503
212
+ # Build ruby params to send to -query action via RFM
213
+ def assemble_query(query_hash)
214
+ key_values, query_map = build_key_values(query_hash)
215
+ key_values.merge("-query" => query_translate(array_mix(query_map)))
216
+ end
217
+
218
+ # Build key-value definitions and query map '-q1...'
219
+ def build_key_values(qh)
220
+ # @key_values = {}
221
+ # @query_map = []
222
+ # @counter = 0
223
+
224
+ qh.each_with_index do |ha,i|
225
+ ha[1] = ha[1].to_a
226
+ query_tag = []
227
+ ha[1].each do |v|
228
+ @key_values["-q#{@counter}"] = ha[0]
229
+ @key_values["-q#{@counter}.value"] = v
230
+ query_tag << "q#{@counter}"
231
+ @counter += 1
232
+ end
233
+ @query_map << query_tag
234
+ end
235
+ return @key_values, @query_map
236
+ end
237
+
238
+ # Build query request logic for FMP requests '-query...'
239
+ def array_mix(ary, line=[], rslt=[])
240
+ ary[0].to_a.each_with_index do |v,i|
241
+ array_mix(ary[1,ary.size], (line + [v]), rslt)
242
+ rslt << (line + [v]) if ary.size == 1
243
+ end
244
+ return rslt
245
+ end
246
+
247
+ # Translate query request logic to string
248
+ def query_translate(mixed_ary)
249
+ rslt = ""
250
+ sub = mixed_ary.collect {|a| "(#{a.join(',')})"}
251
+ sub.join(";")
252
+ end
253
+ end
254
+ end
255
+ end