ghazel-slim-attributes 0.6.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT_LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Stephen Sykes
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,44 @@
1
+ SlimAttributes
2
+ ==============
3
+
4
+ This is a small patch to the ActiveRecord Mysql adaptor that stops rails from using the all_hashes / each_hash mechanism - which is what is called when you do a find.
5
+
6
+ It is faster, and uses less memory.
7
+
8
+ Measuring with just ActiveRecord code - fetching stuff from the database - we see anything up to a 50% (or more) speed increase, but I suppose it really depends on your system and environment, and what you are doing with the results from the database. The more columns your tables have, the better the improvement will likely be. Measure your own system and send me the results!
9
+
10
+
11
+ Installation
12
+ ============
13
+
14
+ You're going to need the mysql headers for this to work.
15
+
16
+ Try:
17
+ gem install slim-attributes -- --with-mysql-config
18
+ or:
19
+ gem install slim-attributes
20
+
21
+ then add this to environment.rb:
22
+ require 'slim_attributes'
23
+
24
+
25
+ Description
26
+ ===========
27
+
28
+ The reason for overriding all_hashes is threefold:
29
+
30
+ * making a hash of each and every row returned from the database is slow
31
+ * ruby makes frozen copies of each column name string (for the keys) which results in a great many strings which are not really needed
32
+ * we observe that it's not often that all the fields of rows fetched from the database are actually used
33
+
34
+ So this is an alternative implementation of all_hashes that returns a 'fake hash' which contains a hash of the column names (the same hash of names is used for every row), and also contains the row data in an area memcpy'd directly from the mysql API.
35
+
36
+ The field contents are then instantiated into Ruby strings on demand - ruby strings are only made if you need them. Note that if you always look at all the columns when you fetch data from the database then this won't necessarily be faster that the unpatched mysql adapter. But it won't be much slower either, and we do expect that most times not all the columns from a result set are accessed.
37
+
38
+
39
+ ===========
40
+
41
+ No warranty - this gem has been tested, but not in all environments. However, that said, we are using it in our production environment with good results.
42
+ Please report bugs to sdsykes at symbol gmail pip com.
43
+
44
+ Copyright (c) 2008 Stephen Sykes, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "slim-attributes"
7
+ s.summary = "Slim-attributes - lazy instantiation of attributes for ActiveRecord"
8
+ s.email = "sdsykes@gmail.com"
9
+ s.homepage = "http://github.com/sdsykes/slim-attributes"
10
+ s.description = "Slim attributes boosts speed in Rails/Mysql ActiveRecord Models by avoiding
11
+ instantiating Hashes for each result row, and lazily instantiating attributes as needed."
12
+ s.authors = ["Stephen Sykes"]
13
+ s.files = FileList["[A-Z]*", "{ext,lib,test}/**/*"]
14
+ s.extensions = "ext/extconf.rb"
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://
18
+ gems.github.com"
19
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 3
3
+ :major: 0
4
+ :minor: 6
data/ext/extconf.rb ADDED
@@ -0,0 +1,26 @@
1
+ # this code is borrowed from Mysql/Ruby, credit to TOMITA Masahiro
2
+ # it works for me under OS X and Fedora, hope it works for you also
3
+
4
+ require 'mkmf'
5
+
6
+ if /mswin32/ =~ RUBY_PLATFORM
7
+ inc, lib = dir_config('mysql')
8
+ exit 1 unless have_library("libmysql")
9
+ elsif mc = with_config('mysql-config') then
10
+ mc = 'mysql_config' if mc == true
11
+ cflags = `#{mc} --cflags`.chomp
12
+ exit 1 if $? != 0
13
+ libs = `#{mc} --libs`.chomp
14
+ exit 1 if $? != 0
15
+ $CPPFLAGS += ' ' + cflags
16
+ $libs = libs + " " + $libs
17
+ else
18
+ inc, lib = dir_config('mysql', '/usr/local')
19
+ libs = ['m', 'z', 'socket', 'nsl', 'mygcc']
20
+ while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do
21
+ exit 1 if libs.empty?
22
+ have_library(libs.shift)
23
+ end
24
+ end
25
+
26
+ create_makefile("slim_attrib_ext")
@@ -0,0 +1,210 @@
1
+ // Author: Stephen Sykes
2
+ // http://pennysmalls.com
3
+
4
+ #include "ruby.h"
5
+ #include "st.h"
6
+
7
+ #include <mysql.h>
8
+ #include <errmsg.h>
9
+ #include <mysqld_error.h>
10
+
11
+ #define GetMysqlRes(obj) (Check_Type(obj, T_DATA), ((struct mysql_res*)DATA_PTR(obj))->res)
12
+ #define GetCharPtr(obj) (Check_Type(obj, T_DATA), (char*)DATA_PTR(obj))
13
+ #define GetCharStarPtr(obj) (Check_Type(obj, T_DATA), (char**)DATA_PTR(obj))
14
+
15
+ VALUE cRowHash, cClass;
16
+ ID pointers_id, row_info_id, field_indexes_id, real_hash_id, to_hash_id;
17
+
18
+ #define MAX_CACHED_COLUMN_IDS 40
19
+ ID column_ids[MAX_CACHED_COLUMN_IDS];
20
+
21
+ // from mysql/ruby
22
+ struct mysql_res {
23
+ MYSQL_RES* res;
24
+ char freed;
25
+ };
26
+
27
+ // row info
28
+ #define SLIM_IS_NULL (char)0x01
29
+ #define SLIM_IS_SET (char)0x02
30
+
31
+ #define GET_COL_IV_ID(str, cnum) (cnum < MAX_CACHED_COLUMN_IDS ? column_ids[cnum] : (sprintf(str, "@col_%ld", cnum), rb_intern(str)))
32
+
33
+ #define REAL_HASH_EXISTS NIL_P(field_indexes = rb_ivar_get(obj, field_indexes_id))
34
+
35
+ // This replaces the usual all_hashes method defined in mysql_adaptor.rb
36
+ //
37
+ // It copies the data from the mysql result into allocated memory
38
+ // ready for creating ruby strings from on demand, instead of creating
39
+ // all the data into ruby strings immediately.
40
+ // all_hashes returns an array of result rows
41
+ static VALUE all_hashes(VALUE obj) {
42
+ MYSQL_RES *res = GetMysqlRes(obj);
43
+ MYSQL_FIELD *fields = mysql_fetch_fields(res);
44
+ MYSQL_ROW row;
45
+ VALUE all_hashes_ary, col_names_hsh, frh;
46
+ my_ulonglong nr = mysql_num_rows(res);
47
+ unsigned int nf = mysql_num_fields(res);
48
+ unsigned int i, j, s, len;
49
+ unsigned long *lengths;
50
+ char *row_info_space, **pointers_space, *p;
51
+
52
+ // make a hash of column names
53
+ col_names_hsh = rb_hash_new();
54
+ for (i=0; i < nf; i++) {
55
+ rb_hash_aset(col_names_hsh, rb_str_new2(fields[i].name), INT2FIX(i));
56
+ }
57
+
58
+ // make the array of result rows
59
+ all_hashes_ary = rb_ary_new2(nr);
60
+
61
+ for (i=0; i < nr; i++) {
62
+ row = mysql_fetch_row(res); // get the row data and lengths from mysql
63
+ lengths = mysql_fetch_lengths(res);
64
+ for (s=j=0; j < nf; j++) s += lengths[j]; // s = total of lengths
65
+ pointers_space = ruby_xmalloc((nf + 1) * sizeof(char *) + s); // space for data pointers & data
66
+ if (!pointers_space) {
67
+ rb_raise(rb_eNoMemError, "out of memory");
68
+ }
69
+ p = *pointers_space = (char *)(pointers_space + nf + 1); // pointer to first data item
70
+ row_info_space = ruby_xcalloc(nf, 1); // space for flags for each column in this row
71
+ if (!row_info_space) {
72
+ ruby_xfree(pointers_space);
73
+ rb_raise(rb_eNoMemError, "out of memory");
74
+ }
75
+ for (j=0; j < nf; j++) {
76
+ len = (unsigned int)lengths[j];
77
+ if (len) {
78
+ memcpy(p, row[j], len); // copy row data in
79
+ p += len;
80
+ } else if (!row[j]) row_info_space[j] = SLIM_IS_NULL; // flag so we can handle null
81
+ pointers_space[j + 1] = p;
82
+ }
83
+ frh = rb_class_new_instance(0, NULL, cRowHash); // create the row object
84
+ rb_ivar_set(frh, pointers_id, Data_Wrap_Struct(cClass, 0, ruby_xfree, pointers_space));
85
+ rb_ivar_set(frh, row_info_id, Data_Wrap_Struct(cClass, 0, ruby_xfree, row_info_space));
86
+ rb_ivar_set(frh, field_indexes_id, col_names_hsh);
87
+ rb_ary_store(all_hashes_ary, i, frh); // store it in the array
88
+ }
89
+ return all_hashes_ary;
90
+ }
91
+
92
+ // This does the actual work of creating a ruby string when one is demanded, typically through
93
+ // a call to an active record model property method.
94
+ static VALUE fetch_by_index(VALUE obj, VALUE index) {
95
+ VALUE contents;
96
+ char *row_info, **pointers, *start, col_name[16];
97
+ ID col_id;
98
+ long col_number = FIX2LONG(index);
99
+ unsigned int length;
100
+ row_info = GetCharPtr(rb_ivar_get(obj, row_info_id)) + col_number; // flags for this column
101
+ if (*row_info == SLIM_IS_NULL) return Qnil; // return nil if null from db
102
+ col_id = GET_COL_IV_ID(col_name, col_number);
103
+ if (*row_info == SLIM_IS_SET) return rb_ivar_get(obj, col_id); // was made to a string already
104
+ pointers = GetCharStarPtr(rb_ivar_get(obj, pointers_id)); // find the data and make ruby string
105
+ start = pointers[col_number];
106
+ length = pointers[col_number + 1] - start;
107
+ contents = rb_tainted_str_new(start, length);
108
+ rb_ivar_set(obj, col_id, contents); // it is efficient to save the string in an instance variable
109
+ *row_info = SLIM_IS_SET;
110
+ return contents;
111
+ }
112
+
113
+ // This is the [] method of the row data object.
114
+ // It checks for a real hash, but if none exists it will call fetch_by_index
115
+ static VALUE slim_fetch(VALUE obj, VALUE name) {
116
+ VALUE field_indexes, hash_lookup;
117
+
118
+ if (REAL_HASH_EXISTS) return rb_hash_aref(rb_ivar_get(obj, real_hash_id), name);
119
+
120
+ hash_lookup = rb_hash_aref(field_indexes, name);
121
+ if (NIL_P(hash_lookup)) return Qnil;
122
+ return fetch_by_index(obj, hash_lookup);
123
+ }
124
+
125
+ // This is the []= method of the row data object.
126
+ // It either operates on the real hash if it exists, or sets the appropriate
127
+ // column instance variable
128
+ static VALUE set_element(VALUE obj, VALUE name, VALUE val) {
129
+ VALUE field_indexes, hash_lookup;
130
+ long col_number;
131
+ char col_name[16];
132
+ ID col_id;
133
+
134
+ if (REAL_HASH_EXISTS) return rb_hash_aset(rb_ivar_get(obj, real_hash_id), name, val);
135
+
136
+ hash_lookup = rb_hash_aref(field_indexes, name);
137
+ if (NIL_P(hash_lookup)) return rb_hash_aset(rb_funcall(obj, to_hash_id, 0), name, val);
138
+ col_number = FIX2LONG(hash_lookup);
139
+ col_id = GET_COL_IV_ID(col_name, col_number);
140
+ rb_ivar_set(obj, col_id, val);
141
+ GetCharPtr(rb_ivar_get(obj, row_info_id))[col_number] = SLIM_IS_SET;
142
+ return val;
143
+ }
144
+
145
+ // This is the dup method of the row data object.
146
+ // When the query cache is used, the result of all_hashes is dupped before
147
+ // being passed back to the user.
148
+ // Subsequent queries of the same SQL will get another dup of the results.
149
+ // So we must implement dup in an efficient way (without converting to a real hash).
150
+ //
151
+ // Note: this method currently ignores any columns that have been assigned to using
152
+ // []= before calling dup (the original values will be seen in the dup). This works ok
153
+ // for active record usage, but perhaps could cause unexpected behaviour if model
154
+ // attributes are dupped by the user after changing them.
155
+ static VALUE slim_dup(VALUE obj) {
156
+ VALUE frh, field_indexes;
157
+ int nf, i;
158
+ char *row_info_space;
159
+
160
+ if (REAL_HASH_EXISTS) return rb_obj_dup(rb_ivar_get(obj, real_hash_id));
161
+
162
+ nf = RHASH(field_indexes)->tbl->num_entries;
163
+ row_info_space = ruby_xmalloc(nf); // dup needs its own set of flags
164
+ if (!row_info_space) rb_raise(rb_eNoMemError, "out of memory");
165
+ memcpy(row_info_space, GetCharPtr(rb_ivar_get(obj, row_info_id)), nf);
166
+ for (i=0; i < nf; i++) row_info_space[i] &= ~SLIM_IS_SET; // remove any set flags
167
+ frh = rb_class_new_instance(0, NULL, cRowHash); // make the new row data object
168
+ rb_ivar_set(frh, pointers_id, rb_ivar_get(obj, pointers_id));
169
+ rb_ivar_set(frh, row_info_id, Data_Wrap_Struct(cClass, 0, ruby_xfree, row_info_space));
170
+ rb_ivar_set(frh, field_indexes_id, field_indexes);
171
+ return frh;
172
+ }
173
+
174
+ // This is the has_key? method of the row data object.
175
+ // Calls to model property methods in AR cause a call to has_key?, so it
176
+ // is implemented here in C for speed.
177
+ static VALUE has_key(VALUE obj, VALUE name) {
178
+ VALUE field_indexes;
179
+
180
+ if (REAL_HASH_EXISTS) return (st_lookup(RHASH(rb_ivar_get(obj, real_hash_id))->tbl, name, 0) ? Qtrue : Qfalse);
181
+ else return (st_lookup(RHASH(field_indexes)->tbl, name, 0) ? Qtrue : Qfalse);
182
+ }
183
+
184
+ void Init_slim_attrib_ext() {
185
+ int i;
186
+ char col_name[16];
187
+ VALUE c = rb_cObject;
188
+
189
+ c = rb_const_get_at(c, rb_intern("Mysql"));
190
+ c = rb_const_get_at(c, rb_intern("Result"));
191
+ rb_define_method(c, "all_hashes", (VALUE(*)(ANYARGS))all_hashes, 0);
192
+ cRowHash = rb_const_get_at(c, rb_intern("RowHash"));
193
+ cClass = rb_define_class("CObjects", cRowHash);
194
+ // set up methods
195
+ rb_define_private_method(cRowHash, "fetch_by_index", (VALUE(*)(ANYARGS))fetch_by_index, 1);
196
+ rb_define_method(cRowHash, "[]", (VALUE(*)(ANYARGS))slim_fetch, 1);
197
+ rb_define_method(cRowHash, "[]=", (VALUE(*)(ANYARGS))set_element, 2);
198
+ rb_define_method(cRowHash, "dup", (VALUE(*)(ANYARGS))slim_dup, 0);
199
+ rb_define_method(cRowHash, "has_key?", (VALUE(*)(ANYARGS))has_key, 1);
200
+ // set up some symbols that we will need
201
+ pointers_id = rb_intern("@pointers");
202
+ row_info_id = rb_intern("@row_info");
203
+ field_indexes_id = rb_intern("@field_indexes");
204
+ real_hash_id = rb_intern("@real_hash");
205
+ to_hash_id = rb_intern("to_hash");
206
+ for(i=0; i < MAX_CACHED_COLUMN_IDS; i++) {
207
+ sprintf(col_name, "@col_%d", i);
208
+ column_ids[i] = rb_intern(col_name);
209
+ }
210
+ }
@@ -0,0 +1,54 @@
1
+ # Author: Stephen Sykes
2
+ # http://pennysmalls.com
3
+
4
+ require 'mysql'
5
+
6
+ class Mysql::Result; class RowHash; end; end
7
+
8
+ require 'slim_attrib_ext'
9
+
10
+ class Mysql::Result
11
+ class RowHash
12
+ def marshal_dump
13
+ to_hash
14
+ end
15
+
16
+ def marshal_load(hash)
17
+ @real_hash = hash
18
+ end
19
+
20
+ alias_method :include?, :has_key?
21
+
22
+ def keys
23
+ @real_hash ? @real_hash.keys : @field_indexes.keys
24
+ end
25
+
26
+ # If you want to do anything other than [], []=, dup, keys and has_key? then
27
+ # we'll handle that by doing the operation on a real ruby hash.
28
+ # This should be the exception though, and the efficiencies of using slim-attributes
29
+ # are lost when this happens.
30
+ def to_hash
31
+ return @real_hash if @real_hash
32
+ @real_hash = {}
33
+ @field_indexes.each_pair {|name, index| @real_hash[name] = fetch_by_index(index)}
34
+ @field_indexes = nil
35
+ @real_hash
36
+ end
37
+
38
+ def to_a
39
+ to_hash.to_a
40
+ end
41
+
42
+ # Load up all the attributes before a freeze
43
+ alias_method :regular_freeze, :freeze
44
+
45
+ def freeze
46
+ to_hash.freeze
47
+ regular_freeze
48
+ end
49
+
50
+ def method_missing(name, *args, &block)
51
+ to_hash.send(name, *args, &block)
52
+ end
53
+ end
54
+ end
data/test/benchmark.rb ADDED
@@ -0,0 +1,34 @@
1
+ # Author: Stephen Sykes
2
+ # http://pennysmalls.com
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ require File.dirname(__FILE__) + "/products"
7
+ require File.dirname(__FILE__) + "/slim_db_test_utils"
8
+
9
+ SlimDbTestUtils.connect_and_create_db
10
+ Product.create_large_product_table
11
+ Product.make_some_products
12
+
13
+ def do_benchmark(name)
14
+ start_t = Time.now
15
+
16
+ 2000.times do |n|
17
+ product = Product.find(:all)[n % 100]
18
+ x = product.name
19
+ y = product.comment
20
+ z = product.created_at
21
+ end
22
+
23
+ end_t = Time.now
24
+ time_taken = end_t - start_t
25
+ puts "#{name}: #{time_taken}s"
26
+ time_taken
27
+ end
28
+
29
+ time1 = do_benchmark("Without slim-attributes")
30
+ require 'slim_attributes'
31
+ time2 = do_benchmark("With slim-attributes")
32
+ puts "Diff: #{"%.2f"%(100 - time2 / time1 * 100)}%"
33
+
34
+ SlimDbTestUtils.remove_db
data/test/database.yml ADDED
@@ -0,0 +1,6 @@
1
+ slim_attributes_test:
2
+ adapter: mysql
3
+ database: slim_attributes_test
4
+ username: your_username
5
+ password: your_password
6
+ socket: /tmp/mysql.sock
data/test/products.rb ADDED
@@ -0,0 +1,40 @@
1
+ # Author: Stephen Sykes
2
+ # http://pennysmalls.com
3
+
4
+ class Product < ActiveRecord::Base
5
+
6
+ def self.create_product_table
7
+ ActiveRecord::Base.connection.drop_table(:products) rescue ActiveRecord::StatementInvalid
8
+ ActiveRecord::Base.connection.create_table(:products) do |t|
9
+ t.column :name, :string, :limit => 60
10
+ t.column :created_at, :datetime
11
+ t.column :number, :integer
12
+ t.column :nil_test, :string
13
+ t.column :comment, :text
14
+ end
15
+ end
16
+
17
+ def self.create_large_product_table
18
+ ActiveRecord::Base.connection.drop_table(:products) rescue ActiveRecord::StatementInvalid
19
+ ActiveRecord::Base.connection.create_table(:products) do |t|
20
+ t.column :name, :string, :limit => 60
21
+ t.column :created_at, :datetime
22
+ t.column :number, :integer
23
+ t.column :nil_test, :string
24
+ t.column :comment, :text
25
+ 40.times {|n| t.column "test_col_#{n}", :string}
26
+ end
27
+ end
28
+
29
+
30
+ def self.make_some_products
31
+ 100.times do |n|
32
+ Product.create(:name=>"product_#{n}", :number=>n, :comment=>"Made by the test suite")
33
+ end
34
+ end
35
+
36
+ def attributes_iv
37
+ @attributes
38
+ end
39
+
40
+ end
@@ -0,0 +1,169 @@
1
+ # Author: Stephen Sykes
2
+ # http://pennysmalls.com
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ require 'active_record/version'
7
+ require 'slim_attributes'
8
+ require 'test/unit'
9
+ require File.dirname(__FILE__) + "/products"
10
+ require File.dirname(__FILE__) + "/slim_db_test_utils"
11
+
12
+ class SlimAttributesTest < Test::Unit::TestCase
13
+ def setup
14
+ SlimDbTestUtils.connect_and_create_db
15
+ Product.create_product_table
16
+ Product.make_some_products
17
+ end
18
+
19
+ def test_finds_all
20
+ items = Product.find(:all)
21
+ assert items.size == 100, "must find all 100 items"
22
+ end
23
+
24
+ def test_items_have_correct_attributes
25
+ items = Product.find(:all, :order=>"id")
26
+ items.each_index do |i|
27
+ check_attributes_for(items[i], i)
28
+ end
29
+ end
30
+
31
+ def test_item_attributes_can_be_changed
32
+ item = Product.find(:first)
33
+ old_name = item.name.dup
34
+ item.name << "more"
35
+ assert_equal old_name + "more", item.name, "change must stick"
36
+ item.nil_test = "not a nil"
37
+ assert_equal "not a nil", item.nil_test, "change must stick"
38
+ item.name = "something else"
39
+ assert_equal "something else", item.name, "change must stick"
40
+ item.save
41
+ item = Product.find(:first)
42
+ assert_equal "something else", item.name, "change must persist in DB"
43
+ assert_equal "not a nil", item.nil_test, "change must persist in DB"
44
+ end
45
+
46
+ def test_item_attributes_to_a_works
47
+ item = Product.find_by_id(1)
48
+ arr = item.attributes_iv.to_a
49
+ expected = [["id","1"], ["name", "product_0"], ["number","0"], ["comment","Made by the test suite"], ["created_at", item.created_at_before_type_cast], ["nil_test", nil]]
50
+ arr.each do |a|
51
+ assert expected.include?(a), "array must match"
52
+ end
53
+ end
54
+
55
+ def test_item_can_be_marshalled
56
+ item = Product.find_by_id(1)
57
+ mi = Marshal.load(Marshal.dump(item))
58
+ check_attributes_for(mi, 0)
59
+ end
60
+
61
+ def test_has_key_and_include
62
+ item = Product.find_by_id(1)
63
+ assert item.attributes_iv.has_key?("name"), "must have key name"
64
+ assert item.attributes_iv.include?("name"), "must have key name"
65
+ assert !item.attributes_iv.has_key?("name1"), "must not have key name1"
66
+ assert !item.attributes_iv.include?("name1"), "must not have key name1"
67
+ end
68
+
69
+ def test_keys
70
+ item = Product.find_by_id(1)
71
+ assert_equal ["id", "name", "created_at", "number", "comment", "nil_test"].sort, item.attributes_iv.keys.sort, "keys must work"
72
+ end
73
+
74
+ def test_to_hash
75
+ item = Product.find_by_id(1)
76
+ expected = {"id"=>"1", "name"=>"product_0", "number"=>"0", "comment"=>"Made by the test suite", "created_at"=>item.created_at_before_type_cast, "nil_test"=>nil}
77
+ hash = item.attributes_iv.to_hash
78
+ assert_equal Hash, hash.class, "to_hash must result in a Hash"
79
+ assert_equal expected, hash, "to_hash must work"
80
+ end
81
+
82
+ def test_fake_hash_can_be_updated
83
+ item = Product.find_by_id(1)
84
+ old_name = item.name
85
+ item.attributes_iv.update("name"=>"foobar")
86
+ assert_equal "foobar", item.name, "update must work"
87
+ item.name_will_change! if ActiveRecord::VERSION::STRING >= "2.1.0"
88
+ item.save
89
+ item = Product.find_by_id(1)
90
+ assert_equal "foobar", item.name, "update must work and stick through db"
91
+ assert_equal 0, item.number, "other attributes must not be changed in db"
92
+ item.name = old_name
93
+ item.save
94
+ end
95
+
96
+ def test_can_assign_to_non_columns_in_hash
97
+ item = Product.find_by_id(1)
98
+ fh = item.attributes_iv
99
+ fh["something"] = 23
100
+ fh[:symbol] = "23"
101
+ assert_equal 23, fh["something"], "assignment to non col"
102
+ assert_equal "23", fh[:symbol], "assignment to non col"
103
+ assert fh.keys.include?("something"), "keys must include new"
104
+ assert fh.keys.include?(:symbol), "keys must include new"
105
+ assert fh.keys.include?("name"), "keys must still include old"
106
+ assert_equal 23, item.something, "non col becomes accessible with method call"
107
+ end
108
+
109
+ def test_key_can_be_deleted
110
+ item = Product.find_by_id(1)
111
+ fh = item.attributes_iv
112
+ fh.delete("name")
113
+ assert_nil fh["name"], "name must be nil now"
114
+ assert_raises(ActiveRecord::MissingAttributeError, "name must raise on method call") {item.name}
115
+ end
116
+
117
+ def test_gc
118
+ GC.start
119
+ assert true, "gc didn't crash"
120
+ end
121
+
122
+ # rails query cache uses dup
123
+ def test_dup
124
+ items = Product.find(:all)
125
+ attr_dup = items[0].attributes_iv.dup
126
+ assert_equal "product_0", attr_dup["name"], "name must be correct in dup'd attributes"
127
+ attr_dup = items[0].attributes_iv.dup
128
+ assert_equal "product_0", attr_dup["name"], "name must be correct in dup'd attributes 2nd time"
129
+ end
130
+
131
+ def test_cached_result
132
+ ActiveRecord::Base.connection.cache do
133
+ item1 = Product.find_by_id(1)
134
+ item1.name = "foo"
135
+ item2 = Product.find_by_id(1)
136
+ assert_equal "product_0", item2.name, "name must be original from cached query"
137
+ item3 = Product.find_by_id(1)
138
+ item1.name = "bar"
139
+ assert_equal "product_0", item3.name, "name must be original from cached query"
140
+ item2.name << "_test"
141
+ # unmodified rails fails this test - but does it matter either way?
142
+ check_attributes_for(item3, 0)
143
+ end
144
+ end
145
+
146
+ def test_accessing_destroyed_object_attributes
147
+ item1 = Product.find_by_id(1)
148
+ assert_equal false, item1.frozen?
149
+ item1.destroy # object is frozen
150
+ assert_equal true, item1.frozen?
151
+ check_attributes_for(item1, 0)
152
+ assert_raises(TypeError) {item1.name = "another product"}
153
+ end
154
+
155
+ def teardown
156
+ SlimDbTestUtils.remove_db
157
+ end
158
+
159
+ private
160
+
161
+ def check_attributes_for(item, i)
162
+ assert_equal "product_#{i}", item.name, "item name must be right"
163
+ assert_equal i, item.number, "item number must be right"
164
+ assert_equal i + 1, item.id, "item id must be right"
165
+ assert_equal "Made by the test suite", item.comment, "item comment must be right"
166
+ assert ((1.minute.ago)..(Time.now)) === item.created_at, "item created_at must be reasonable"
167
+ assert_nil item.nil_test, "nil_test must be nil"
168
+ end
169
+ end
@@ -0,0 +1,38 @@
1
+ # Author: Stephen Sykes
2
+ # http://pennysmalls.com
3
+
4
+ module SlimDbTestUtils
5
+ DB_NAME = "slim_attributes_test"
6
+
7
+ def self.db_config
8
+ @config ||= YAML.load(File.read(File.dirname(__FILE__) + "/database.yml"))
9
+ @config[DB_NAME]
10
+ end
11
+
12
+ def self.connect_and_create_db
13
+ connect_with_config(db_config)
14
+ unless ActiveRecord::Base.connected? # database did not exist (as expected)
15
+ connect_with_config(db_config.merge({"database"=>nil}))
16
+ ActiveRecord::Base.connection.create_database(db_config["database"])
17
+ connect_with_config(db_config)
18
+ end
19
+ end
20
+
21
+ def self.connect_with_config(config)
22
+ begin
23
+ ActiveRecord::Base.establish_connection(config)
24
+ ActiveRecord::Base.connection
25
+ rescue Mysql::Error
26
+ end
27
+ end
28
+
29
+ def self.remove_db
30
+ if ActiveRecord::Base.connected?
31
+ ActiveRecord::Base.connection.execute("DROP DATABASE #{db_config["database"]}")
32
+ end
33
+ end
34
+
35
+ def self.import_sql(file)
36
+ `mysql -p#{db_config["password"]} -u #{db_config["username"]} #{db_config["database"]} < #{File.dirname(__FILE__)}/#{file}`
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ghazel-slim-attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Sykes
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-12 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Slim attributes boosts speed in Rails/Mysql ActiveRecord Models by avoiding instantiating Hashes for each result row, and lazily instantiating attributes as needed.
17
+ email: sdsykes@gmail.com
18
+ executables: []
19
+
20
+ extensions:
21
+ - ext/extconf.rb
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - MIT_LICENCE
26
+ - Rakefile
27
+ - README
28
+ - VERSION.yml
29
+ - ext/extconf.rb
30
+ - ext/slim_attrib_ext.c
31
+ - lib/slim_attributes.rb
32
+ - test/benchmark.rb
33
+ - test/database.yml
34
+ - test/products.rb
35
+ - test/slim_attributes_test.rb
36
+ - test/slim_db_test_utils.rb
37
+ has_rdoc: true
38
+ homepage: http://github.com/sdsykes/slim-attributes
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --inline-source
42
+ - --charset=UTF-8
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: Slim-attributes - lazy instantiation of attributes for ActiveRecord
64
+ test_files: []
65
+