lardawge-rfm 1.4.1.2 → 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ *.sw?
2
+ .DS_Store
3
+ .document
4
+ local_testing
5
+ coverage
6
+ rdoc
7
+ pkg
8
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
7
+ - 2.0.0
8
+ - ree
@@ -0,0 +1,37 @@
1
+ ## 1.4.2
2
+
3
+ ### enhancements
4
+ * Make nil default on fields with no value.
5
+
6
+ ```ruby
7
+ record.john #=> ""
8
+ record.john #=> nil
9
+ ```
10
+
11
+ ## 1.4.1.2
12
+
13
+ ### bug fixes
14
+ * Pointing out why testing is soooooo important when refactoring... Found a bug in getter/setter method in Rfm::Record
15
+
16
+ ## 1.4.1.1
17
+
18
+ ### bug fixes
19
+ * Inadvertently left out an attr_reader for server from resultset effecting container urls.
20
+
21
+ ## 1.4.1
22
+
23
+ ### enhancements
24
+ * Changed Server#do_action to Server#connect.
25
+ * XML Parsing is now done via xpath which significantly speeds up parsing.
26
+ * Changes to accessor method names for Resultset#portals Resultset#fields to Resultset#portal_meta and Resultset#field_meta to better describe what you get back.
27
+ * Added an option to load portal records which defaults to false. This significantly speeds up load time when portals are present on the layout.
28
+
29
+ ```ruby
30
+ # This will fetch all records with portal records attached.
31
+ result = fm_server('layout').find({:username => "==#{username}"}, {:include_portals => true})
32
+
33
+ result.first.portals # return an empty hash if incude_portals is not true
34
+ ```
35
+
36
+ * Internal file restructuring. Some classes have changed but it should be nothing a developer would use API wise. Please let me know if it is.
37
+ * Removed Layout#value_lists && Layout#field_controls. Will put back in if the demand is high. Needs a major refactor and different placement if it goes back in. Was broken so it didn't seem to be used by many devs.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in lardawge-rfm.gemspec
4
+ gemspec
@@ -1,211 +1,259 @@
1
- = Rfm
1
+ # Rfm
2
2
 
3
- This version of rfm is a significant upgrade to rfm in a few ways. First and most important (to me anyway) is it allows for the use of secure connections and certificate files.
4
- Rfm 1.0.0 is broken in that way and does not allow for this. Second it has a few bug fixes that were never pushed to rfm 1.0.0. Last but not least is that the xml parser is
5
- switched to nokogiri which is a huge speed improvement over REXML (used in rfm 1.0.0) on large datasets.
3
+ [![Build Status](https://travis-ci.org/lardawge/rfm.png?branch=master)](https://travis-ci.org/lardawge/rfm)
4
+ [![Code Quality](https://codeclimate.com/badge.png)](https://codeclimate.com/github/lardawge/rfm)
5
+ [![Still Maintained](http://stillmaintained.com/lardawge/rfm.png)](http://stillmaintained.com/lardawge/rfm)
6
6
 
7
- Rfm was primarily designed by Six Fried Rice co-founder Geoff Coffey.
7
+ ## Installation
8
8
 
9
- Other lead contributors:
9
+ Terminal:
10
10
 
11
- - Mufaddal Khumri helped architect Rfm in the most ruby-like way possible. He also contributed the outstanding error handling code and a comprehensive hierarchy of error classes.
12
- - Atsushi Matsuo was an early Rfm tester, and provided outstanding feedback, critical code fixes, and a lot of web exposure.
13
- - Jesse Antunes helped ensure that Rfm is stable and functional.
14
- - Larry Sprock added ssl support and switched the xml parser to a much faster Nokogiri.
11
+ ```bash
12
+ gem install lardawge-rfm
13
+ ```
15
14
 
16
- Rdoc location: http://rdoc.info/projects/lardawge/rfm
17
-
18
- == Installation
19
-
20
- Run the following if you haven't already:
21
-
22
- gem sources -a http://gemcutter.org
23
-
24
- Followed by:
15
+ Once the gem is installed, you can use rfm in your ruby scripts by requiring it:
25
16
 
26
- gem install lardawge-rfm
17
+ ```ruby
18
+ require 'rubygems'
19
+ require 'rfm'
20
+ ```
21
+ ### In Rails >= 3.0
27
22
 
28
- Once the gem is installed, you can use rfm in your ruby scripts by requiring it:
23
+ In the Gemfile:
29
24
 
30
- require 'rubygems'
31
- require 'rfm'
25
+ ```ruby
26
+ gem 'lardawge-rfm'
27
+ ```
32
28
 
33
- == Connecting
29
+ ## Connecting
34
30
 
35
31
  IMPORTANT:SSL and Certificate verification are on by default. Please see Server#new in rdocs for explanation and setup.
36
32
  You connect with the Rfm::Server object. This little buddy will be your window into FileMaker data.
37
33
 
38
- require 'rfm/rfm'
34
+ ```ruby
35
+ require 'rfm/rfm'
39
36
 
40
- my_server = Rfm::Server.new(
41
- :host => 'myservername',
42
- :username => 'user',
43
- :password => 'pw',
44
- :ssl => false
45
- )
37
+ my_server = Rfm::Server.new(
38
+ :host => 'myservername',
39
+ :username => 'user',
40
+ :password => 'pw',
41
+ :ssl => false
42
+ )
43
+ ```
46
44
 
47
45
  if your web publishing engine runs on a port other than 80, you can provide the port number as well:
48
46
 
49
- my_server = Rfm::Server.new(
50
- :host => 'myservername',
51
- :username => 'user',
52
- :password => 'pw',
53
- :port => 8080,
54
- :ssl => false
55
- )
47
+ ```ruby
48
+ my_server = Rfm::Server.new(
49
+ :host => 'myservername',
50
+ :username => 'user',
51
+ :password => 'pw',
52
+ :port => 8080,
53
+ :ssl => false
54
+ )
55
+ ```
56
56
 
57
- == Databases and Layouts
57
+ ## Databases and Layouts
58
58
 
59
59
  All access to data in FileMaker's XML interface is done through layouts, and layouts live in databases. The Rfm::Server object has a collection of databases called 'db'. So to get ahold of a database called "My Database", you can do this:
60
60
 
61
- my_db = my_server.db["My Database"]
61
+ ```ruby
62
+ my_db = my_server.db["My Database"]
63
+ ```
62
64
 
63
65
  As a convenience, you can do this too:
64
66
 
65
- my_db = my_server["My Database"]
67
+ ```ruby
68
+ my_db = my_server["My Database"]
69
+ ```
66
70
 
67
71
  Finally, if you want to introspect the server and find out what databases are available, you can do this:
68
72
 
69
- all_dbs = my_server.db.all
73
+ ```ruby
74
+ all_dbs = my_server.db.all
75
+ ```
70
76
 
71
77
  In any case, you get back Rfm::Database objects. A database object in turn has a property called "layout":
72
78
 
73
- my_layout = my_db.layout["My Layout"]
79
+ ```ruby
80
+ my_layout = my_db.layout["My Layout"]
81
+ ```
74
82
 
75
83
  Again, for convenience:
76
84
 
77
- my_layout = my_db["My Layout"]
85
+ ```ruby
86
+ my_layout = my_db["My Layout"]
87
+ ```
78
88
 
79
89
  And to get them all:
80
90
 
81
- all_layouts = my_db.layout.all
91
+ ```ruby
92
+ all_layouts = my_db.layout.all
93
+ ```
82
94
 
83
95
  Bringing it all together, you can do this to go straight from a server to a specific layout:
84
96
 
85
- my_layout = my_server["My Database"]["My Layout"]
97
+ ```ruby
98
+ my_layout = my_server["My Database"]["My Layout"]
99
+ ```
86
100
 
87
- NOTE: for the ruby experts, Rfm::Server#db is an Rfm::Factory::DatabaseFactory object, which is just a fancied up Hash. So anything you can do with a hash, you can also do with my_server.db. The same goes for my_db.layouts, which is an Rfm::Factory::LayoutFactory.
88
-
89
-
90
- == Working with Layouts
101
+ ## Working with Layouts
91
102
 
92
103
  Once you have a layout object, you can start doing some real work. To get every record from the layout:
93
104
 
94
- my_layout.all # be careful with this
105
+ ```ruby
106
+ my_layout.all # be careful with this
107
+ ```
95
108
 
96
109
  To get a random record:
97
110
 
98
- my_layout.any
111
+ ```ruby
112
+ my_layout.any
113
+ ```
99
114
 
100
115
  To find every record with "Arizona" in the "State" field:
101
116
 
102
- my_layout.find({"State" => "Arizona"})
117
+ ```ruby
118
+ my_layout.find({"State" => "Arizona"})
119
+ ```
103
120
 
104
121
  To add a new record with my personal info:
105
122
 
106
- my_layout.create({
107
- :first_name => "Geoff",
108
- :last_name => "Coffey",
109
- :email => "gwcoffey@gmail.com"}
110
- )
123
+ ```ruby
124
+ my_layout.create({
125
+ :first_name => "Geoff",
126
+ :last_name => "Coffey",
127
+ :email => "gwcoffey@gmail.com"}
128
+ )
129
+ ```
111
130
 
112
131
  Notice that in this case I used symbols instead of strings for the hash keys. The API will accept either form, so if your field names don't have whitespace or punctuation, you might prefer the symbol notation.
113
132
 
114
133
  To edit the record whos recid (filemaker internal record id) is 200:
115
134
 
116
- my_layout.edit(200, {:first_name => 'Mamie'})
135
+ ```ruby
136
+ my_layout.edit(200, {:first_name => 'Mamie'})
137
+ ```
117
138
 
118
139
  Note: See the "Record Objects" section below for more on editing records.
119
140
 
120
141
  To delete the record whose recid is 200:
121
142
 
122
- my_layout.delete(200)
143
+ ```ruby
144
+ my_layout.delete(200)
145
+ ```
123
146
 
124
147
  All of these methods return an Rfm::Result::ResultSet object (see below), and every one of them takes an optional parameter (the very last one) with additional options. For example, to find just a page full of records, you can do this:
125
148
 
126
- my_layout.find({:state => "AZ"}, {:max_records => 10, :skip_records => 100})
149
+ ```ruby
150
+ my_layout.find({:state => "AZ"}, {:max_records => 10, :skip_records => 100})
151
+ ```
127
152
 
128
153
  For a complete list of the available options, see the "expand_options" method in the Rfm::Server object in the file named rfm_command.rb.
129
154
 
130
155
  Finally, if filemaker returns an error when executing any of these methods, an error will be raised in your ruby script. There is one exception to this, though. If a find results in no records being found (FileMaker error # 401) I just ignore it and return you a ResultSet with zero records in it. If you prefer an error in this case, add :raise_on_401 => true to the options you pass the Rfm::Server when you create it.
131
156
 
132
157
 
133
- == ResultSet and Record Objects
158
+ ## ResultSet and Record Objects
134
159
 
135
160
  Any method on the Layout object that returns data will return a ResultSet object. Rfm::Result::ResultSet is a subclass of Array, so first and foremost, you can use it like any other array:
136
161
 
137
- my_result = my_layout.any
138
- my_result.size # returns '1'
139
- my_result[0] # returns the first record (an Rfm::Result::Record object)
162
+ ```ruby
163
+ my_result = my_layout.any
164
+ my_result.size # returns '1'
165
+ my_result[0] # returns the first record (an Rfm::Result::Record object)
166
+ ```
140
167
 
141
168
  The ResultSet object also tells you information about the fields and portals in the result. ResultSet#fields and ResultSet#portals are both standard ruby hashes, with strings for keys. The fields hash has Rfm::Result::Field objects for values. The portals hash has another hash for its values. This nested hash is the fields on the portal. This would print out all the field names:
142
169
 
143
- my_result.fields.each { |name, field| puts name }
170
+ ```ruby
171
+ my_result.fields.each { |name, field| puts name }
172
+ ```
144
173
 
145
174
  This would print out the tables each portal on the layout is associated with. Below each table name, and indented, it will print the names of all the fields on each portal.
146
175
 
147
- my_result.portals.each { |table, fields|
148
- puts "table: #{table}"
149
- fields.each { |name, field| puts "\t#{name}"}
150
- }
176
+ ```ruby
177
+ my_result.portals.each { |table, fields|
178
+ puts "table: #{table}"
179
+ fields.each { |name, field| puts "\t#{name}"}
180
+ }
181
+ ```
151
182
 
152
183
  But most importantly, the ResultSet contains record objects. Rfm::Result::Record is a subclass of Hash, so it can be used in many standard ways. This code would print the value in the 'first_name' field in the first record of the ResultSet:
153
184
 
154
- my_record = my_result[0]
155
- puts my_record["first_name"]
185
+ ```ruby
186
+ my_record = my_result[0]
187
+ puts my_record["first_name"]
188
+ ```
156
189
 
157
190
  As a convenience, if your field names are valid ruby method names (ie, they don't have spaces or odd punctuation in them), you can do this instead:
158
191
 
159
- puts my_record.first_name
192
+ ```ruby
193
+ puts my_record.first_name
194
+ ```
160
195
 
161
196
  Since ResultSets are arrays and Records are hashes, you can take advantage of Ruby's wonderful expressiveness. For example, to get a comma-separated list of the full names of all the people in California, you could do this:
162
197
 
163
- my_layout.find({:state => 'CA'}).collect {|rec| "#{rec.first_name} #{rec.last_name}"}.join(", ")
198
+ ```ruby
199
+ my_layout.find(:state => 'CA').collect {|rec| "#{rec.first_name} #{rec.last_name}"}.join(", ")
200
+ ```
164
201
 
165
202
  Record objects can also be edited:
166
203
 
167
- my_record.first_name = 'Isabel'
204
+ ```ruby
205
+ my_record.first_name = 'Isabel'
206
+ ```
168
207
 
169
208
  Once you have made a series of edits, you can save them back to the database like this:
170
209
 
171
- my_record.save
210
+ ```ruby
211
+ my_record.save
212
+ ```
172
213
 
173
214
  The save operation causes the record to be reloaded from the database, so any changes that have been made outside your script will also be picked up after the save.
174
215
 
175
216
  If you want to detect concurrent modification, you can do this instead:
176
217
 
177
- my_record.save_if_not_modified
218
+ ```ruby
219
+ my_record.save_if_not_modified
220
+ ```
178
221
 
179
222
  This version will refuse to update the database and raise an error if the record was modified after it was loaded but before it was saved.
180
223
 
181
224
  Record objects also have portals. While the portals in a ResultSet tell you about the tables and fields the portals show, the portals in a Record have the actual data. For example, if an Order record has Line Item records, you could do this:
182
225
 
183
- my_order = order_layout.any[0] # the [0] is important!
184
- my_lines = my_order.portals["Line Items"]
226
+ ```ruby
227
+ my_order = order_layout.any[0] # the [0] is important!
228
+ my_lines = my_order.portals["Line Items"]
229
+ ```
185
230
 
186
231
  At the end of the previous block of code, my_lines is an array of Record objects. In this case, they are the records in the "Line Items" portal for the particular order record. You can then operate on them as you would any other record.
187
232
 
188
233
  NOTE: Fields on a portal have the table name and the "::" stripped off of their names if they belong to the table the portal is tied to. In other words, if our "Line Items" portal includes a quantity field and a price field, you would do this:
189
234
 
190
- my_lines[0]["Quantity"]
191
- my_lines[0]["Price"]
235
+ ```ruby
236
+ my_lines[0]["Quantity"]
237
+ my_lines[0]["Price"]
238
+ ```
192
239
 
193
240
  You would NOT do this:
194
241
 
195
- my_lines[0]["Line Items::Quantity"]
196
- my_lines[0]["Line Items::Quantity"]
242
+ ```ruby
243
+ my_lines[0]["Line Items::Quantity"]
244
+ my_lines[0]["Line Items::Quantity"]
245
+ ```
197
246
 
198
247
  My feeling is that the table name is redundant and cumbersome if it is the same as the portal's table. This is also up for debate.
199
248
 
200
249
  Again, you can string things together with Ruby. This will calculate the total dollar amount of the order:
201
-
202
- total = 0.0
203
- my_order.portals["Line Items"].each {|line| total += line.quantity * line.price}
204
250
 
205
- I intend to add a 'delete' method to the Record class as well, but I haven't done this yet. Finally, should you be able to create new records using the Record class? I'm not sure...lots of points to consider on this.
251
+ ```ruby
252
+ total = 0.0
253
+ my_order.portals["Line Items"].each {|line| total += line.quantity * line.price}
254
+ ```
206
255
 
207
-
208
- == Data Types
256
+ ## Data Types
209
257
 
210
258
  FileMaker's field types are coerced to Ruby types thusly:
211
259
 
@@ -228,7 +276,7 @@ Finally, container fields will come back as URI objects. You can:
228
276
 
229
277
  Specifically, the URI refers to the _contents_ of the container field. When accessed, the file, picture, or movie in the field will be downloaded.
230
278
 
231
- == Troubleshooting
279
+ ## Troubleshooting
232
280
 
233
281
  There are two cheesy methods to help track down problems. When you create a server object, you can provide two additional optional parameters:
234
282
 
@@ -240,16 +288,16 @@ When this is 'true' your script will dump the actual response it got from FileMa
240
288
 
241
289
  So, for an annoying, but detailed load of output, make a connection like this:
242
290
 
243
- my_server = Rfm::Server.new(
244
- :host => 'myservername',
245
- :username => 'user',
246
- :password => 'pw',
247
- :log_actions => true,
248
- :log_responses => true
249
- )
250
-
251
- These options will change in the future. They're only there now as a quick-easy way for me to track down problems. I'm open to suggestions for what kind of login options will make sense in the final release.
291
+ ```ruby
292
+ my_server # Rfm::Server.new(
293
+ :host #> 'myservername',
294
+ :username #> 'user',
295
+ :password #> 'pw',
296
+ :log_actions #> true,
297
+ :log_responses #> true
298
+ )
299
+ ```
252
300
 
253
- == Copyright
301
+ ## Copyright
254
302
 
255
- Copyright (c) 2007 Six Fried Rice, LLC and Mufaddal Khumr. See LICENSE for details.
303
+ Copyright (c) 2007 Six Fried Rice, LLC and Mufaddal Khumr. See LICENSE for details.
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rspec/core/rake_task'
6
+ desc "Run all examples"
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"]
9
+ t.pattern = 'spec/**/*_spec.rb'
10
+ end
11
+
12
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "lardawge-rfm"
7
+ s.version = Rfm::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Geoff Coffey", "Mufaddal Khumri", "Atsushi Matsuo", "Larry Sprock"]
10
+ s.email = ["larry@lucidbleu.com"]
11
+ s.homepage = "https://github.com/lardawge/rfm"
12
+ s.licenses = ["MIT"]
13
+ s.summary = %q{Ruby to Filemaker adapter}
14
+ s.description = %q{Rfm brings your FileMaker data to Ruby. Now your Ruby scripts and Rails applications can talk directly to your FileMaker server.}
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency "nokogiri"
22
+ s.add_dependency "addressable"
23
+
24
+ s.add_development_dependency "rspec", ["~> 2.12.0"]
25
+ s.add_development_dependency "mocha", ["~> 0.13.0"]
26
+ s.add_development_dependency "rake"
27
+
28
+ end
29
+
data/lib/rfm.rb CHANGED
@@ -1,8 +1,11 @@
1
- path = File.expand_path(File.dirname(__FILE__))
2
- $:.unshift(path) unless $:.include?(path)
3
-
4
- require path + '/rfm/utilities/case_insensitive_hash'
5
- require path + '/rfm/utilities/factory'
1
+ # encoding: utf-8
2
+ require 'rfm/utilities/case_insensitive_hash'
3
+ require 'rfm/utilities/factory'
4
+ require 'rfm/error'
5
+ require 'rfm/server'
6
+ require 'rfm/database'
7
+ require 'rfm/layout'
8
+ require 'rfm/resultset'
6
9
 
7
10
  module Rfm
8
11
 
@@ -10,10 +13,4 @@ module Rfm
10
13
  class ParameterError < StandardError; end
11
14
  class AuthenticationError < StandardError; end
12
15
 
13
- autoload :Error, 'rfm/error'
14
- autoload :Server, 'rfm/server'
15
- autoload :Database, 'rfm/database'
16
- autoload :Layout, 'rfm/layout'
17
- autoload :Resultset, 'rfm/resultset'
18
-
19
- end
16
+ end
@@ -59,6 +59,9 @@ module Rfm
59
59
  # * *state* is a hash of all server options used to initialize this server
60
60
  class Database
61
61
 
62
+ attr_reader :server, :name, :account_name, :password, :layout, :script
63
+ attr_writer :account_name, :password
64
+
62
65
  # Initialize a database object. You never really need to do this. Instead, just do this:
63
66
  #
64
67
  # myServer = Rfm::Server.new(...)
@@ -74,9 +77,6 @@ module Rfm
74
77
  @script = Rfm::Factory::ScriptFactory.new(server, self)
75
78
  end
76
79
 
77
- attr_reader :server, :name, :account_name, :password, :layout, :script
78
- attr_writer :account_name, :password
79
-
80
80
  # Access the Layout object representing a layout in this database. For example:
81
81
  #
82
82
  # myDatabase['Details']
@@ -93,4 +93,4 @@ module Rfm
93
93
  end
94
94
 
95
95
  end
96
- end
96
+ end
@@ -120,6 +120,8 @@ module Rfm
120
120
 
121
121
  class Layout
122
122
 
123
+ attr_reader :name, :db
124
+
123
125
  # Initialize a layout object. You never really need to do this. Instead, just do this:
124
126
  #
125
127
  # myServer = Rfm::Server.new(...)
@@ -138,8 +140,6 @@ module Rfm
138
140
  @db = db
139
141
  end
140
142
 
141
- attr_reader :name, :db
142
-
143
143
  # Returns a ResultSet object containing _every record_ in the table associated with this layout.
144
144
  def all(options = {})
145
145
  get_records('-findall', {}, options)
@@ -209,14 +209,14 @@ module Rfm
209
209
 
210
210
  private
211
211
 
212
- def get_records(action, extra_params = {}, options = {})
213
- include_portals = options[:include_portals] ? options.delete(:include_portals) : nil
214
- xml_response = @db.server.connect(@db.account_name, @db.password, action, params.merge(extra_params), options).body
215
- Rfm::Resultset.new(@db.server, xml_response, self, include_portals)
212
+ def get_records(action, params = {}, options = {})
213
+ include_portals = options.delete(:include_portals)
214
+ xml_response = db.server.connect(action, default_params.merge(params), options)
215
+ Rfm::Resultset.new(db.server, xml_response.body, self, include_portals)
216
216
  end
217
217
 
218
- def params
219
- {"-db" => @db.name, "-lay" => self.name}
218
+ def default_params
219
+ {"-db" => db.name, "-lay" => self.name}
220
220
  end
221
221
  end
222
- end
222
+ end
@@ -45,7 +45,7 @@ module Rfm
45
45
  #
46
46
  # * *control& is a FieldControl object representing the sytle and value list information associated
47
47
  # with this field on the layout.
48
- #
48
+ #
49
49
  # Note: Since a field can sometimes appear on a layout more than once, +control+ may be an Array.
50
50
  # If you don't know ahead of time, you'll need to deal with this. One easy way is:
51
51
  #
@@ -56,11 +56,11 @@ module Rfm
56
56
  #
57
57
  # The code above makes sure the control is always an array. Typically, though, you'll know up front
58
58
  # if the control is an array or not, and you can code accordingly.
59
-
59
+
60
60
  class Field
61
-
61
+
62
62
  attr_reader :name, :result, :type, :max_repeats, :global
63
-
63
+
64
64
  # Initializes a field object. You'll never need to do this. Instead, get your Field objects from
65
65
  # ResultSet::fields
66
66
  def initialize(field)
@@ -70,8 +70,8 @@ module Rfm
70
70
  @max_repeats = field['max-repeats']
71
71
  @global = field['global']
72
72
  end
73
-
74
- # Coerces the text value from an +fmresultset+ document into proper Ruby types based on the
73
+
74
+ # Coerces the text value from an +fmresultset+ document into proper Ruby types based on the
75
75
  # type of the field. You'll never need to do this: Rfm does it automatically for you when you
76
76
  # access field data through the Record object.
77
77
  def coerce(value, resultset)
@@ -82,12 +82,12 @@ module Rfm
82
82
  when "date" then Date.strptime(value, resultset.date_format)
83
83
  when "time" then DateTime.strptime("1/1/-4712 #{value}", "%m/%d/%Y #{resultset.time_format}")
84
84
  when "timestamp" then DateTime.strptime(value, resultset.timestamp_format)
85
- when "container" then URI.parse("#{resultset.server.scheme}://#{resultset.server.host_name}:#{resultset.server.port}#{value}")
85
+ when "container" then URI.parse("#{resultset.server.uri.scheme}://#{resultset.server.uri.host}:#{resultset.server.uri.port}#{value}")
86
86
  else nil
87
87
  end
88
-
88
+
89
89
  end
90
-
90
+
91
91
  end
92
92
  end
93
- end
93
+ end
@@ -191,26 +191,38 @@ module Rfm
191
191
  # Record::save or Record::save_if_not_modified to actually save the data.
192
192
  def []=(name, value)
193
193
  return super unless @loaded
194
- raise Rfm::ParameterError, "You attempted to modify a field does not exist." unless self[name]
194
+ raise Rfm::ParameterError,
195
+ "You attempted to modify the field :#{name} which does not exist in the current Filemaker layout." unless self.key?(name)
195
196
  @mods[name] = value
196
197
  end
197
-
198
+
199
+ alias :_original_hash_reader :[]
200
+ def [](value)
201
+ read_attribute(value)
202
+ end
203
+
198
204
  def respond_to?(symbol, include_private = false)
199
- return true if self[symbol.to_s]
205
+ return true if self.include?(symbol.to_s)
200
206
  super
201
207
  end
202
-
208
+
203
209
  private
204
-
205
- def method_missing (symbol, *attrs, &block)
210
+
211
+ def read_attribute(key)
212
+ raise NoMethodError,
213
+ "#{key} does not exist as a field in the current Filemaker layout." unless key?(key)
214
+ _original_hash_reader(key)
215
+ end
216
+
217
+ def method_missing(symbol, *attrs, &block)
206
218
  method = symbol.to_s
207
- return self[method] if self.include?(method)
219
+ return read_attribute(method) if self.key?(method)
208
220
 
209
- if method =~ /(=)$/ && self.has_key?($`)
221
+ if method =~ /(=)$/ && self.key?($`)
210
222
  return @mods[$`] = attrs.first
211
223
  end
212
224
  super
213
225
  end
214
226
 
215
227
  end
216
- end
228
+ end
@@ -1,5 +1,6 @@
1
1
  require 'net/https'
2
- require 'cgi'
2
+ require 'addressable/uri'
3
+
3
4
  module Rfm
4
5
  # This class represents a single FileMaker server. It is initialized with basic
5
6
  # connection information, including the hostname, port number, and default database
@@ -107,7 +108,9 @@ module Rfm
107
108
  # * *name* is the name of this database
108
109
  # * *state* is a hash of all server options used to initialize this server
109
110
  class Server
110
- #
111
+
112
+ attr_reader :db, :state, :uri
113
+
111
114
  # To create a Server object, you typically need at least a host name:
112
115
  #
113
116
  # myServer = Rfm::Server.new({:host => 'my.host.com'})
@@ -192,7 +195,6 @@ module Rfm
192
195
  # :root_cert_name => 'example.pem'
193
196
  # :root_cert_path => '/usr/cert_file/'
194
197
  # })
195
-
196
198
  def initialize(options)
197
199
  @state = {
198
200
  :host => 'localhost',
@@ -203,21 +205,16 @@ module Rfm
203
205
  :root_cert_path => '/',
204
206
  :account_name => '',
205
207
  :password => '',
206
- :log_actions => false,
207
- :log_responses => false,
208
+ :log_actions => nil,
209
+ :log_responses => nil,
208
210
  :warn_on_redirect => true,
209
- :raise_on_401 => false
211
+ :raise_on_401 => nil
210
212
  }.merge(options)
211
-
212
- @state.freeze
213
-
214
- @host_name = @state[:host]
215
- @scheme = @state[:ssl] ? "https" : "http"
216
- @port = @state[:ssl] && options[:port].nil? ? 443 : @state[:port]
217
-
218
- @db = Rfm::Factory::DbFactory.new(self)
213
+
214
+ @uri = Addressable::URI.parse("#{scheme}://#{state[:host]}:#{port}")
215
+ @db = Rfm::Factory::DbFactory.new(self)
219
216
  end
220
-
217
+
221
218
  # Access the database object representing a database on the server. For example:
222
219
  #
223
220
  # myServer['Customers']
@@ -233,9 +230,7 @@ module Rfm
233
230
  def [](dbname)
234
231
  self.db[dbname]
235
232
  end
236
-
237
- attr_reader :db, :host_name, :port, :scheme, :state
238
-
233
+
239
234
  # Performs a raw FileMaker action. You will generally not call this method directly, but it
240
235
  # is exposed in case you need to do something "under the hood."
241
236
  #
@@ -263,70 +258,78 @@ module Rfm
263
258
  # },
264
259
  # { :max_records => 20 }
265
260
  # )
266
- def connect(account_name, password, action, args, options = {})
261
+ def connect(action, args, options = {})
267
262
  post = args.merge(expand_options(options)).merge({action => ''})
268
- http_fetch(@host_name, @port, "/fmi/xml/fmresultset.xml", account_name, password, post)
263
+ http_fetch("/fmi/xml/fmresultset.xml", post)
269
264
  end
270
-
265
+
271
266
  def load_layout(layout)
272
267
  post = {'-db' => layout.db.name, '-lay' => layout.name, '-view' => ''}
273
- http_fetch(@host_name, @port, "/fmi/xml/FMPXMLLAYOUT.xml", layout.db.account_name, layout.db.password, post)
268
+ http_fetch("/fmi/xml/FMPXMLLAYOUT.xml", post)
274
269
  end
275
-
270
+
276
271
  private
277
-
278
- def http_fetch(host_name, port, path, account_name, password, post_data, limit=10)
272
+
273
+ def http_fetch(path, post_data, limit=10)
279
274
  raise Rfm::CommunicationError.new("While trying to reach the Web Publishing Engine, RFM was redirected too many times.") if limit == 0
280
275
 
281
- if @state[:log_actions] == true
282
- qs = post_data.collect{|key,val| "#{CGI::escape(key.to_s)}=#{CGI::escape(val.to_s)}"}.join("&")
283
- warn "#{@scheme}://#{@host_name}:#{@port}#{path}?#{qs}"
284
- end
276
+ uri.path = path
277
+ uri.query_values = post_data
278
+ warn uri.to_s if state[:log_actions]
285
279
 
286
- request = Net::HTTP::Post.new(path)
287
- request.basic_auth(account_name, password)
280
+ request = Net::HTTP::Post.new(uri.path)
281
+ request.basic_auth(state[:account_name], state[:password])
288
282
  request.set_form_data(post_data)
289
283
 
290
- response = Net::HTTP.new(host_name, port)
284
+ response = Net::HTTP.new(uri.host, uri.port)
291
285
 
292
- if @state[:ssl]
286
+ if state[:ssl]
293
287
  response.use_ssl = true
294
- if @state[:root_cert]
288
+ if state[:root_cert]
295
289
  response.verify_mode = OpenSSL::SSL::VERIFY_PEER
296
- response.ca_file = File.join(@state[:root_cert_path], @state[:root_cert_name])
290
+ response.ca_file = File.join(state[:root_cert_path], state[:root_cert_name])
297
291
  else
298
292
  response.verify_mode = OpenSSL::SSL::VERIFY_NONE
299
293
  end
300
294
  end
301
295
 
302
296
  response = response.start { |http| http.request(request) }
303
- if @state[:log_responses] == true
297
+ if state[:log_responses]
304
298
  response.to_hash.each { |key, value| warn "#{key}: #{value}" }
305
299
  warn response.body
306
300
  end
307
-
301
+
302
+ parse_response(response, limit)
303
+ end
304
+
305
+ def parse_response(response, limit)
308
306
  case response
309
307
  when Net::HTTPSuccess
310
308
  response
311
309
  when Net::HTTPRedirection
312
- if @state[:warn_on_redirect]
313
- warn "The web server redirected to " + response['location'] +
314
- ". You should revise your connection hostname or fix your server configuration if possible to improve performance."
310
+ if state[:warn_on_redirect]
311
+ warn "The web server redirected to #{response['location']}.
312
+ You should revise your connection hostname or fix your server configuration if possible to improve performance."
315
313
  end
316
- newloc = URI.parse(response['location'])
317
- http_fetch(newloc.host, newloc.port, newloc.request_uri, account_name, password, post_data, limit - 1)
314
+
315
+ newloc = Addressable::URI.parse(response['location'])
316
+ uri.host = newloc.host
317
+ uri.port = newloc.port
318
+ http_fetch(newloc.path, newloc.query_values, limit-1)
318
319
  when Net::HTTPUnauthorized
319
- msg = "The account name (#{account_name}) or password provided is not correct (or the account doesn't have the fmxml extended privilege)."
320
+ msg = "The account name (#{state[:account_name]}) or password provided is not correct
321
+ (or the account doesn't have the fmxml extended privilege)."
320
322
  raise Rfm::AuthenticationError.new(msg)
321
323
  when Net::HTTPNotFound
322
324
  msg = "Could not talk to FileMaker because the Web Publishing Engine is not responding (server returned 404)."
323
325
  raise Rfm::CommunicationError.new(msg)
324
326
  else
325
- msg = "Unexpected response from server: #{response.code} (#{response.class.to_s}). Unable to communicate with the Web Publishing Engine."
327
+ msg = "Unexpected response from server: #{response.code} (#{response.class.to_s}).
328
+ Unable to communicate with the Web Publishing Engine."
326
329
  raise Rfm::CommunicationError.new(msg)
327
330
  end
328
331
  end
329
-
332
+
330
333
  def expand_options(options)
331
334
  result = {}
332
335
  options.each do |key,value|
@@ -382,6 +385,22 @@ module Rfm
382
385
  end
383
386
  return result
384
387
  end
388
+
389
+ def scheme
390
+ if state[:ssl]
391
+ "https"
392
+ else
393
+ "http"
394
+ end
395
+ end
396
+
397
+ def port
398
+ if state[:ssl] && state[:port] == 80
399
+ 443
400
+ else
401
+ state[:port]
402
+ end
403
+ end
385
404
 
386
405
  end
387
- end
406
+ end
@@ -20,7 +20,7 @@ module Rfm
20
20
 
21
21
  def all
22
22
  if !@loaded
23
- Rfm::Result::ResultSet.new(@server, @server.connect(@server.state[:account_name], @server.state[:password], '-dbnames', {}).body).each {|record|
23
+ Rfm::Result::ResultSet.new(@server, @server.connect('-dbnames', {}).body).each {|record|
24
24
  name = record['DATABASE_NAME']
25
25
  self[name] = Rfm::Database.new(name, @server) if self[name] == nil
26
26
  }
@@ -45,7 +45,7 @@ module Rfm
45
45
 
46
46
  def all
47
47
  if !@loaded
48
- Rfm::Result::ResultSet.new(@server, @server.connect(@server.state[:account_name], @server.state[:password], '-layoutnames', {"-db" => @database.name}).body).each {|record|
48
+ Rfm::Result::ResultSet.new(@server, @server.connect('-layoutnames', {"-db" => @database.name}).body).each {|record|
49
49
  name = record['LAYOUT_NAME']
50
50
  self[name] = Rfm::Layout.new(name, @database) if self[name] == nil
51
51
  }
@@ -70,7 +70,7 @@ module Rfm
70
70
 
71
71
  def all
72
72
  if !@loaded
73
- Rfm::Result::ResultSet.new(@server, @server.connect(@server.state[:account_name], @server.state[:password], '-scriptnames', {"-db" => @database.name}).body).each {|record|
73
+ Rfm::Result::ResultSet.new(@server, @server.connect('-scriptnames', {"-db" => @database.name}).body).each {|record|
74
74
  name = record['SCRIPT_NAME']
75
75
  self[name] = Rfm::Script.new(name, @database) if self[name] == nil
76
76
  }
@@ -81,4 +81,4 @@ module Rfm
81
81
 
82
82
  end
83
83
  end
84
- end
84
+ end
@@ -0,0 +1,3 @@
1
+ module Rfm
2
+ VERSION = '1.4.2'
3
+ end
@@ -24,12 +24,20 @@ describe Rfm::Record do
24
24
  @record.instance_variable_get(:@mods)['tester'].should eql('green')
25
25
  end
26
26
 
27
- it "raises an Rfm::ParameterError if a key is used that does not exist" do
27
+ it "raises an Rfm::ParameterError if a value is set on a key that does not exist" do
28
28
  @record.instance_variable_set(:@loaded, true)
29
-
29
+
30
30
  ex = rescue_from { @record['tester2'] = 'error' }
31
31
  ex.class.should eql(Rfm::ParameterError)
32
- ex.message.should eql('You attempted to modify a field does not exist.')
32
+ ex.message.should eql('You attempted to modify the field :tester2 which does not exist in the current Filemaker layout.')
33
+ end
34
+
35
+ it "raises an NoMethodError if a key is used that does not exist" do
36
+ @record.instance_variable_set(:@loaded, true)
37
+
38
+ ex = rescue_from { @record['tester2'] }
39
+ ex.class.should eql(NoMethodError)
40
+ ex.message.should eql('tester2 does not exist as a field in the current Filemaker layout.')
33
41
  end
34
42
 
35
43
  end
@@ -80,4 +88,4 @@ describe Rfm::Record do
80
88
  end
81
89
 
82
90
  end
83
- end
91
+ end
@@ -1,10 +1,10 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'logger'
3
4
  require 'rfm'
4
- require 'spec'
5
- require 'spec/autorun'
6
5
 
7
- Spec::Runner.configure do |config|
6
+ RSpec.configure do |c|
7
+ c.mock_with :mocha
8
8
  end
9
9
 
10
10
  def rescue_from(&block)
metadata CHANGED
@@ -1,16 +1,10 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: lardawge-rfm
3
- version: !ruby/object:Gem::Version
4
- hash: 127
5
- prerelease: false
6
- segments:
7
- - 1
8
- - 4
9
- - 1
10
- - 2
11
- version: 1.4.1.2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.4.2
5
+ prerelease:
12
6
  platform: ruby
13
- authors:
7
+ authors:
14
8
  - Geoff Coffey
15
9
  - Mufaddal Khumri
16
10
  - Atsushi Matsuo
@@ -18,34 +12,105 @@ authors:
18
12
  autorequire:
19
13
  bindir: bin
20
14
  cert_chain: []
21
-
22
- date: 2010-07-09 00:00:00 -07:00
23
- default_executable:
24
- dependencies:
25
- - !ruby/object:Gem::Dependency
15
+ date: 2013-06-02 00:00:00.000000000 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
26
18
  name: nokogiri
19
+ requirement: !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ! '>='
23
+ - !ruby/object:Gem::Version
24
+ version: '0'
25
+ type: :runtime
27
26
  prerelease: false
28
- requirement: &id001 !ruby/object:Gem::Requirement
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: addressable
35
+ requirement: !ruby/object:Gem::Requirement
29
36
  none: false
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- hash: 3
34
- segments:
35
- - 0
36
- version: "0"
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
37
41
  type: :runtime
38
- version_requirements: *id001
39
- description: Rfm brings your FileMaker data to Ruby. Now your Ruby scripts and Rails applications can talk directly to your FileMaker server.
40
- email: http://groups.google.com/group/rfmcommunity
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ requirement: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: 2.12.0
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ version: 2.12.0
65
+ - !ruby/object:Gem::Dependency
66
+ name: mocha
67
+ requirement: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ~>
71
+ - !ruby/object:Gem::Version
72
+ version: 0.13.0
73
+ type: :development
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ~>
79
+ - !ruby/object:Gem::Version
80
+ version: 0.13.0
81
+ - !ruby/object:Gem::Dependency
82
+ name: rake
83
+ requirement: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Rfm brings your FileMaker data to Ruby. Now your Ruby scripts and Rails
98
+ applications can talk directly to your FileMaker server.
99
+ email:
100
+ - larry@lucidbleu.com
41
101
  executables: []
42
-
43
102
  extensions: []
44
-
45
- extra_rdoc_files:
103
+ extra_rdoc_files: []
104
+ files:
105
+ - .gitignore
106
+ - .rspec
107
+ - .travis.yml
108
+ - CHANGELOG.md
109
+ - Gemfile
46
110
  - LICENSE
47
- - README.rdoc
48
- files:
111
+ - README.md
112
+ - Rakefile
113
+ - lardawge-rfm.gemspec
49
114
  - lib/rfm.rb
50
115
  - lib/rfm/database.rb
51
116
  - lib/rfm/error.rb
@@ -57,48 +122,37 @@ files:
57
122
  - lib/rfm/server.rb
58
123
  - lib/rfm/utilities/case_insensitive_hash.rb
59
124
  - lib/rfm/utilities/factory.rb
60
- - LICENSE
61
- - README.rdoc
125
+ - lib/version.rb
62
126
  - spec/rfm/error_spec.rb
63
127
  - spec/rfm/record_spec.rb
64
128
  - spec/spec_helper.rb
65
- has_rdoc: true
66
- homepage: http://sixfriedrice.com/wp/products/rfm/
67
- licenses: []
68
-
129
+ homepage: https://github.com/lardawge/rfm
130
+ licenses:
131
+ - MIT
69
132
  post_install_message:
70
- rdoc_options:
71
- - --line-numbers
72
- - --main
73
- - README.rdoc
74
- require_paths:
133
+ rdoc_options: []
134
+ require_paths:
75
135
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
136
+ required_ruby_version: !ruby/object:Gem::Requirement
77
137
  none: false
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 3
82
- segments:
83
- - 0
84
- version: "0"
85
- required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
143
  none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- hash: 3
91
- segments:
92
- - 0
93
- version: "0"
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
94
148
  requirements: []
95
-
96
149
  rubyforge_project:
97
- rubygems_version: 1.3.7
150
+ rubygems_version: 1.8.24
98
151
  signing_key:
99
152
  specification_version: 3
100
153
  summary: Ruby to Filemaker adapter
101
- test_files:
154
+ test_files:
102
155
  - spec/rfm/error_spec.rb
103
156
  - spec/rfm/record_spec.rb
104
157
  - spec/spec_helper.rb
158
+ has_rdoc: