slim-attributes 0.6.0 → 0.6.4
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/README +11 -1
- data/Rakefile +19 -0
- data/VERSION.yml +4 -0
- data/ext/slim_attrib_ext.c +16 -2
- data/test/benchmark.rb +34 -0
- data/test/database.yml +6 -0
- data/test/products.rb +40 -0
- data/test/slim_attributes_test.rb +169 -0
- data/test/slim_db_test_utils.rb +38 -0
- metadata +23 -15
data/README
CHANGED
@@ -11,6 +11,8 @@ Measuring with just ActiveRecord code - fetching stuff from the database - we se
|
|
11
11
|
Installation
|
12
12
|
============
|
13
13
|
|
14
|
+
Slim attributes works with ruby 1.8.6+ and 1.9.1+.
|
15
|
+
|
14
16
|
You're going to need the mysql headers for this to work.
|
15
17
|
|
16
18
|
Try:
|
@@ -41,4 +43,12 @@ The field contents are then instantiated into Ruby strings on demand - ruby stri
|
|
41
43
|
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
44
|
Please report bugs to sdsykes at symbol gmail pip com.
|
43
45
|
|
44
|
-
|
46
|
+
|
47
|
+
Credits
|
48
|
+
=======
|
49
|
+
|
50
|
+
Thanks to IronDigital for the initial port for ruby 1.9.
|
51
|
+
Thanks to Greg Hazel for fixing compilation for windows.
|
52
|
+
|
53
|
+
|
54
|
+
Copyright (c) 2008-2009 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
data/ext/slim_attrib_ext.c
CHANGED
@@ -2,7 +2,12 @@
|
|
2
2
|
// http://pennysmalls.com
|
3
3
|
|
4
4
|
#include "ruby.h"
|
5
|
+
|
6
|
+
#ifdef HAVE_RUBY_ST_H
|
7
|
+
#include "ruby/st.h"
|
8
|
+
#else
|
5
9
|
#include "st.h"
|
10
|
+
#endif
|
6
11
|
|
7
12
|
#include <mysql.h>
|
8
13
|
#include <errmsg.h>
|
@@ -152,14 +157,18 @@ static VALUE set_element(VALUE obj, VALUE name, VALUE val) {
|
|
152
157
|
// []= before calling dup (the original values will be seen in the dup). This works ok
|
153
158
|
// for active record usage, but perhaps could cause unexpected behaviour if model
|
154
159
|
// attributes are dupped by the user after changing them.
|
155
|
-
static VALUE
|
160
|
+
static VALUE slim_dup(VALUE obj) {
|
156
161
|
VALUE frh, field_indexes;
|
157
162
|
int nf, i;
|
158
163
|
char *row_info_space;
|
159
164
|
|
160
165
|
if (REAL_HASH_EXISTS) return rb_obj_dup(rb_ivar_get(obj, real_hash_id));
|
161
166
|
|
167
|
+
#ifdef RHASH_SIZE
|
168
|
+
nf = RHASH_SIZE(field_indexes);
|
169
|
+
#else
|
162
170
|
nf = RHASH(field_indexes)->tbl->num_entries;
|
171
|
+
#endif
|
163
172
|
row_info_space = ruby_xmalloc(nf); // dup needs its own set of flags
|
164
173
|
if (!row_info_space) rb_raise(rb_eNoMemError, "out of memory");
|
165
174
|
memcpy(row_info_space, GetCharPtr(rb_ivar_get(obj, row_info_id)), nf);
|
@@ -177,8 +186,13 @@ static VALUE dup(VALUE obj) {
|
|
177
186
|
static VALUE has_key(VALUE obj, VALUE name) {
|
178
187
|
VALUE field_indexes;
|
179
188
|
|
189
|
+
#ifdef RHASH_TBL
|
190
|
+
if (REAL_HASH_EXISTS) return (st_lookup(RHASH_TBL(rb_ivar_get(obj, real_hash_id)), name, 0) ? Qtrue : Qfalse);
|
191
|
+
else return (st_lookup(RHASH_TBL(field_indexes), name, 0) ? Qtrue : Qfalse);
|
192
|
+
#else
|
180
193
|
if (REAL_HASH_EXISTS) return (st_lookup(RHASH(rb_ivar_get(obj, real_hash_id))->tbl, name, 0) ? Qtrue : Qfalse);
|
181
194
|
else return (st_lookup(RHASH(field_indexes)->tbl, name, 0) ? Qtrue : Qfalse);
|
195
|
+
#endif
|
182
196
|
}
|
183
197
|
|
184
198
|
void Init_slim_attrib_ext() {
|
@@ -195,7 +209,7 @@ void Init_slim_attrib_ext() {
|
|
195
209
|
rb_define_private_method(cRowHash, "fetch_by_index", (VALUE(*)(ANYARGS))fetch_by_index, 1);
|
196
210
|
rb_define_method(cRowHash, "[]", (VALUE(*)(ANYARGS))slim_fetch, 1);
|
197
211
|
rb_define_method(cRowHash, "[]=", (VALUE(*)(ANYARGS))set_element, 2);
|
198
|
-
rb_define_method(cRowHash, "dup", (VALUE(*)(ANYARGS))
|
212
|
+
rb_define_method(cRowHash, "dup", (VALUE(*)(ANYARGS))slim_dup, 0);
|
199
213
|
rb_define_method(cRowHash, "has_key?", (VALUE(*)(ANYARGS))has_key, 1);
|
200
214
|
// set up some symbols that we will need
|
201
215
|
pointers_id = rb_intern("@pointers");
|
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
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, it's ok
|
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(RUBY_VERSION >= "1.9" ? RuntimeError : 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 item.created_at <= Time.now && item.created_at > Time.now - 30, "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
CHANGED
@@ -1,37 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slim-attributes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Sykes
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-07-12 00:00:00 +03:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description:
|
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
17
|
email: sdsykes@gmail.com
|
18
18
|
executables: []
|
19
19
|
|
20
20
|
extensions:
|
21
21
|
- ext/extconf.rb
|
22
|
-
extra_rdoc_files:
|
23
|
-
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
24
|
files:
|
25
|
-
-
|
25
|
+
- MIT_LICENCE
|
26
|
+
- Rakefile
|
27
|
+
- README
|
28
|
+
- VERSION.yml
|
26
29
|
- ext/extconf.rb
|
27
30
|
- ext/slim_attrib_ext.c
|
28
|
-
-
|
29
|
-
-
|
30
|
-
|
31
|
-
|
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
|
32
39
|
post_install_message:
|
33
|
-
rdoc_options:
|
34
|
-
|
40
|
+
rdoc_options:
|
41
|
+
- --inline-source
|
42
|
+
- --charset=UTF-8
|
35
43
|
require_paths:
|
36
44
|
- lib
|
37
45
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -48,10 +56,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
56
|
version:
|
49
57
|
requirements: []
|
50
58
|
|
51
|
-
rubyforge_project:
|
59
|
+
rubyforge_project:
|
52
60
|
rubygems_version: 1.3.1
|
53
61
|
signing_key:
|
54
62
|
specification_version: 2
|
55
|
-
summary: Slim
|
63
|
+
summary: Slim-attributes - lazy instantiation of attributes for ActiveRecord
|
56
64
|
test_files: []
|
57
65
|
|