persistize 0.0.3

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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ doc/
2
+ persistize-*.gem
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Luismi Cavallé & Sergio Gil
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.rdoc ADDED
@@ -0,0 +1,73 @@
1
+ =Persistize
2
+
3
+ Persistize is a Rails plugin for easy denormalization. It works just like +memoize+ but it stores the value as an attribute in the database. You only need to write a method with the denormalization logic and a field in the database with the same name of the method. The field will get updated each time the record is saved:
4
+
5
+ class Person < ActiveRecord::Base
6
+ def full_name
7
+ "#{first_name} #{last_name}"
8
+ end
9
+
10
+ persistize :full_name
11
+ end
12
+
13
+ ...
14
+
15
+ Person.create(:first_name => 'Jimi', :last_name => 'Hendrix')
16
+ Person.find_by_full_name('Jimi Hendrix') # #<Person id:1, first_name:"Jimi", last_name:"Hendrix", full_name:"Jimi Hendrix" ...>
17
+
18
+ ==Dependency
19
+
20
+ Sometimes you want to update the field not when the record is changed, but when some other associated records are. For example:
21
+
22
+ class Project < ActiveRecord::Base
23
+ has_many :tasks
24
+
25
+ def completed?
26
+ tasks.any? && tasks.all?(&:completed?)
27
+ end
28
+
29
+ persistize :completed?, :depending_on => :tasks
30
+
31
+ named_scope :completed, :conditions => { :completed => true }
32
+ end
33
+
34
+ class Task < ActiveRecord::Base
35
+ belongs_to :project
36
+ end
37
+
38
+ ...
39
+
40
+ project = Project.create(:name => 'Rails')
41
+ task = project.tasks.create(:name => 'Make it scale', :completed => false)
42
+ Project.completed # []
43
+
44
+ task.update_attributes(:completed => true)
45
+ Project.completed # [#<Project id:1, name:"Rails", completed:true ...>]
46
+
47
+ You can add more than one dependency using an array:
48
+
49
+ persistize :summary, :depending_on => [:projects, :people, :tasks]
50
+
51
+ These examples are just some of the possible applications of this pattern, your imagination is the limit =;-) If you can find better examples, please send them to us.
52
+
53
+ ==Install
54
+
55
+ ===As a plugin (Rails >= 2.1.0)
56
+
57
+ $ script/plugin install git://github.com/porras/persistize.git
58
+
59
+ ===As a gem (Rails >= 2.1.0)
60
+
61
+ # in config/environment.rb
62
+ config.gem "porras-persistize", :lib => "persistize", :source => "http://gems.github.com"
63
+
64
+ And then:
65
+
66
+ $ [sudo] rake gems:install
67
+
68
+ ==To-do
69
+
70
+ * More kinds of dependencies (+has_one+, <code>has_many :through</code>)
71
+ * Make cache optional (cache can cause records to be inconsistent if changed and not saved so it would be nice to be able to deactivate it)
72
+
73
+ Copyright (c) 2008 Luismi Cavallé & Sergio Gil, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the persistize plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the persistize plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Persistize'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ begin
25
+ require 'jeweler'
26
+ Jeweler::Tasks.new do |gemspec|
27
+ gemspec.name = "persistize"
28
+ gemspec.authors = ["Sergio Gil", "Luismi Cavallé"]
29
+ gemspec.email = "ballsbreaking@bebanjo.com"
30
+ gemspec.homepage = "http://github.com/bebanjo/persistize"
31
+ gemspec.summary = "Easy denormalization for your ActiveRecord models"
32
+ gemspec.extra_rdoc_files = ["README.rdoc"]
33
+ end
34
+ rescue LoadError
35
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'persistize'
data/lib/persistize.rb ADDED
@@ -0,0 +1,99 @@
1
+ module Persistize
2
+ module ClassMethods
3
+ def persistize(*args)
4
+ options = args.pop if args.last.is_a?(Hash)
5
+
6
+ args.each do |method|
7
+ attribute = method.to_s.sub(/\?$/, '')
8
+
9
+ original_method = :"_unpersistized_#{attribute}"
10
+ update_method = :"_update_#{attribute}"
11
+
12
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
13
+ alias #{original_method} #{method} # alias _unpersistized_full_name full_name
14
+ #
15
+ def #{method} # def full_name
16
+ if new_record? # if new_record?
17
+ #{original_method} # _unpersistized_full_name
18
+ else # else
19
+ self[:#{attribute}] # self[:full_name]
20
+ end # end
21
+ end # end
22
+ #
23
+ before_save :#{update_method} # before_save :_update_full_name
24
+ #
25
+ def #{update_method} # def _update_full_name
26
+ self[:#{attribute}] = #{original_method} # self[:full_name] = _unpersistized_full_name
27
+ true # return true to avoid canceling the save # true
28
+ end # end
29
+ #
30
+ def #{update_method}! # def _update_full_name!
31
+ #{update_method} # _update_full_name
32
+ save! if #{attribute}_changed? # save! if full_name_changed?
33
+ end # end
34
+ RUBY
35
+
36
+ if options && options[:depending_on]
37
+ dependencies = [options[:depending_on]].flatten
38
+
39
+ dependencies.each do |dependency|
40
+ generate_callback(reflections[dependency], update_method)
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def generate_callback(association, update_method)
50
+ callback_name = :"#{update_method}_in_#{self.to_s.underscore}_callback"
51
+ association_type = "#{association.macro}#{'_through' if association.through_reflection}"
52
+ generate_method = :"generate_#{association_type}_callback"
53
+ unless respond_to?(generate_method, true)
54
+ raise "#{association_type} associations are not supported by persistize"
55
+ end
56
+ send(generate_method, association, update_method, callback_name)
57
+ end
58
+
59
+ def generate_has_many_callback(association, update_method, callback_name)
60
+ association.klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
61
+ def #{callback_name} # def _update_completed_in_project_callback
62
+ return true unless parent_id = self[:#{association.primary_key_name}] # return true unless parent_id = self[:project_id]
63
+ parent = #{self.name}.find(parent_id) # parent = Project.find(parent_id)
64
+ parent.#{update_method}! # parent._update_completed!
65
+ end # end
66
+ after_save :#{callback_name} # after_save :_update_completed_in_project_callback
67
+ after_destroy :#{callback_name} # after_destroy :_update_completed_in_project_callback
68
+ RUBY
69
+ end
70
+
71
+ def generate_has_many_through_callback(association, update_method, callback_name)
72
+ association.klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
73
+ def #{callback_name} # def _update_completed_in_person_callback
74
+ return true unless through_id = self[:#{association.through_reflection.association_foreign_key}] # return true unless through_id = self[:project_id]
75
+ through = #{association.through_reflection.class_name}.find(through_id) # through = Project.find(through_id)
76
+ return true unless parent_id = through[:#{association.primary_key_name}] # return true unless parent_id = self[:person_id]
77
+ parent = #{self.name}.find(parent_id) # parent = Person.find(person_id)
78
+ parent.#{update_method}! # parent._update_completed!
79
+ end # end
80
+ after_save :#{callback_name} # after_save :_update_completed_in_person_callback
81
+ after_destroy :#{callback_name} # after_destroy :_update_completed_in_person_callback
82
+ RUBY
83
+ end
84
+
85
+ def generate_belongs_to_callback(association, update_method, callback_name)
86
+ association.klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
87
+ def #{callback_name} # def _update_project_name_in_task_callback
88
+ childs = #{self.name}.all(:conditions => {:#{association.primary_key_name} => id}) # childs = Task.all(:conditions => {:project_id => id})
89
+ childs.each(&:"#{update_method}!") # childs.each(&:"_update_project_name!")
90
+ end # end
91
+ after_save :#{callback_name} # after_save :_update_project_name_in_task_callback
92
+ after_destroy :#{callback_name} # after_destroy :_update_project_name_in_task_callback
93
+ RUBY
94
+ end
95
+
96
+ end
97
+ end
98
+
99
+ ActiveRecord::Base.extend(Persistize::ClassMethods)
@@ -0,0 +1,54 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{persistize}
5
+ s.version = "0.0.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Sergio Gil", "Luismi Cavall\303\251"]
9
+ s.date = %q{2009-05-22}
10
+ s.email = %q{ballsbreaking@bebanjo.com}
11
+ s.extra_rdoc_files = [
12
+ "README.rdoc"
13
+ ]
14
+ s.files = [
15
+ ".gitignore",
16
+ "MIT-LICENSE",
17
+ "README.rdoc",
18
+ "Rakefile",
19
+ "VERSION",
20
+ "init.rb",
21
+ "lib/persistize.rb",
22
+ "persistize.gemspec",
23
+ "test/models/company.rb",
24
+ "test/models/person.rb",
25
+ "test/models/project.rb",
26
+ "test/models/task.rb",
27
+ "test/persistize_test.rb",
28
+ "test/test_helper.rb"
29
+ ]
30
+ s.has_rdoc = true
31
+ s.homepage = %q{http://github.com/bebanjo/persistize}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.1}
35
+ s.summary = %q{Easy denormalization for your ActiveRecord models}
36
+ s.test_files = [
37
+ "test/models/company.rb",
38
+ "test/models/person.rb",
39
+ "test/models/project.rb",
40
+ "test/models/task.rb",
41
+ "test/persistize_test.rb",
42
+ "test/test_helper.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 2
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ else
51
+ end
52
+ else
53
+ end
54
+ end
@@ -0,0 +1,10 @@
1
+ class Company < ActiveRecord::Base
2
+ has_many :people
3
+ has_many :tasks
4
+
5
+ def summary
6
+ "#{name} has #{people.count} people and #{tasks.count} tasks"
7
+ end
8
+
9
+ persistize :summary, :depending_on => [ :people, :tasks ]
10
+ end
@@ -0,0 +1,20 @@
1
+ class Person < ActiveRecord::Base
2
+
3
+ has_many :projects
4
+ has_many :tasks, :through => :projects
5
+
6
+ def full_name
7
+ "#{first_name} #{last_name}"
8
+ end
9
+
10
+ def initials
11
+ "#{first_name.first}#{last_name.first}".upcase
12
+ end
13
+
14
+ def info
15
+ "#{full_name} has #{tasks.count} tasks in #{projects.count} projects"
16
+ end
17
+
18
+ persistize :full_name, :initials
19
+ persistize :info, :depending_on => [:projects, :tasks]
20
+ end
@@ -0,0 +1,10 @@
1
+ class Project < ActiveRecord::Base
2
+ has_many :tasks
3
+ belongs_to :person
4
+
5
+ def completed?
6
+ tasks.any? && tasks.all?(&:completed?)
7
+ end
8
+
9
+ persistize :completed?, :depending_on => :tasks
10
+ end
@@ -0,0 +1,9 @@
1
+ class Task < ActiveRecord::Base
2
+ belongs_to :project
3
+
4
+ def project_name
5
+ project && project.name
6
+ end
7
+
8
+ persistize :project_name, :depending_on => :project
9
+ end
@@ -0,0 +1,115 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class PersistizeTest < Test::Unit::TestCase
4
+
5
+ context "Using persistize" do
6
+
7
+ setup do
8
+ setup_db
9
+ end
10
+
11
+ teardown do
12
+ drop_db
13
+ end
14
+
15
+ context "a person" do
16
+
17
+ setup do
18
+ @person = Person.create(:first_name => "Jimi", :last_name => "Hendrix")
19
+ end
20
+
21
+ should "update the value of full name in the database when created" do
22
+ assert_equal('Jimi Hendrix', @person[:full_name])
23
+ assert_equal(@person, Person.find_by_full_name('Jimi Hendrix'))
24
+ end
25
+
26
+ should "update the value of full name in the database when updated" do
27
+ @person.update_attributes!(:first_name => "Axl", :last_name => "Rose")
28
+ assert_equal('Axl Rose', @person[:full_name])
29
+ assert_equal(@person, Person.find_by_full_name('Axl Rose'))
30
+ end
31
+
32
+ should "not update the calculated value until saved" do
33
+ @person.first_name = 'Axl'
34
+ assert_equal('Jimi Hendrix', @person.full_name)
35
+ # TODO: Rethink this behaviour. Do we want to cache or not?
36
+ end
37
+
38
+ should "call the method when reading before being created" do
39
+ person = Person.new(:first_name => "Jimi", :last_name => "Hendrix")
40
+ assert_equal('Jimi Hendrix', person.full_name)
41
+ end
42
+
43
+ should "also persistize #initials" do
44
+ assert_equal('JH', @person[:initials])
45
+ end
46
+
47
+ end
48
+
49
+ context "a project with tasks" do
50
+
51
+ setup do
52
+ @project = Project.create(:name => "Rails")
53
+ @task = @project.tasks.create(:completed => true)
54
+ end
55
+
56
+ should "update @project#completed? when a new task is created" do
57
+ Task.create(:project_id => @project.id, :completed => false)
58
+ assert !@project.reload[:completed]
59
+ end
60
+
61
+ should "update @project#completed? when a task is deleted" do
62
+ @task.destroy
63
+ assert !@project.reload[:completed]
64
+ end
65
+
66
+ should "update @project#completed? when a task is updated" do
67
+ @task.update_attributes!(:completed => false)
68
+ assert !@project.reload[:completed]
69
+ end
70
+
71
+ should "update each task's #project_name when the project name is updated" do
72
+ @project.update_attributes!(:name => "Merb")
73
+ assert_equal "Merb", @task.reload[:project_name]
74
+ end
75
+ end
76
+
77
+ context "a company with people and tasks" do
78
+
79
+ setup do
80
+ @company = Company.create(:name => "BeBanjo")
81
+ end
82
+
83
+ should "update summary when it is created" do
84
+ assert_equal("BeBanjo has 0 people and 0 tasks", @company.reload[:summary])
85
+ end
86
+
87
+ should "update summary when a person is created" do
88
+ @company.people.create(:first_name => 'Bruce', :last_name => 'Dickinson')
89
+ assert_equal("BeBanjo has 1 people and 0 tasks", @company.reload[:summary])
90
+ end
91
+
92
+ should "update summary when a task created" do
93
+ @company.tasks.create
94
+ assert_equal("BeBanjo has 0 people and 1 tasks", @company.reload[:summary])
95
+ end
96
+
97
+ end
98
+
99
+ context "a person with projects and tasks" do
100
+
101
+ setup do
102
+ @person = Person.create(:first_name => "Enjuto", :last_name => "Mojamuto")
103
+ @project = Project.create(:name => "La Hora Chanante", :person => @person)
104
+ Project.create(:name => "Wadus", :person => @person)
105
+ 3.times { Task.create(:project => @project)}
106
+ end
107
+
108
+ should "have info" do
109
+ assert_equal("Enjuto Mojamuto has 3 tasks in 2 projects", @person.reload[:info])
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,42 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'shoulda'
4
+ require 'activerecord'
5
+ require 'activesupport'
6
+ require 'ruby-debug'
7
+
8
+ require File.dirname(__FILE__) + '/../init'
9
+
10
+ ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__) + '/models'
11
+
12
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
13
+ ActiveRecord::Schema.verbose = false
14
+
15
+ def setup_db
16
+ ActiveRecord::Schema.define(:version => 1) do
17
+ create_table :people do |t|
18
+ t.string :first_name, :last_name, :full_name, :initials, :info
19
+ t.integer :company_id
20
+ end
21
+ create_table :projects do |t|
22
+ t.string :name
23
+ t.boolean :completed
24
+ t.integer :person_id
25
+ end
26
+ create_table :tasks do |t|
27
+ t.integer :project_id, :company_id
28
+ t.boolean :completed
29
+ t.string :project_name
30
+ end
31
+ create_table :companies do |t|
32
+ t.string :name, :summary
33
+ end
34
+ end
35
+ end
36
+
37
+ def drop_db
38
+ ActiveRecord::Base.connection.tables.each do |table|
39
+ ActiveRecord::Base.connection.drop_table(table)
40
+ end
41
+ end
42
+
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: persistize
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Sergio Gil
8
+ - "Luismi Cavall\xC3\xA9"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-10-29 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description:
18
+ email: ballsbreaking@bebanjo.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.rdoc
25
+ files:
26
+ - .gitignore
27
+ - MIT-LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION
31
+ - init.rb
32
+ - lib/persistize.rb
33
+ - persistize.gemspec
34
+ - test/models/company.rb
35
+ - test/models/person.rb
36
+ - test/models/project.rb
37
+ - test/models/task.rb
38
+ - test/persistize_test.rb
39
+ - test/test_helper.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/bebanjo/persistize
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: Easy denormalization for your ActiveRecord models
68
+ test_files:
69
+ - test/models/company.rb
70
+ - test/models/person.rb
71
+ - test/models/project.rb
72
+ - test/models/task.rb
73
+ - test/persistize_test.rb
74
+ - test/test_helper.rb