ActiveExcel 0.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.
- data/History.txt +7 -0
- data/Manifest.txt +7 -0
- data/README.txt +48 -0
- data/Rakefile +30 -0
- data/bin/active_excel +3 -0
- data/lib/active_excel.rb +432 -0
- data/test/test_active_excel.rb +180 -0
- metadata +73 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= active_excel
|
2
|
+
|
3
|
+
* FIX (url)
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
FIX (describe your package)
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* FIX (list of features or problems)
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
FIX (code sample of usage)
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* FIX (list of requirements)
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* FIX (sudo gem install, anything else)
|
24
|
+
|
25
|
+
== LICENSE:
|
26
|
+
|
27
|
+
(The MIT License)
|
28
|
+
|
29
|
+
Copyright (c) 2009 FIX
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
32
|
+
a copy of this software and associated documentation files (the
|
33
|
+
'Software'), to deal in the Software without restriction, including
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
37
|
+
the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be
|
40
|
+
included in all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
$:.unshift(File.dirname(__FILE__) + "/lib")
|
6
|
+
require 'active_excel'
|
7
|
+
|
8
|
+
Hoe.spec 'active_excel' do |p|
|
9
|
+
#self.rubyforge_name = 'active_excelx' # if different than 'active_excel'
|
10
|
+
#p.developer('FIX', 'FIX@example.com')
|
11
|
+
#Hoe.new('ActiveExcel', ActiveExcel::VERSION) do |p|
|
12
|
+
p.name = "ActiveExcel"
|
13
|
+
p.author = "Rob Whitener"
|
14
|
+
p.description = "Excel as the container of model objects"
|
15
|
+
p.email = 'robwhitener@gmail.com'
|
16
|
+
p.summary = <<EXCEL
|
17
|
+
Use Excel to contain model objects. Each row in a sheet represents an object, each sheet in a workbook
|
18
|
+
represents a class. Make sure that you rename your sheets - this gem uses ActiveSupport to take advantage
|
19
|
+
of the pluralization familiar to Rails developers. Modeled liberally on ActiveRecord, not quite as cool but
|
20
|
+
hopefully useful enough. Cheers.
|
21
|
+
EXCEL
|
22
|
+
p.url = "http://robsexocortex.blogspot.com/2009/06/activeexcel.html"
|
23
|
+
p.clean_globs = ['test/actual'] # Remove this directory on "rake clean"
|
24
|
+
p.remote_rdoc_dir = '' # Release to root
|
25
|
+
#p.changes = p.paragraphs_of('CHANGELOG', 0..1).join("\n\n")
|
26
|
+
# * extra_deps - An array of rubygem dependencies.
|
27
|
+
#end
|
28
|
+
end
|
29
|
+
|
30
|
+
# vim: syntax=ruby
|
data/bin/active_excel
ADDED
data/lib/active_excel.rb
ADDED
@@ -0,0 +1,432 @@
|
|
1
|
+
require 'win32ole'
|
2
|
+
require 'activesupport'
|
3
|
+
|
4
|
+
class ExcelConstants
|
5
|
+
XLBLACK = 1
|
6
|
+
XLRED = 3
|
7
|
+
XLGREEN = 4
|
8
|
+
XLBLUE = 5
|
9
|
+
XLYELLOW = 6
|
10
|
+
XLMAGENTA = 7
|
11
|
+
XLCYAN = 8
|
12
|
+
XLBROWN = 9
|
13
|
+
XLOLIVE = 10
|
14
|
+
XLROYAL = 11
|
15
|
+
XLVOMIT = 12
|
16
|
+
XLPURPLE = 13
|
17
|
+
XLNOCOLOR = -4142
|
18
|
+
end
|
19
|
+
|
20
|
+
module ActiveExcel
|
21
|
+
VERSION = '0.0.1'
|
22
|
+
#This class conatins the main Excel functionality that will be inherited
|
23
|
+
#by objects which intend to wrap data in the spreadsheet
|
24
|
+
class Root
|
25
|
+
attr_accessor :workbook, :workbook_name, :id, :fields, :dirty, :is_new, :this_workbook
|
26
|
+
@@excel = nil
|
27
|
+
@@sheets = nil
|
28
|
+
@@workbook = nil
|
29
|
+
@@sheet_name = nil
|
30
|
+
|
31
|
+
def initialize()
|
32
|
+
#this will be called by the new method and will be called for
|
33
|
+
#each instance of any subclass that is created.
|
34
|
+
#The initialize routine will handel adding attributes to the
|
35
|
+
#new instance based on the fieldnames from the spreadsheet.
|
36
|
+
#If Find was used to get an object out of the sheet, then the
|
37
|
+
#attributes will be populated. Each instance will also have
|
38
|
+
#an id field that corresponds to its row number in the spreadsheet
|
39
|
+
@fields = nil
|
40
|
+
@this_workbook = @@workbook
|
41
|
+
add_attributes
|
42
|
+
@id = -1 #the id can have a default value of -1, since there is no -1 row in a sheet
|
43
|
+
@dirty = false
|
44
|
+
@is_new = true
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self ###Class methods
|
49
|
+
|
50
|
+
#Config is a hash that expects the following parameters
|
51
|
+
# :wkbook -> The name of the workbook to open
|
52
|
+
# :path -> pathe where the workbook can be found if the workbook isn't in the my docs folder
|
53
|
+
def workbook_connect(config=nil)
|
54
|
+
@workbook_name = config[:wkbook]
|
55
|
+
if config[:path]
|
56
|
+
#there was a path specified, prepend that to the workbook name
|
57
|
+
@workbook_name = config[:path] + "/" + @workbook_name
|
58
|
+
puts "Fully Qualified Workbook Path:>> #{@workbook_name}"
|
59
|
+
else
|
60
|
+
#otherwise, figure out where MyDocuments is
|
61
|
+
@workbook_name = ENV['USERPROFILE'] + "\\" + "My Documents" + "\\" + @workbook_name
|
62
|
+
end
|
63
|
+
#create the Excel application
|
64
|
+
|
65
|
+
begin
|
66
|
+
@@excel = WIN32OLE::connect('Excel.Application') if !@@excel
|
67
|
+
rescue
|
68
|
+
@@excel = WIN32OLE::new('Excel.Application')
|
69
|
+
end
|
70
|
+
|
71
|
+
#we now have a connection to a workbook.
|
72
|
+
#first, check to see if the desired workbook is open, if it is put that in @@workbook
|
73
|
+
if @@excel.Workbooks.Count > 0 then
|
74
|
+
#there are workbooks open, grab the one we want by name
|
75
|
+
@@workbook = nil
|
76
|
+
for wkbk in @@excel.Workbooks
|
77
|
+
if wkbk.Name == config[:wkbook] then
|
78
|
+
@@workbook = wkbk
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
#at this point if @@workbook is nil, then go ahead and open it.
|
85
|
+
if @@workbook == nil then
|
86
|
+
@@workbook = @@excel.Workbooks.Open(@workbook_name)
|
87
|
+
end
|
88
|
+
@@excel.Visible = 1
|
89
|
+
#get all the sheet names
|
90
|
+
@@sheets ||= {}
|
91
|
+
i = 1
|
92
|
+
for worksheet in @@workbook.Worksheets
|
93
|
+
@@sheets[i] = worksheet.Name
|
94
|
+
i += 1
|
95
|
+
end
|
96
|
+
@@workbook
|
97
|
+
end
|
98
|
+
|
99
|
+
def sheet_names
|
100
|
+
@@sheets
|
101
|
+
end
|
102
|
+
|
103
|
+
def load_constants
|
104
|
+
begin
|
105
|
+
ExcelConstants::XLDown #try to access a constant,
|
106
|
+
rescue
|
107
|
+
#if an error is thrown, load them.
|
108
|
+
WIN32OLE.const_load(@@excel, ExcelConstants) #load Excel constants into the module.
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def connected?
|
113
|
+
return @@excel ? true : false
|
114
|
+
end
|
115
|
+
|
116
|
+
def workbook_disconnect
|
117
|
+
#this method will save any changes to the workbook and then close it down.
|
118
|
+
@@workbook.Save if @@workbook
|
119
|
+
@@workbook.Close if @@workbook
|
120
|
+
@@workbook = nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def excel_disconnect
|
124
|
+
@@excel.Quit if @@excel
|
125
|
+
@@excel = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
#Define a sheet name to use, in case the naming convention doesn't work for
|
129
|
+
#particular situations
|
130
|
+
def set_sheet_name(sht)
|
131
|
+
#Add the sheetname as a class variable
|
132
|
+
class_eval{ class_variable_set(:"@@sheet_name",sht.to_s)}
|
133
|
+
end
|
134
|
+
|
135
|
+
#This method will find one or more records in the spreadsheet and
|
136
|
+
#return them as ActiveExcel objects.
|
137
|
+
def find(rec_no)
|
138
|
+
|
139
|
+
if rec_no.is_a? Integer
|
140
|
+
#if its just an integer, then look up by id
|
141
|
+
return find_by_id(rec_no)
|
142
|
+
elsif rec_no.is_a? Hash
|
143
|
+
return nil
|
144
|
+
elsif rec_no.is_a? Symbol
|
145
|
+
case rec_no
|
146
|
+
when :all then return find_all
|
147
|
+
when :first then find_first
|
148
|
+
when :last then find_last
|
149
|
+
end
|
150
|
+
end
|
151
|
+
set_sheet_name(self.class.to_s.tableize)
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
def find_by_id(i)
|
156
|
+
return nil if i < 2 #The rowid has to be 2 or greater
|
157
|
+
o = new #create a new object, this will also set the workseet
|
158
|
+
o.this_workbook = @@workbook.clone
|
159
|
+
@wksheet = get_the_worksheet
|
160
|
+
|
161
|
+
#iterate over the list of field names
|
162
|
+
@columns.each_pair do |k,v|
|
163
|
+
#Access the Cell by passing in the Letter cooresponding to the field name and the
|
164
|
+
#Row index (1 based)
|
165
|
+
value = @wksheet.Range("#{v}#{i}").Value
|
166
|
+
o.send(:"#{k.to_s}=",value)
|
167
|
+
|
168
|
+
end
|
169
|
+
o.dirty = false
|
170
|
+
o.is_new = false
|
171
|
+
o.id=i
|
172
|
+
o #return the new object
|
173
|
+
end
|
174
|
+
|
175
|
+
def find_all
|
176
|
+
#basically, grab all the records except the header, roll them into ActiveExcel objects
|
177
|
+
obj_array = Array.new #make a new array to hold the objects
|
178
|
+
#figure out the current region size
|
179
|
+
current_region = get_the_current_region
|
180
|
+
2.upto(current_region.Rows.Count) do |i|
|
181
|
+
o = new
|
182
|
+
o.this_workbook = @@workbook.clone
|
183
|
+
1.upto(current_region.Columns.Count) do |j|
|
184
|
+
field = get_the_field_name_for_index(j-1) #the letters array is 0 based, but excel is 1 based.
|
185
|
+
o.send(:"#{field}=",@wksheet.Cells(i,j).Value.to_s)
|
186
|
+
o.is_new = false
|
187
|
+
o.dirty = false
|
188
|
+
o.id = i
|
189
|
+
end
|
190
|
+
obj_array << o
|
191
|
+
end
|
192
|
+
obj_array
|
193
|
+
end
|
194
|
+
|
195
|
+
#Returns a list of column names. If the columns hash hasn't been created, it will create the
|
196
|
+
#columns hash by scanning the first row of the CurrentRegion (Which is defined as a region on the
|
197
|
+
#active worksheet bounded by blank cells).
|
198
|
+
def column_names(sheet = nil)
|
199
|
+
if !@columns
|
200
|
+
if sheet
|
201
|
+
#if a sheet name was passed in we can just use that to get the column names
|
202
|
+
@wksheet = @@workbook.Worksheets(sheet)
|
203
|
+
else
|
204
|
+
#Test to see if a sheet_name class variable has been set
|
205
|
+
#if it has, then use that value other wise use the class name
|
206
|
+
classvar = self.class_eval{class_variable_get(:"@@sheet_name")}
|
207
|
+
classname = classvar ? classvar : self.class.to_s.tableize
|
208
|
+
puts "Tableized: #{classname}"
|
209
|
+
@wksheet = @@workbook.Worksheets(classname)
|
210
|
+
end
|
211
|
+
#now, grab the collumn names and put them into a hash name=>col_letter
|
212
|
+
row = @wksheet.Cells.CurrentRegion.Rows(1)
|
213
|
+
@columns = {}
|
214
|
+
@letters = []
|
215
|
+
col_name = "A"
|
216
|
+
row.Cells.each do |c|
|
217
|
+
#c represents a Cell in the worksheet
|
218
|
+
@columns[c.Value.to_sym] = col_name.clone
|
219
|
+
@letters << col_name.clone
|
220
|
+
col_name.succ! #advance the col_name one letter
|
221
|
+
end
|
222
|
+
end
|
223
|
+
#return a list of the keys
|
224
|
+
keys = []
|
225
|
+
@columns.each_key {|k| keys << k}
|
226
|
+
keys
|
227
|
+
end
|
228
|
+
|
229
|
+
def columns
|
230
|
+
return @columns
|
231
|
+
end
|
232
|
+
|
233
|
+
def wksheet
|
234
|
+
return @wksheet
|
235
|
+
end
|
236
|
+
|
237
|
+
#This implementation of method_missing will provide methods that will allow records
|
238
|
+
#to be looked up using find_by_<field_name>(value). For simplicity sake, this first
|
239
|
+
#iteration will only allow the search of individual fields.
|
240
|
+
def method_missing(methodid,*args,&blk)
|
241
|
+
#make sure the columns hash has been built
|
242
|
+
puts("METHOD ID --> #{methodid.to_s}")
|
243
|
+
column_names if !@columns
|
244
|
+
|
245
|
+
#if the words 'find_ and 'by' are not part of method id, then get out.
|
246
|
+
return if methodid.to_s !~ /find_.*by/
|
247
|
+
|
248
|
+
field = methodid.to_s.split("by_").last
|
249
|
+
letter = @columns[field.to_sym] #grab the column letter
|
250
|
+
col_index = @letters.index(letter) + 1 #get the column index, add 1 because Excel is one based.
|
251
|
+
|
252
|
+
if methodid.to_s.index("find_all_by")
|
253
|
+
return find_all_by_attribute(field,col_index,args[0])
|
254
|
+
else
|
255
|
+
return find_by_attribute(field,col_index,args[0])
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
#this method is caled from within method missing to return all objects
|
260
|
+
#that can be found which contain a particular value for a particular field
|
261
|
+
def find_all_by_attribute(field,col_index,args)
|
262
|
+
lookup_records(field,col_index,args,false)
|
263
|
+
end
|
264
|
+
|
265
|
+
#This method is called by method_missing to return the first object in the sheet which
|
266
|
+
#contains a particular value for a particular field.
|
267
|
+
def find_by_attribute(field,col_index,args)
|
268
|
+
lookup_records(field,col_index,args)
|
269
|
+
end
|
270
|
+
|
271
|
+
def lookup_records(field,col_index,args,first=true)
|
272
|
+
i = 1 #keep track of the row number with this variable
|
273
|
+
@wksheet = get_the_worksheet
|
274
|
+
current_region = get_the_current_region
|
275
|
+
records = Array.new
|
276
|
+
current_region.each do |range|
|
277
|
+
if @wksheet.Cells(i,col_index).Value == args then
|
278
|
+
#the value of the cell in the range matches the value passed into the
|
279
|
+
#missing method. Create a new object and return it
|
280
|
+
o = new
|
281
|
+
o.this_workbook = @@workbook.clone
|
282
|
+
1.upto(current_region.Columns.Count) do |j|
|
283
|
+
#get the column letter
|
284
|
+
l = @letters[j-1]
|
285
|
+
#from the hash, go backward from the column letter to the field name
|
286
|
+
field = @columns.index(l)
|
287
|
+
o.send(:"#{field.to_s}=",@wksheet.Cells(i,j).Value.to_s)
|
288
|
+
end
|
289
|
+
o.id = i
|
290
|
+
o.dirty = false
|
291
|
+
o.is_new = false
|
292
|
+
return o if first
|
293
|
+
records << o
|
294
|
+
break if i == current_region.Rows.Count
|
295
|
+
end
|
296
|
+
i+=1
|
297
|
+
end
|
298
|
+
records
|
299
|
+
end
|
300
|
+
|
301
|
+
#Get the CurrentRegion of the spreadsheet as a standard range
|
302
|
+
def get_the_current_region
|
303
|
+
@wksheet = get_the_worksheet
|
304
|
+
curr_reg_addr = @wksheet.Cells.CurrentRegion.address.to_s.gsub(/\$/,'') #remove $ from address
|
305
|
+
current_region = @wksheet.Range(curr_reg_addr)
|
306
|
+
end
|
307
|
+
|
308
|
+
#returns the field name for a given column letter
|
309
|
+
def get_the_field_name_for_index(index)
|
310
|
+
l = @letters[index]
|
311
|
+
@columns.index(l)
|
312
|
+
end
|
313
|
+
|
314
|
+
def get_the_worksheet
|
315
|
+
puts("-----------------------------")
|
316
|
+
puts("\tself.name.tableize: #{self.name.tableize}")
|
317
|
+
puts("\tsheet_name: #{@@sheet_name}")
|
318
|
+
@@sheet_name = self.name.tableize if !@@sheet_name
|
319
|
+
puts("\tThe SheetName being used is: #{@@sheet_name}\n")
|
320
|
+
puts("-----------------------------")
|
321
|
+
@@workbook.Worksheets(@@sheet_name)
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
end ###End class methods####
|
326
|
+
|
327
|
+
#allows the workbook that an individual object comes from to be stored in the object.
|
328
|
+
#def this_workbook=(wkbk)
|
329
|
+
# @this_workbook = wkbk
|
330
|
+
#end
|
331
|
+
#
|
332
|
+
#def this_workbook
|
333
|
+
# @this_workbook
|
334
|
+
#end
|
335
|
+
|
336
|
+
#Define the attributes of the instance based on what is in the
|
337
|
+
#spreadheet
|
338
|
+
def add_attributes
|
339
|
+
#we should be able to use the column_names here
|
340
|
+
if !@@sheet_name then
|
341
|
+
@fields = self.class.column_names(self.class.to_s.tableize)
|
342
|
+
else
|
343
|
+
@fields = self.class.column_names()
|
344
|
+
end
|
345
|
+
#add a getter/setter pair for each column name
|
346
|
+
@fields.each do |c|
|
347
|
+
#add the setter method
|
348
|
+
self.class_eval do
|
349
|
+
define_method(:"#{c.to_s}=") do |v|
|
350
|
+
instance_variable_set(:"@#{c.to_s}",v)
|
351
|
+
instance_variable_set(:"@dirty",true)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
#add the getter method
|
356
|
+
self.class_eval do
|
357
|
+
define_method(c) do
|
358
|
+
instance_variable_get(:"@#{c.to_s}")
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
#This method will save the object back into the spreadsheet. THIS DOES NOT SAVE
|
366
|
+
#CHANGES TO THE ACTUAL SPREADSHEET THAT IS IN MEMORY.
|
367
|
+
#
|
368
|
+
def save
|
369
|
+
update_or_create
|
370
|
+
end
|
371
|
+
|
372
|
+
#Returns whether or not this is a dirty object or not
|
373
|
+
def dirty?
|
374
|
+
dirty
|
375
|
+
end
|
376
|
+
|
377
|
+
def color_cell(name,const)
|
378
|
+
#first, get the cell name
|
379
|
+
letter = self.class.columns[name.to_sym]
|
380
|
+
#get the sheet name, if we don't already have it
|
381
|
+
@wksheet = @@workbook.worksheets(self.class.to_s.tableize) if !@wksheet
|
382
|
+
#color the cell
|
383
|
+
@wksheet.Range("#{letter}#{self.id}").Interior.ColorIndex = const
|
384
|
+
end
|
385
|
+
|
386
|
+
def get_cell_color(name)
|
387
|
+
#get the cell name
|
388
|
+
letter = self.class.columns[name.to_sym]
|
389
|
+
@wksheet = @@workbook.worksheets(self.class.to_s.tableize) if !@wksheet
|
390
|
+
#get the cell color
|
391
|
+
@wksheet.Range("#{letter}#{self.id}").Interior.ColorIndex
|
392
|
+
end
|
393
|
+
|
394
|
+
private
|
395
|
+
#This method will determine whether to update an existing record
|
396
|
+
#or create a brand new one
|
397
|
+
def update_or_create
|
398
|
+
res = (is_new == true) ? create : update
|
399
|
+
res != false
|
400
|
+
end
|
401
|
+
|
402
|
+
#This method will create a brand new row in the sheet
|
403
|
+
def create
|
404
|
+
#determine the last row in the table
|
405
|
+
row = self.class.wksheet.Cells.CurrentRegion.Rows.Count
|
406
|
+
row += 1 #add 1 to the row to point to the next empty row
|
407
|
+
self.class.columns.each_pair do |k,v|
|
408
|
+
#k: will be the field name
|
409
|
+
#v: will be the column letter
|
410
|
+
self.class.wksheet.Range("#{v}#{row}").Value = self.send(k)
|
411
|
+
|
412
|
+
end
|
413
|
+
self.dirty = false
|
414
|
+
self.is_new = false
|
415
|
+
self.id = row
|
416
|
+
end
|
417
|
+
|
418
|
+
#this method will save the existing record
|
419
|
+
def update
|
420
|
+
self.class.columns.each_pair do |k,v|
|
421
|
+
#k: will be the field name
|
422
|
+
#v: will be the column letter
|
423
|
+
self.class.wksheet.Range("#{v}#{self.id}").Value = self.send(k)
|
424
|
+
end
|
425
|
+
self.dirty = false
|
426
|
+
end
|
427
|
+
|
428
|
+
|
429
|
+
|
430
|
+
end #End of Root class
|
431
|
+
|
432
|
+
end #End of Module
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require File.dirname(__FILE__)+ "/../lib/active_excel"
|
3
|
+
|
4
|
+
#this is a subclass of ActiveExcel::Root, it will be used
|
5
|
+
#to help verify inherited behaviors and connections
|
6
|
+
class OtherRecord < ActiveExcel::Root
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
class NoSheet < ActiveExcel::Root
|
11
|
+
set_sheet_name :other_records
|
12
|
+
end
|
13
|
+
|
14
|
+
class X12CarbonCopyTest < ActiveExcel::Root
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class TestActiveExcel < Test::Unit::TestCase
|
19
|
+
|
20
|
+
def setup
|
21
|
+
@wkbk = ActiveExcel::Root.workbook_connect({:wkbook=>"gxstestsubs_billing.xls"})
|
22
|
+
#ActiveExcel::Root.load_constants if !@const_loaded
|
23
|
+
#@const_loaded = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def teardown
|
27
|
+
puts "Closing down excel....."
|
28
|
+
ActiveExcel::Root.workbook_disconnect
|
29
|
+
ActiveExcel::Root.excel_disconnect
|
30
|
+
@wkbk = nil
|
31
|
+
GC.start
|
32
|
+
sleep(2)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_connect_to_workbook
|
36
|
+
#this test verifies that a workbook can be connected to by passing in a hash
|
37
|
+
#config = {:wkbook=>"gxstestsubs_billing.xls"}
|
38
|
+
#ActiveExcel::Root.workbook_connect(config)
|
39
|
+
assert_not_nil @wkbk
|
40
|
+
assert_equal 3,@wkbk.Worksheets.Count
|
41
|
+
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
#def test_connect_to_two_different_workbooks
|
46
|
+
# #this test verifies that ActiveExcel won't crash if two Excel workbooks are open.
|
47
|
+
# #the first one will be opened by the set up method, and the second will be a connection
|
48
|
+
# #to SmallCCTests_FEReq_FEPart_FERecip.xls
|
49
|
+
#
|
50
|
+
# #first, grab an OtherRecord object, this will store the first workbook in the object:
|
51
|
+
# orec = OtherRecord.find(3)
|
52
|
+
# #now, open up the second one.
|
53
|
+
# ActiveExcel::Root.workbook_connect({:wkbook=>"SmallCCTests_FEReq_FEPart_FERecip.xls"})
|
54
|
+
# ActiveExcel::Root.set_sheet_name :x12_carbon_copy_tests
|
55
|
+
# x12 = X12CarbonCopyTest.find(2)
|
56
|
+
# assert_not_equal(orec.this_workbook, x12.this_workbook )
|
57
|
+
# ActiveExcel::Root.set_sheet_name = nil
|
58
|
+
#end
|
59
|
+
|
60
|
+
def test_column_names
|
61
|
+
#ae = ActiveExcel::Root.new
|
62
|
+
h = ActiveExcel::Root.column_names("other_records")
|
63
|
+
0.upto(3) do |i|
|
64
|
+
assert_equal true,h.include?(:"Field#{i+1}")
|
65
|
+
end
|
66
|
+
assert_equal "A",ActiveExcel::Root.columns[:Field1]
|
67
|
+
assert_equal "B",ActiveExcel::Root.columns[:Field2]
|
68
|
+
assert_equal "C",ActiveExcel::Root.columns[:Field3]
|
69
|
+
assert_equal "D",ActiveExcel::Root.columns[:Field4]
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_using_a_subclass
|
73
|
+
oe = OtherRecord.new
|
74
|
+
h = oe.class.column_names
|
75
|
+
0.upto(3) do |i|
|
76
|
+
assert_equal true,h.include?(:"Field#{i+1}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_specifying_a_sheet_name
|
81
|
+
|
82
|
+
ns = NoSheet.new
|
83
|
+
#assert_equal "other_records",NoSheet.sheet_name
|
84
|
+
h = ns.class.column_names
|
85
|
+
0.upto(3) do |i|
|
86
|
+
assert_equal true,h.include?(:"Field#{i+1}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_verify_that_attr_added_dynamically
|
91
|
+
orec = OtherRecord.new
|
92
|
+
orec.Field1 = "A new value"
|
93
|
+
orec.Field4 = "BLAH BLAH"
|
94
|
+
assert_equal "A new value",orec.Field1
|
95
|
+
assert_equal -1,orec.id
|
96
|
+
assert_equal nil,orec.Field2
|
97
|
+
assert_equal nil,orec.Field3
|
98
|
+
assert_equal "BLAH BLAH",orec.Field4
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_find_a_record_with_a_specific_id
|
102
|
+
|
103
|
+
#simply passing in an integer will cause it to find by id
|
104
|
+
orec = OtherRecord.find(3)
|
105
|
+
assert_equal "Value2",orec.Field1
|
106
|
+
assert_equal "Value10",orec.Field2
|
107
|
+
assert_equal "Value18",orec.Field3
|
108
|
+
assert_equal "Value26",orec.Field4
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_find_by_attr_name
|
112
|
+
orec = OtherRecord.find_by_Field2("Value11")
|
113
|
+
assert_equal 4,orec.id
|
114
|
+
assert_equal "Value3",orec.Field1
|
115
|
+
assert_equal "Value11",orec.Field2
|
116
|
+
assert_equal "Value19",orec.Field3
|
117
|
+
assert_equal "Value27",orec.Field4
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_find_all_by_attr_name
|
121
|
+
orecs = OtherRecord.find_all_by_Field1("Value8")
|
122
|
+
assert_equal 2,orecs.size
|
123
|
+
orec1 = orecs[0]
|
124
|
+
orec2 = orecs[1]
|
125
|
+
assert_equal "Value8",orec1.Field1
|
126
|
+
assert_equal "Value16",orec1.Field2
|
127
|
+
assert_equal "Value8", orec2.Field1
|
128
|
+
assert_equal "Value160",orec2.Field2
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_find_by_attr_name_with_underscore
|
132
|
+
orec = OtherRecord.find_by_Field_Five("Value35")
|
133
|
+
assert_equal "Value3",orec.Field1
|
134
|
+
assert_equal "Value11",orec.Field2
|
135
|
+
assert_equal "Value19",orec.Field3
|
136
|
+
assert_equal "Value27",orec.Field4
|
137
|
+
assert_equal "Value35",orec.Field_Five
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_save_an_edited_record
|
141
|
+
orec = OtherRecord.find_by_Field3("Value23")
|
142
|
+
orec.Field1="UPDATED"
|
143
|
+
orec.Field2="RECORD"
|
144
|
+
orec.save
|
145
|
+
|
146
|
+
teardown()
|
147
|
+
#sleep(5) #go to sleep for 5 seconds so I can watch the taskmanager
|
148
|
+
|
149
|
+
#open it back up now
|
150
|
+
ActiveExcel::Root.workbook_connect({:wkbook=>"gxstestsubs_billing.xls"})
|
151
|
+
|
152
|
+
uprec = OtherRecord.find_by_Field3("Value23")
|
153
|
+
assert_equal "UPDATED",uprec.Field1
|
154
|
+
assert_equal "RECORD",uprec.Field2
|
155
|
+
|
156
|
+
#change them again and ensure that the object has been marked dirty.
|
157
|
+
uprec.Field1 = "Value7"
|
158
|
+
uprec.Field2 = "Value15"
|
159
|
+
|
160
|
+
assert_equal true,uprec.dirty?
|
161
|
+
uprec.save
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_find_all_records_in_sheet
|
165
|
+
recs = OtherRecord.find(:all)
|
166
|
+
assert_equal 9,recs.size
|
167
|
+
0.upto(7) do |i|
|
168
|
+
assert_equal "OtherRecord",recs[i].class.to_s
|
169
|
+
assert_equal "Value#{i+1}",recs[i].send(:"Field1")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_colorize_cell
|
174
|
+
#this verifies that we can color a cell using an excel constant
|
175
|
+
oe = OtherRecord.find(4)
|
176
|
+
oe.color_cell("Field1",ExcelConstants::XLGREEN)
|
177
|
+
oe.color_cell("Field3",ExcelConstants::XLRED)
|
178
|
+
oe.save
|
179
|
+
end
|
180
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ActiveExcel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob Whitener
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-02 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.0.0
|
24
|
+
version:
|
25
|
+
description: Excel as the container of model objects
|
26
|
+
email: robwhitener@gmail.com
|
27
|
+
executables:
|
28
|
+
- active_excel
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.txt
|
35
|
+
files:
|
36
|
+
- History.txt
|
37
|
+
- Manifest.txt
|
38
|
+
- README.txt
|
39
|
+
- Rakefile
|
40
|
+
- bin/active_excel
|
41
|
+
- lib/active_excel.rb
|
42
|
+
- test/test_active_excel.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://robsexocortex.blogspot.com/2009/06/activeexcel.html
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --main
|
50
|
+
- README.txt
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project: active_excel
|
68
|
+
rubygems_version: 1.3.5
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Use Excel to contain model objects. Each row in a sheet represents an object, each sheet in a workbook represents a class. Make sure that you rename your sheets - this gem uses ActiveSupport to take advantage of the pluralization familiar to Rails developers. Modeled liberally on ActiveRecord, not quite as cool but hopefully useful enough. Cheers.
|
72
|
+
test_files:
|
73
|
+
- test/test_active_excel.rb
|