denormalize-field 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,9 +1,10 @@
1
1
  source "http://rubygems.org"
2
2
  gem "activerecord", "~> 3.2.0"
3
3
 
4
- group :development do
4
+ group :development, :test do
5
+ gem 'em-synchrony'
5
6
  gem 'pry'
6
- gem 'sqlite3'
7
+ gem 'pg'
7
8
  gem "rspec", "~> 2.8.0"
8
9
  gem "rdoc", "~> 3.12"
9
10
  gem "jeweler", "~> 1.8.4"
data/Gemfile.lock CHANGED
@@ -16,6 +16,9 @@ GEM
16
16
  builder (3.0.3)
17
17
  coderay (1.0.7)
18
18
  diff-lcs (1.1.3)
19
+ em-synchrony (1.0.3)
20
+ eventmachine (>= 1.0.0.beta.1)
21
+ eventmachine (1.0.3)
19
22
  git (1.2.5)
20
23
  i18n (0.6.1)
21
24
  jeweler (1.8.4)
@@ -29,6 +32,7 @@ GEM
29
32
  mocha (0.10.4)
30
33
  metaclass (~> 0.0.1)
31
34
  multi_json (1.3.6)
35
+ pg (0.14.1)
32
36
  pry (0.9.10)
33
37
  coderay (~> 1.0.5)
34
38
  method_source (~> 0.8)
@@ -45,7 +49,6 @@ GEM
45
49
  diff-lcs (~> 1.1.2)
46
50
  rspec-mocks (2.8.0)
47
51
  slop (3.3.3)
48
- sqlite3 (1.3.6)
49
52
  tzinfo (0.3.33)
50
53
 
51
54
  PLATFORMS
@@ -53,9 +56,10 @@ PLATFORMS
53
56
 
54
57
  DEPENDENCIES
55
58
  activerecord (~> 3.2.0)
59
+ em-synchrony
56
60
  jeweler (~> 1.8.4)
57
61
  mocha
62
+ pg
58
63
  pry
59
64
  rdoc (~> 3.12)
60
65
  rspec (~> 2.8.0)
61
- sqlite3
data/README.rdoc CHANGED
@@ -1,15 +1,18 @@
1
1
  = denormalize-field
2
2
 
3
+ Postgres only as of 0.2
4
+
3
5
  Denormalizes fields in ActiveRecord models in order to avoid SQL joins and hydrating Ruby Objects to obtain a simple fields.
4
6
 
5
7
  = Usage
6
8
 
7
- class Post < ActiveRecord::Base
8
- belongs_to :category
9
- denormalizes :category => :name
10
- end
9
+ class Post < ActiveRecord::Base
10
+ belongs_to :category
11
+ denormalizes :category => :name
12
+ end
13
+
14
+ category = Category.create(:name => "News")
15
+ post = Post.create(:category => category)
11
16
 
12
- category = Category.create(:name => "News")
13
- post = Post.create(:category => category)
17
+ post.category_name # "News" (you have to create the migration to add Post#category_name manually)
14
18
 
15
- post.category_name # "News" (you have to create the migration to add Post#category_name manually)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.2.0
@@ -0,0 +1,7 @@
1
+ development:
2
+ adapter: postgresql
3
+ database: denormalizefielddev
4
+
5
+ test: &test
6
+ adapter: postgresql
7
+ database: denormalizefieldtest
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "denormalize-field"
8
- s.version = "0.1.3"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Len Smith"]
12
- s.date = "2012-10-15"
12
+ s.date = "2013-05-28"
13
13
  s.description = "Denormalize ActiveRecord fields for performance reasons"
14
14
  s.email = "len@barrison.com"
15
15
  s.extra_rdoc_files = [
@@ -25,8 +25,12 @@ Gem::Specification.new do |s|
25
25
  "README.rdoc",
26
26
  "Rakefile",
27
27
  "VERSION",
28
+ "config/database.yml",
28
29
  "denormalize-field.gemspec",
29
30
  "lib/denormalize-field.rb",
31
+ "lib/tasks.rb",
32
+ "lib/tasks/denormalize-tasks.rake",
33
+ "spec/db_connect.rb",
30
34
  "spec/denormalize-field_spec.rb",
31
35
  "spec/schema.rb",
32
36
  "spec/spec_helper.rb"
@@ -42,16 +46,18 @@ Gem::Specification.new do |s|
42
46
 
43
47
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
48
  s.add_runtime_dependency(%q<activerecord>, ["~> 3.2.0"])
49
+ s.add_development_dependency(%q<em-synchrony>, [">= 0"])
45
50
  s.add_development_dependency(%q<pry>, [">= 0"])
46
- s.add_development_dependency(%q<sqlite3>, [">= 0"])
51
+ s.add_development_dependency(%q<pg>, [">= 0"])
47
52
  s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
48
53
  s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
49
54
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
50
55
  s.add_development_dependency(%q<mocha>, [">= 0"])
51
56
  else
52
57
  s.add_dependency(%q<activerecord>, ["~> 3.2.0"])
58
+ s.add_dependency(%q<em-synchrony>, [">= 0"])
53
59
  s.add_dependency(%q<pry>, [">= 0"])
54
- s.add_dependency(%q<sqlite3>, [">= 0"])
60
+ s.add_dependency(%q<pg>, [">= 0"])
55
61
  s.add_dependency(%q<rspec>, ["~> 2.8.0"])
56
62
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
57
63
  s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
@@ -59,8 +65,9 @@ Gem::Specification.new do |s|
59
65
  end
60
66
  else
61
67
  s.add_dependency(%q<activerecord>, ["~> 3.2.0"])
68
+ s.add_dependency(%q<em-synchrony>, [">= 0"])
62
69
  s.add_dependency(%q<pry>, [">= 0"])
63
- s.add_dependency(%q<sqlite3>, [">= 0"])
70
+ s.add_dependency(%q<pg>, [">= 0"])
64
71
  s.add_dependency(%q<rspec>, ["~> 2.8.0"])
65
72
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
66
73
  s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
@@ -2,7 +2,18 @@ require 'active_record'
2
2
  require 'active_support'
3
3
  require 'active_support/inflector'
4
4
 
5
+ class DenormalizeUpdater
6
+ def self.sync_all
7
+ DenormalizeFields::UPDATE_STATEMENTS.each do |sql|
8
+ DenormalizeFields::CLASSES.first.connection.execute sql
9
+ end
10
+ end
11
+ end
12
+
5
13
  module DenormalizeFields
14
+ UPDATE_STATEMENTS = []
15
+ CLASSES = []
16
+
6
17
  def denormalizes(hash)
7
18
  hash.keys.each do |key|
8
19
  _field_name = hash[key]
@@ -16,13 +27,23 @@ module DenormalizeFields
16
27
  end
17
28
 
18
29
  _klass = key.to_s.camelize.constantize
30
+ update_sql = "UPDATE #{table_name} SET #{_denormalized_field_name} = c2.#{_field_name} FROM #{table_name} c1 INNER JOIN #{_klass.table_name} c2 on c2.id = c1.#{key}_id"
31
+
19
32
  _klass.after_save do
20
33
  if self.send "#{_field_name}_changed?"
21
- self.send(_original_klass.name.downcase.pluralize).each do |child|
22
- child.update_attribute _denormalized_field_name, self.send(_field_name)
23
- end
34
+ update_sql = "UPDATE #{_original_klass.table_name} SET #{_denormalized_field_name} = '#{self.send(_field_name)}' where #{key}_id = #{self.id}"
35
+ self.connection.execute update_sql
24
36
  end
25
37
  end
38
+
39
+ self.class.class_eval <<-EVAL
40
+ define_method "#{_klass.table_name}_out_of_sync" do
41
+ #{self.name}.where("id in (SELECT c1.id FROM #{table_name} c1 INNER JOIN #{_klass.table_name} c2 on c2.id = c1.#{key}_id where c1.#{_denormalized_field_name} != c2.#{_field_name})")
42
+ end
43
+ EVAL
44
+
45
+ UPDATE_STATEMENTS.push update_sql
46
+ CLASSES.push self
26
47
  end
27
48
  end
28
49
  end
data/lib/tasks.rb ADDED
@@ -0,0 +1,5 @@
1
+ class DenormalizeFieldTask < Rails::Railtie
2
+ rake_tasks do
3
+ Dir[File.join(File.dirname(__FILE__),'lib/tasks/*.rake')].each { |f| load f }
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ namespace :denormalize do
2
+ task :show_outdated do
3
+ DenormalizeFields::CLASSES.each do |klass|
4
+ p "Out of sync for #{klass.name}: #{klass.out_of_sync.count}"
5
+ end
6
+ end
7
+
8
+ task :sync do
9
+ DenormalizeUpdater.sync_all
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+
2
+ require 'erb'
3
+ require 'uri'
4
+ require 'em-synchrony/activerecord'
5
+
6
+ class DbConnect
7
+ attr_accessor :config
8
+ def initialize
9
+ @db = URI.parse(ENV['DATABASE_URL'] || 'http://localhost')
10
+ if @db.scheme == 'postgres' # This section makes Heroku work
11
+ ActiveRecord::Base.establish_connection(
12
+ :adapter => @db.scheme == 'postgres' ? 'postgresql' : @db.scheme,
13
+ :host => @db.host,
14
+ :username => @db.user,
15
+ :password => @db.password,
16
+ :database => @db.path[1..-1],
17
+ :encoding => 'utf8'
18
+ )
19
+ else # And this is for my local environment
20
+ environment = ENV['DATABASE_URL'] ? 'production' : 'development'
21
+ @db = YAML.load(ERB.new(File.read('config/database.yml')).result)[environment]
22
+ ActiveRecord::Base.establish_connection(@db)
23
+ @config = ActiveRecord::Base.connection.pool.spec.config
24
+ end
25
+ end
26
+ end
27
+
28
+ # connect to the database
29
+ DbConnect.new
@@ -9,6 +9,17 @@ class Post < ActiveRecord::Base
9
9
  denormalizes category: :name
10
10
  end
11
11
 
12
+ describe DenormalizeUpdater do
13
+ let(:category) { Category.new(name: "News") }
14
+ let(:post) { Post.create(category: category) }
15
+
16
+ it "syncs all records" do
17
+ post.connection.execute("UPDATE posts set category_name = 'cool story';")
18
+ in_sync_post = Post.create(category: category)
19
+ Post.categories_out_of_sync.should == [post]
20
+ end
21
+ end
22
+
12
23
  describe "DenormalizeField" do
13
24
  let(:category) { Category.new(name: "News") }
14
25
  let(:post) { Post.new(category: category) }
data/spec/spec_helper.rb CHANGED
@@ -2,6 +2,7 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
3
  require 'rspec'
4
4
  require 'denormalize-field'
5
+ require 'db_connect'
5
6
 
6
7
  # Requires supporting files with custom matchers and macros, etc,
7
8
  # in ./support/ and its subdirectories.
@@ -15,18 +16,21 @@ RSpec.configure do |config|
15
16
  end
16
17
  end
17
18
 
18
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
19
- :database => File.dirname(__FILE__) + "/denormalize-field.sqlite3")
19
+ ActiveRecord::Base.establish_connection adapter:'postgresql', database: 'denormalizefielddev'
20
+
21
+ begin
22
+ ActiveRecord::Base.connection.drop_table(:categories)
23
+ ActiveRecord::Base.connection.drop_table(:posts)
24
+ rescue
25
+ end
20
26
 
21
- ActiveRecord::Base.connection.drop_table(:categories)
22
- ActiveRecord::Base.connection.drop_table(:posts)
23
27
  ActiveRecord::Base.connection.create_table(:categories) do |t|
24
28
  t.string :name
25
29
  t.timestamps
26
30
  end
27
31
 
28
32
  ActiveRecord::Base.connection.create_table(:posts) do |t|
29
- t.string :category_id
33
+ t.integer :category_id
30
34
  t.string :category_name
31
35
  t.timestamps
32
36
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: denormalize-field
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-15 00:00:00.000000000 Z
12
+ date: 2013-05-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
29
  version: 3.2.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: em-synchrony
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: pry
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -44,7 +60,7 @@ dependencies:
44
60
  - !ruby/object:Gem::Version
45
61
  version: '0'
46
62
  - !ruby/object:Gem::Dependency
47
- name: sqlite3
63
+ name: pg
48
64
  requirement: !ruby/object:Gem::Requirement
49
65
  none: false
50
66
  requirements:
@@ -139,8 +155,12 @@ files:
139
155
  - README.rdoc
140
156
  - Rakefile
141
157
  - VERSION
158
+ - config/database.yml
142
159
  - denormalize-field.gemspec
143
160
  - lib/denormalize-field.rb
161
+ - lib/tasks.rb
162
+ - lib/tasks/denormalize-tasks.rake
163
+ - spec/db_connect.rb
144
164
  - spec/denormalize-field_spec.rb
145
165
  - spec/schema.rb
146
166
  - spec/spec_helper.rb
@@ -159,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
159
179
  version: '0'
160
180
  segments:
161
181
  - 0
162
- hash: -2555535460486354925
182
+ hash: -308078005241704715
163
183
  required_rubygems_version: !ruby/object:Gem::Requirement
164
184
  none: false
165
185
  requirements: