ginjo-rfm 2.0.2 → 2.1.0.pre02

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.
data/lib/rfm/server.rb CHANGED
@@ -107,6 +107,9 @@ module Rfm
107
107
  # * *name* is the name of this database
108
108
  # * *state* is a hash of all server options used to initialize this server
109
109
  class Server
110
+ #extend Config
111
+ include Config
112
+
110
113
 
111
114
  # To create a Server object, you typically need at least a host name:
112
115
  #
@@ -192,10 +195,14 @@ module Rfm
192
195
  # :root_cert_name => 'example.pem'
193
196
  # :root_cert_path => '/usr/cert_file/'
194
197
  # })
195
- def initialize(options)
196
- raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Server has no host name. Attempted name '#{options[:host]}'.") if options[:host].to_s == ''
198
+ def initialize(*args)
199
+ config :parent => 'Rfm::Config'
200
+ options = get_config(*args)
201
+ config sanitize_config(options, {}, true)
202
+ config(:host => (options[:strings].delete_at(0) || options[:host]) )
203
+ raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Server has no host name. Attempted name '#{config[:host]}'.") if config[:host].to_s == ''
197
204
 
198
- @state = {
205
+ @defaults = {
199
206
  :host => 'localhost',
200
207
  :port => 80,
201
208
  :ssl => true,
@@ -210,18 +217,13 @@ module Rfm
210
217
  :warn_on_redirect => true,
211
218
  :raise_on_401 => false,
212
219
  :timeout => 60,
213
- :ignore_bad_data => false
214
- }.merge(options)
215
-
216
- @state.freeze
217
-
218
- @host_name = @state[:host]
219
- @scheme = @state[:ssl] ? "https" : "http"
220
- @port = @state[:ssl] && options[:port].nil? ? 443 : @state[:port]
220
+ :ignore_bad_data => false,
221
+ :grammar => 'fmresultset'
222
+ } #.merge(options)
221
223
 
222
- @db = Rfm::Factory::DbFactory.new(self)
224
+ @databases = Rfm::Factory::DbFactory.new(self)
223
225
  end
224
-
226
+
225
227
  # Access the database object representing a database on the server. For example:
226
228
  #
227
229
  # myServer['Customers']
@@ -237,10 +239,19 @@ module Rfm
237
239
  # def [](dbname, acnt=nil, pass=nil)
238
240
  # self.db[dbname, acnt, pass]
239
241
  # end
240
- def_delegator :db, :[]
242
+ def_delegator :databases, :[]
243
+
244
+ attr_reader :databases #, :host_name, :port, :scheme, :state
245
+ # Legacy Rfm method to get/create databases from server object
246
+ alias_method :db, :databases
241
247
 
242
- attr_reader :db, :host_name, :port, :scheme, :state
243
- alias_method :databases, :db
248
+ def state(*args)
249
+ @defaults.merge(get_config(*args))
250
+ end
251
+
252
+ def host_name; state[:host]; end
253
+ def scheme; state[:ssl] ? "https" : "http"; end
254
+ def port; state[:ssl] && state[:port].nil? ? 443 : state[:port]; end
244
255
 
245
256
  # Performs a raw FileMaker action. You will generally not call this method directly, but it
246
257
  # is exposed in case you need to do something "under the hood."
@@ -270,16 +281,27 @@ module Rfm
270
281
  # { :max_records => 20 }
271
282
  # )
272
283
  def connect(account_name, password, action, args, options = {})
284
+ grammar_option = options.delete(:grammar)
273
285
  post = args.merge(expand_options(options)).merge({action => ''})
274
- http_fetch(@host_name, @port, "/fmi/xml/fmresultset.xml", account_name, password, post)
286
+ grammar = select_grammar(post, :grammar=>grammar_option)
287
+ http_fetch(host_name, port, "/fmi/xml/#{grammar}.xml", account_name, password, post)
275
288
  end
276
289
 
277
290
  def load_layout(layout)
278
291
  post = {'-db' => layout.db.name, '-lay' => layout.name, '-view' => ''}
279
- resp = http_fetch(@host_name, @port, "/fmi/xml/FMPXMLLAYOUT.xml", layout.db.account_name, layout.db.password, post)
292
+ resp = http_fetch(host_name, port, "/fmi/xml/FMPXMLLAYOUT.xml", layout.db.account_name, layout.db.password, post)
280
293
  #remove_namespace(resp.body)
281
294
  end
282
295
 
296
+ def select_grammar(post, options={})
297
+ grammar = state(options)[:grammar] || 'fmresultset'
298
+ if grammar.to_s.downcase == 'auto'
299
+ post.keys.find(){|k| %w(-find -findall -dbnames -layoutnames -scriptnames).include? k.to_s} ? "FMPXMLRESULT" : "fmresultset"
300
+ else
301
+ grammar
302
+ end
303
+ end
304
+
283
305
  # Removes namespace from fmpxmllayout, so xpath will work
284
306
  def remove_namespace(xml)
285
307
  xml.gsub(/xmlns=\"[^\"]*\"/, '')
@@ -290,11 +312,11 @@ module Rfm
290
312
  def http_fetch(host_name, port, path, account_name, password, post_data, limit=10)
291
313
  raise Rfm::CommunicationError.new("While trying to reach the Web Publishing Engine, RFM was redirected too many times.") if limit == 0
292
314
 
293
- if @state[:log_actions] == true
315
+ if state[:log_actions] == true
294
316
  #qs = post_data.collect{|key,val| "#{CGI::escape(key.to_s)}=#{CGI::escape(val.to_s)}"}.join("&")
295
317
  qs_unescaped = post_data.collect{|key,val| "#{key.to_s}=#{val.to_s}"}.join("&")
296
318
  #warn "#{@scheme}://#{@host_name}:#{@port}#{path}?#{qs}"
297
- warn "#{@scheme}://#{@host_name}:#{@port}#{path}?#{qs_unescaped}"
319
+ warn "#{scheme}://#{host_name}:#{port}#{path}?#{qs_unescaped}"
298
320
  end
299
321
 
300
322
  request = Net::HTTP::Post.new(path)
@@ -303,19 +325,19 @@ module Rfm
303
325
 
304
326
  response = Net::HTTP.new(host_name, port)
305
327
  #ADDED LONG TIMEOUT TIMOTHY TING 05/12/2011
306
- response.open_timeout = response.read_timeout = @state[:timeout]
307
- if @state[:ssl]
328
+ response.open_timeout = response.read_timeout = state[:timeout]
329
+ if state[:ssl]
308
330
  response.use_ssl = true
309
- if @state[:root_cert]
331
+ if state[:root_cert]
310
332
  response.verify_mode = OpenSSL::SSL::VERIFY_PEER
311
- response.ca_file = File.join(@state[:root_cert_path], @state[:root_cert_name])
333
+ response.ca_file = File.join(state[:root_cert_path], state[:root_cert_name])
312
334
  else
313
335
  response.verify_mode = OpenSSL::SSL::VERIFY_NONE
314
336
  end
315
337
  end
316
338
 
317
339
  response = response.start { |http| http.request(request) }
318
- if @state[:log_responses] == true
340
+ if state[:log_responses] == true
319
341
  response.to_hash.each { |key, value| warn "#{key}: #{value}" }
320
342
  warn response.body
321
343
  end
@@ -324,7 +346,7 @@ module Rfm
324
346
  when Net::HTTPSuccess
325
347
  response
326
348
  when Net::HTTPRedirection
327
- if @state[:warn_on_redirect]
349
+ if state[:warn_on_redirect]
328
350
  warn "The web server redirected to " + response['location'] +
329
351
  ". You should revise your connection hostname or fix your server configuration if possible to improve performance."
330
352
  end
@@ -1,13 +1,6 @@
1
1
  module Rfm
2
2
 
3
- # Top level config hash accepts any defined config parameters,
4
- # or group-name keys pointing to config subsets.
5
- # The subsets can be any grouping of defined config parameters, as a hash.
6
- # See CONFIG_KEYS for defined config parameters.
7
- #
8
- module Config
9
- require 'yaml'
10
-
3
+ # Should these go in Rfm module?
11
4
  CONFIG_KEYS = %w(
12
5
  file_name
13
6
  file_path
@@ -31,10 +24,21 @@ module Rfm
31
24
  log_parser
32
25
  use
33
26
  parent
27
+ grammar
34
28
  )
29
+
30
+ CONFIG_DONT_STORE = %w(strings using parents symbols objects)
35
31
 
32
+ # Top level config hash accepts any defined config parameters,
33
+ # or group-name keys pointing to config subsets.
34
+ # The subsets can be any grouping of defined config parameters, as a hash.
35
+ # See CONFIG_KEYS for defined config parameters.
36
+ #
37
+ module Config
38
+ require 'yaml'
39
+
36
40
  extend self
37
- @config = {}
41
+ @config = {}
38
42
 
39
43
  # Set @config with args & options hash.
40
44
  # Args should be symbols representing configuration groups,
@@ -78,16 +82,34 @@ module Rfm
78
82
  # == Gets top level settings, merged with local and ad-hoc settings.
79
83
  # get_config :layout => 'my_layout
80
84
  #
81
- def get_config(*args)
85
+ def get_config(*arguments)
86
+ args = arguments.clone
82
87
  @config ||= {}
83
88
  opt = args.rfm_extract_options!
84
- strings = []
85
- while args[0].is_a?(String) do; strings << args.shift; end
86
- if args.size == 0
87
- config_filter(config_merge_with_parent)
88
- else
89
- config_filter(config_merge_with_parent, args)
90
- end.merge(opt).merge(:strings=>strings)
89
+ strings = opt[:strings].rfm_force_array || []
90
+ symbols = opt[:use].rfm_force_array || []
91
+ objects = opt[:objects].rfm_force_array || []
92
+ args.each do |arg|
93
+ case true
94
+ when arg.is_a?(String) ; strings << arg
95
+ when arg.is_a?(Symbol) ; symbols << arg
96
+ else objects.unshift arg
97
+ end
98
+ end
99
+
100
+ rslt = config_merge_with_parent(symbols).merge(opt)
101
+ #using = rslt[:using].rfm_force_array
102
+ sanitize_config(rslt, CONFIG_DONT_STORE, false)
103
+ rslt[:using].delete ""
104
+ rslt[:parents].delete ""
105
+ rslt.merge(:strings=>strings, :objects=>objects)
106
+ end
107
+
108
+ # Keep should be a list of strings representing keys to keep.
109
+ def sanitize_config(conf={}, keep=[], dupe=false)
110
+ (conf = conf.clone) if dupe
111
+ conf.reject!{|k,v| (!CONFIG_KEYS.include?(k.to_s) or [{},[],''].include?(v)) and !keep.include? k.to_s }
112
+ conf
91
113
  end
92
114
 
93
115
  protected
@@ -115,37 +137,39 @@ module Rfm
115
137
  def config_write(opt, args)
116
138
  strings = []; while args[0].is_a?(String) do; strings << args.shift; end
117
139
  args.each{|a| @config.merge!(:use=>a.to_sym)}
118
- @config.merge!(opt)
140
+ @config.merge!(opt).reject! {|k,v| CONFIG_DONT_STORE.include? k.to_s}
119
141
  yield(strings) if block_given?
120
142
  end
121
143
 
122
- # Get composite config from all levels, adding :use parameters to a
123
- # temporary top-level value.
124
- def config_merge_with_parent
144
+ # Get composite config from all levels, processing :use parameters at each level
145
+ def config_merge_with_parent(filters=nil)
125
146
  remote = if (self != Rfm::Config)
126
147
  eval(@config[:parent] || 'Rfm::Config').config_merge_with_parent rescue {}
127
148
  else
128
149
  get_config_file.merge((defined?(RFM_CONFIG) and RFM_CONFIG.is_a?(Hash)) ? RFM_CONFIG : {})
129
- end
150
+ end.clone
130
151
 
131
- use = (remote[:use].rfm_force_array | @config[:use].rfm_force_array).compact
132
- remote.merge(@config).merge(:use=>use)
152
+ remote[:using] ||= []
153
+ remote[:parents] ||= ['file', 'RFM_CONFIG']
154
+
155
+ filters = (@config[:use].rfm_force_array | filters.rfm_force_array).compact
156
+ rslt = config_filter(remote, filters).merge(config_filter(@config, filters))
157
+
158
+ rslt[:using].concat((@config[:use].rfm_force_array | filters).compact.flatten) #.join
159
+ rslt[:parents] << @config[:parent].to_s
160
+
161
+ rslt.delete :parent
162
+
163
+ rslt
133
164
  end
134
165
 
135
- # This version uses either/or method input filters OR compiled config :use=>filters.
136
- # Given config hash, return filtered subgroup settings. Filters should be symbols.
137
- # def config_filter(conf, filters=nil)
138
- # filters ||= conf[:use].rfm_force_array if !conf[:use].blank?
139
- # filters.each{|f| next unless conf[f]; conf.merge!(conf[f] || {})} if !filters.blank?
140
- # conf.reject!{|k,v| !CONFIG_KEYS.include?(k.to_s) or v.to_s == '' }
141
- # conf
142
- # end
143
- #
144
- # This version combines both method input filters AND :use=>filters
166
+ # Returns a configuration hash overwritten by :use filters in the hash
167
+ # that match passed-in filter names or any filter names contained within the hash's :use key.
145
168
  def config_filter(conf, filters=nil)
146
- filters = conf[:use] = (conf[:use].rfm_force_array | filters.rfm_force_array).compact
169
+ conf = conf.clone
170
+ filters = (conf[:use].rfm_force_array | filters.rfm_force_array).compact
147
171
  filters.each{|f| next unless conf[f]; conf.merge!(conf[f] || {})} if !filters.blank?
148
- conf.reject!{|k,v| !CONFIG_KEYS.include?(k.to_s) or [{},[],''].include?(v) }
172
+ conf.delete(:use)
149
173
  conf
150
174
  end
151
175
 
@@ -32,11 +32,12 @@ class Object
32
32
  # since XmlMini doesn't know which will be returnd for any particular element.
33
33
  # See Rfm Layout & Record where this is used.
34
34
  def rfm_force_array
35
+ return [] if self.nil?
35
36
  self.is_a?(Array) ? self : [self]
36
37
  end
37
38
 
38
39
  # Just testing this functionality
39
- def local_methods
40
+ def rfm_local_methods
40
41
  self.methods - self.class.superclass.methods
41
42
  end
42
43
 
@@ -62,8 +63,73 @@ class Array
62
63
  def rfm_extract_options!
63
64
  last.is_a?(::Hash) ? pop : {}
64
65
  end
66
+
67
+ # These methods allow dynamic extension of array members with other modules.
68
+ # These methods also carry the @root object for reference, when you don't have the
69
+ # root object explicity referenced anywhere.
70
+ #
71
+ # These methods might slow down array traversal, as
72
+ # they add interpreted code to methods that were otherwise pure C.
73
+ def rfm_extend_members(klass, caller=nil)
74
+ @parent = caller
75
+ @root = caller.instance_variable_get(:@root)
76
+ @member_extension = klass
77
+ self.instance_eval do
78
+ class << self
79
+ attr_accessor :parent
80
+
81
+ alias_method 'old_reader', '[]'
82
+ def [](*args)
83
+ member = old_reader(*args)
84
+ rfm_extend_member(member, @member_extension, args[0]) if args[0].is_a? Integer
85
+ member
86
+ end
87
+
88
+ alias_method 'old_each', 'each'
89
+ def each
90
+ i = -1
91
+ old_each do |member|
92
+ i = i + 1
93
+ rfm_extend_member(member, @member_extension, i)
94
+ yield(member)
95
+ end
96
+ end
97
+ end
98
+ end unless defined? old_reader
99
+ self
100
+ end
101
+
102
+ def rfm_extend_member(member, extension, i=nil)
103
+ if member and extension
104
+ unless member.instance_variable_get(:@root)
105
+ member.instance_variable_set(:@root, @root)
106
+ member.instance_variable_set(:@parent, self)
107
+ member.instance_variable_set(:@index, i)
108
+ member.instance_eval(){def root; @root; end}
109
+ member.instance_eval(){def parent; @parent; end}
110
+ member.instance_eval(){def get_index; @index; end}
111
+ end
112
+ member.extend(extension)
113
+ end
114
+ end
115
+
65
116
  end # Array
66
117
 
118
+ class Hash
119
+ # TODO: Possibly deprecated, delete if not used.
120
+ def rfm_only(*keepers)
121
+ self.each_key {|k| self.delete(k) if !keepers.include?(k)}
122
+ end
123
+
124
+ def rfm_filter(*args)
125
+ options = args.rfm_extract_options!
126
+ delete = options[:delete]
127
+ self.each_key do |k|
128
+ self.delete(k) if (delete ? args.include?(k) : !args.include?(k))
129
+ end
130
+ end
131
+ end # Hash
132
+
67
133
  # Allows access to superclass object
68
134
  class SuperProxy
69
135
  def initialize(obj)
@@ -14,9 +14,14 @@ module Rfm
14
14
 
15
15
  class ServerFactory < Rfm::CaseInsensitiveHash
16
16
 
17
- def [](host, conf = Factory.get_config) #(Factory.instance_variable_get(:@config) || {}))
18
- conf[:host] = host
19
- super(host) or (self[host] = Rfm::Server.new(conf.reject{|k,v| [:account_name, :password].include? k}))
17
+ def [](*args)
18
+ options = Factory.get_config(*args)
19
+ host = options[:strings].delete_at(0) || options[:host]
20
+ super(host) or (self[host] = Rfm::Server.new(host, options.rfm_filter(:account_name, :password, :delete=>true)))
21
+ # This part reconfigures the named server, if you pass it new config in the [] method.
22
+ # This breaks some specs in all [] methods in Factory. Consider undoing this. See readme-dev.
23
+ # super(host).config(options) if (options)
24
+ # super(host)
20
25
  end
21
26
 
22
27
  # Potential refactor
@@ -38,18 +43,22 @@ module Rfm
38
43
  @loaded = false
39
44
  end
40
45
 
41
- def [](dbname, acnt=nil, pass=nil) #
42
- db = (super(dbname) or (self[dbname] = Rfm::Database.new(dbname, @server)))
43
- account_name = acnt || db.account_name || @server.state[:account_name]
44
- password = pass || db.password || @server.state[:password]
45
- db.account_name = account_name if account_name
46
- db.password = password if password
47
- db
46
+ def [](*args)
47
+ # was: (dbname, acnt=nil, pass=nil)
48
+ options = Factory.get_config(*args)
49
+ name = options[:strings].delete_at(0) || options[:database]
50
+ account_name = options[:strings].delete_at(0) || options[:account_name]
51
+ password = options[:strings].delete_at(0) || options[:password]
52
+ super(name) or (self[name] = Rfm::Database.new(name, account_name, password, @server))
53
+ # This part reconfigures the named database, if you pass it new config in the [] method.
54
+ # super(name).config({:account_name=>account_name, :password=>password}.merge(options)) if (account_name or password or options)
55
+ # super(name)
48
56
  end
49
57
 
50
58
  def all
51
59
  if !@loaded
52
- Rfm::Resultset.new(@server, @server.connect(@server.state[:account_name], @server.state[:password], '-dbnames', {}).body, nil).each {|record|
60
+ xml = @server.connect(@server.state[:account_name], @server.state[:password], '-dbnames', {}).body
61
+ Rfm::Resultset.new(xml, :server_object => @server).each {|record|
53
62
  name = record['DATABASE_NAME']
54
63
  self[name] = Rfm::Database.new(name, @server) if self.keys.find{|k| k.to_s.downcase == name.to_s.downcase} == nil
55
64
  }
@@ -74,8 +83,13 @@ module Rfm
74
83
  @loaded = false
75
84
  end
76
85
 
77
- def [](layout_name)
78
- super or (self[layout_name] = Rfm::Layout.new(layout_name, @database))
86
+ def [](*args) # was layout_name
87
+ options = Factory.get_config(*args)
88
+ name = options[:strings].delete_at(0) || options[:layout]
89
+ super(name) or (self[name] = Rfm::Layout.new(name, @database, options))
90
+ # This part reconfigures the named layout, if you pass it new config in the [] method.
91
+ # super(name).config({:layout=>name}.merge(options)) if options
92
+ # super(name)
79
93
  end
80
94
 
81
95
  def all
@@ -98,7 +112,8 @@ module Rfm
98
112
  end
99
113
 
100
114
  def get_layout_names
101
- Rfm::Resultset.new(@server, get_layout_names_xml.body, nil)
115
+ #Rfm::Resultset.new(@server, get_layout_names_xml.body, nil)
116
+ Rfm::Resultset.new(get_layout_names_xml.body, :database_object => @database)
102
117
  end
103
118
 
104
119
  def names
@@ -122,7 +137,8 @@ module Rfm
122
137
 
123
138
  def all
124
139
  if !@loaded
125
- Rfm::Resultset.new(@server, @server.connect(@database.account_name, @database.password, '-scriptnames', {"-db" => @database.name}).body, nil).each {|record|
140
+ xml = @server.connect(@database.account_name, @database.password, '-scriptnames', {"-db" => @database.name}).body
141
+ Rfm::Resultset.new(xml, :database_object => @database).each {|record|
126
142
  name = record['SCRIPT_NAME']
127
143
  self[name] = Rfm::Metadata::Script.new(name, @database) if self[name] == nil
128
144
  }
@@ -148,21 +164,22 @@ module Rfm
148
164
  # Returns Rfm::Server instance, given config hash or array
149
165
  def server(*conf)
150
166
  options = get_config(*conf)
151
- server_name = options[:strings][0] || options[:host]
152
- raise Rfm::Error::RfmError.new(0, 'A host name is needed to create a server object.') if server_name.blank?
153
- server = servers[server_name, options]
167
+ #server = servers[server_name, options]
168
+ # These disconnect servers from the ServerFactory hash, but it breaks the ActiveModel spec suite.
169
+ #Rfm::Server.new(server_name, options.rfm_filter(:account_name, :password, :delete=>true))
170
+ Rfm::Server.new(options)
154
171
  end
155
172
  # Potential refactor
156
173
  #def_delegator 'Rfm::Factory::ServerFactory', :[], :server #, :[]
157
174
 
158
175
  # Returns Rfm::Db instance, given config hash or array
159
176
  def db(*conf)
160
- options = get_config(*conf)
161
- db_name = options[:strings][0] || options[:database]
162
- raise Rfm::Error::RfmError.new(0, 'A database name is needed to create a database object.') if db_name.blank?
163
- account_name = options[:strings][1] || options[:account_name]
164
- password = options[:strings][2] || options[:password]
165
- db = server(options)[db_name, account_name, password]
177
+ options = get_config(*conf)
178
+ name = options[:strings].delete_at(0) || options[:database]
179
+ account_name = options[:strings].delete_at(0) || options[:account_name]
180
+ password = options[:strings].delete_at(0) || options[:password]
181
+ s = server(options)
182
+ s[name, account_name, password, options]
166
183
  end
167
184
 
168
185
  alias_method :database, :db
@@ -170,9 +187,9 @@ module Rfm
170
187
  # Returns Rfm::Layout instance, given config hash or array
171
188
  def layout(*conf)
172
189
  options = get_config(*conf)
173
- layout_name = options[:strings][0] || options[:layout]
174
- raise Rfm::Error::RfmError.new(0, 'A layout name is needed to create a layout object.') if layout_name.blank?
175
- layout = db(options)[layout_name]
190
+ name = options[:strings].delete_at(0) || options[:layout]
191
+ d = db(options)
192
+ d[name, options]
176
193
  end
177
194
 
178
195
  end # class << self