cti 0.1.0

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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjIzZTIxNmE3NDVkYzRhMjI4ZGRlYWZkYThkYTMyYmJlZmY5YTZkZQ==
5
+ data.tar.gz: !binary |-
6
+ ZDlkMzEwYTA5NGI5MjVmNDZkOTEyZDA1Y2IxNmE5MTUzNmMzMjY4ZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YWY4MWFmYjE0NTc5YWJkYTJjNDk0MmI4NWZiNjRkOGQyYWY0MzU4YmQ0OWQ4
10
+ NjEwYWIwOGQ5ZTIzOGIzYWZmMWEwZjAxMDEzY2E4ZThhZDkzZjQ1NjBjMzlk
11
+ NTJlMzVlZmFiYTA2NTYwM2FkMTI1NDI3NmU2MmJjZGRjMjliYTI=
12
+ data.tar.gz: !binary |-
13
+ YTgzOGJkOTUwMTAzZjFiOGEyYzBmZTNlM2VjOWNmNzI4Zjg1YTUyZDZlOWRi
14
+ NGZlZmM0NTcwZTliNWU5MGJlNWYyMTFhMTVkZWJiMzlhYjlmYjQyMWVlMjNk
15
+ MGI5ZTgzYzEwYjZlNDA4N2VlZjM3MWY2NTVkMWM3NjI5OWRjYTE=
@@ -0,0 +1,235 @@
1
+ h1. CTI
2
+
3
+ CTI is a gem that implements Multiple Table Inheritance for ActiveRecord models.
4
+
5
+ h2. Compatability
6
+
7
+ CTI has only been tested with Rails 4
8
+
9
+ h2. Installation
10
+
11
+ Simply add CTI to your Gemfile and bundle it up:
12
+
13
+ <pre>
14
+ gem 'cti'
15
+ </pre>
16
+
17
+ h2. Usage
18
+
19
+ CTI works by assigning one model as your @predecessor@, and one or more other models as it's @heir@.
20
+ The predecessor is the parent of it's heirs, and thereby implicitly gives it's heirs access to it's columns, and optionally exposing methods to them.
21
+
22
+ To mark a model as predecessor, simply use the @acts_as_predecessor@ class-method:
23
+
24
+ <pre>
25
+ class Post < ActiveRecord::Base
26
+ acts_as_predecessor
27
+ end
28
+ </pre>
29
+
30
+ To mark a model as heir, simply use the @acts_as_heir_of@ class-method, passing a symbol to the model that is to be the heirs predecessor.
31
+
32
+ <pre>
33
+ class BlogPost < ActiveRecord::Base
34
+ acts_as_heir_of :post
35
+ end
36
+ </pre>
37
+
38
+ This takes care of the model configuration. We however need to add two extra columns to the Posts table.
39
+ We need a @heir_id@ column of type @integer@ and a @heir_type@ column of type @string@.
40
+
41
+ <pre>
42
+ class CreatePosts < ActiveRecord::Migration
43
+ def self.up
44
+ create_table :posts do |t|
45
+ t.integer :heir_id
46
+ t.string :heir_type
47
+ t.string :title
48
+ t.timestamps
49
+ end
50
+ end
51
+
52
+ def self.down
53
+ drop_table :posts
54
+ end
55
+ end
56
+
57
+ class CreateBlogPosts < ActiveRecord::Migration
58
+ def self.up
59
+ create_table :blog_posts do |t|
60
+ t.text :body
61
+ end
62
+ end
63
+
64
+ def self.down
65
+ drop_table :blog_posts
66
+ end
67
+ end
68
+ </pre>
69
+
70
+ When this is done and the database is migrated, we can begin using the models.
71
+
72
+ h2. Creating new instances
73
+
74
+ Now we can simply call the following to create a new @BlogPost@
75
+
76
+ <pre>
77
+ blog_post = BlogPost.create(:title => "Wow", :body => "That's a nice blog post!")
78
+ </pre>
79
+
80
+ Notice that the @title@ attribute belongs to the @Post@ model, and the @body@ attribute belongs to the @BlogPost@ model.
81
+
82
+ h2. Attributes
83
+
84
+ We can directly access the @title@ attribute through @BlogPost@ and even change it's value
85
+
86
+ <pre>
87
+ blog_post.title # "Wow"
88
+ blog_post.title = "Oh boy!"
89
+ blog_post.save!
90
+ blog_post.title # "Oh boy!"
91
+ </pre>
92
+
93
+ We can also update attributes like normal through @update_attributes@
94
+
95
+ <pre>
96
+ blog_post.update_attributes(:title => "Hubba Hubba", :body => "Nice blog post!")
97
+ blog_post.title # "Hubba Hubba"
98
+ blog_post.body # "Nice blog post!"
99
+ </pre>
100
+
101
+ h2. Methods
102
+
103
+ If we want to expose some methods from our predecessor model to it's heirs, we can do so when calling the @acts_as_predecessor@ class-method
104
+
105
+ <pre>
106
+ class Post < ActiveRecord::Base
107
+
108
+ acts_as_predecessor :exposes => :hello
109
+
110
+ def hello
111
+ "Hi there!"
112
+ end
113
+
114
+ end
115
+ </pre>
116
+
117
+ Now all heirs of @Post@ will have a hello-method, which we can call directly on the heir-model:
118
+
119
+ <pre>
120
+ blog_post = BlogPost.create(:title => "I am full", :body => "of methods...")
121
+ blog_post.hello # "Hi there!"
122
+ </pre>
123
+
124
+ If you for some reason need to override the method in one of your heir-models, you can simply implement the method, and it will override the method from the predecessor.
125
+
126
+ <pre>
127
+ class BlogPost < ActiveRecord::Base
128
+
129
+ acts_as_heir_of :post
130
+
131
+ def hello
132
+ "Yo!"
133
+ end
134
+
135
+ end
136
+ </pre>
137
+
138
+ Calling the @hello@ method on BlogPost will now yield another result:
139
+
140
+ <pre>
141
+ blog_post = BlogPost.create(:title => "I have", :body => "my own methods...")
142
+ blog_post.hello # "Yo!"
143
+ </pre>
144
+
145
+ If we need to combine the local method in the heir, with the method in the predecessor, we can do so through the @predecessor@ method of the heir model, kinda like you would use @super@.
146
+
147
+ <pre>
148
+ class BlogPost < ActiveRecord::Base
149
+
150
+ acts_as_heir_of :post
151
+
152
+ def hello
153
+ "Yo! #{predecessor.hello}"
154
+ end
155
+
156
+ end
157
+ </pre>
158
+
159
+ The result would now be a combination of the local method in the heir, and the method in the predecessor:
160
+
161
+ <pre>
162
+ blog_post = BlogPost.create(:title => "I have", :body => "my own methods...")
163
+ blog_post.hello # "Yo! Hi there!"
164
+ </pre>
165
+
166
+ h2. Listing and filtering
167
+
168
+ To list all your wonderful heir models you do as you normally would in ActiveRecord, with one single exception.
169
+
170
+ Normally you would call something like this, to show all @BlogPosts@
171
+
172
+ <pre>
173
+ @posts = BlogPost.all
174
+ </pre>
175
+
176
+ This however will result in 1 + the number of returned records SQL calls, which is hardly good.
177
+ Instead you need to tell ActiveRecord that it should include the predecessors of the heirs, like so:
178
+
179
+ <pre>
180
+ @posts = BlogPost.all(:include => :predecessor)
181
+ </pre>
182
+
183
+ We now only call the database twice; Once for loading the heirs, and once for loading all referenced predecessors.
184
+
185
+ Another gotcha is when you need to filter the heirs. You can't directly filter by attributes from the predecessor model.
186
+ So in our example where we have the @title@ attribute in the @Post@ model, we can't do the following:
187
+
188
+ <pre>
189
+ @posts = BlogPost.where("title = 'test'")
190
+ </pre>
191
+
192
+ Instead we need to join the predecessor attributes by its association, like so:
193
+
194
+ <pre>
195
+ @posts = BlogPost.joins(:predecessor).where("posts.title = 'test'")
196
+ </pre>
197
+
198
+ Behind the scenes, CTI works just like a simple ActiveRecord association, so it makes sense.
199
+
200
+ h2. Timestamps
201
+
202
+ If all of your heir-models needs timestamps, then you can simply add timestamps to the predecessor model, and omit them from the heir-models.
203
+ CTI will make sure, that whenever you update your heir-model, the @updated_at@ timestamp in the predecessor model will be updated.
204
+
205
+ h2. A note on destruction
206
+
207
+ CTI depends on the destroy-method of the models, and as such you should always delete predecessor and heir models by calling the @destroy@ method on either, and NEVER by calling the @delete@ or @delete_all@ methods.
208
+
209
+ If you absolutely need to do a direct delete in the database, then you need to manually remove the counterpart as well.
210
+
211
+ For instance, if you manually delete a @BlogPost@ that is heir of @Post@, then you need to first find the right @Post@, then delete the heir and finally delete the predecessor.
212
+
213
+ h2. Advanced usage
214
+
215
+ It is always possible to traverse between a predecessor and it's associated heir, through the @predecessor@ method of an heir, and the @heir@ method of a predecessor.
216
+
217
+ h2. Credits
218
+
219
+ Credits goes out to Thomas Dippel @ Benjamin Media A/S for the predecessor Heritage and Gerry from TechSpry.com for the idea for this implementation:
220
+ http://techspry.com/ruby_and_rails/multiple-table-inheritance-in-rails-3/
221
+
222
+ h2. Contributing to cti
223
+
224
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
225
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
226
+ * Fork the project.
227
+ * Start a feature/bugfix branch.
228
+ * Commit and push until you are happy with your contribution.
229
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
230
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
231
+
232
+ h2. License
233
+
234
+ <a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/"><img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by-sa/3.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/InteractiveResource" property="dct:title" rel="dct:type">CTI</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="https://github.com/seyedrazavi/cti" property="cc:attributionName" rel="cc:attributionURL">Seyed Razavi @ Education Apps</a> is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-ShareAlike 3.0 Unported License</a>.<br />Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="https://github.com/dipth/Heritage" rel="dct:source">Heritage</a>
235
+
@@ -0,0 +1,5 @@
1
+ require 'rails'
2
+
3
+ module Cti
4
+ require 'cti/railtie'
5
+ end
@@ -0,0 +1,103 @@
1
+ module Cti
2
+ module ActiveRecord
3
+ module ActsAsHeir
4
+
5
+ def child_of(parent_symbol)
6
+ acts_as_heir_of(parent_symbol)
7
+ end
8
+
9
+ def acts_as_heir_of(predecessor_symbol)
10
+ extend ClassMethods
11
+ include InstanceMethods
12
+
13
+ if predecessor_symbol.is_a?(String)
14
+ predecessor_symbol = predecessor_symbol.to_sym
15
+ end
16
+
17
+ class_attribute :_predecessor_klass, :_predecessor_symbol
18
+ self._predecessor_symbol = predecessor_symbol
19
+ self._predecessor_klass = predecessor_symbol.to_s.camelize.constantize # Object.const_get(predecessor_symbol.to_s.capitalize)
20
+
21
+ has_one :predecessor, :as => :heir, :class_name => predecessor_symbol.to_s.camelize, :autosave => true, :dependent => :destroy
22
+
23
+ alias_method_chain :predecessor, :build
24
+
25
+ # Expose columns from the predecessor
26
+ self._predecessor_klass.columns.reject{|c| c.primary || c.name =~ /^heir_/}.map(&:name).each do |att|
27
+ define_method(att) do
28
+ predecessor.send(att)
29
+ end
30
+ define_method("#{att}=") do |val|
31
+ predecessor.send("#{att}=",val)
32
+ end
33
+ end
34
+
35
+ # Expose associations from the predecessor
36
+ self._predecessor_klass.reflect_on_all_associations.reject{|a| a.name == :heir}.each do |association|
37
+ define_method(association.name) do
38
+ predecessor.send(association.name)
39
+ end
40
+ define_method("#{association.name}=") do |val|
41
+ predecessor.send("#{association.name}=",val)
42
+ end
43
+ end
44
+
45
+ # Include validations from the predecessor
46
+ self._predecessor_klass.validators.each do |validator|
47
+ self.validates_with(validator.class, :attributes => validator.attributes, :options => validator.options)
48
+ end
49
+
50
+ # We need to make sure that updated_at values in the predecessor table is updated when the heir is saved.
51
+ before_update :touch_predecessor, :unless => lambda { predecessor.changed? }
52
+
53
+ # Expose methods from predecessor
54
+ self._predecessor_klass.get_heritage_exposed_methods.each do |method_symbol|
55
+ define_method(method_symbol.to_s) do |*args|
56
+ predecessor.send(method_symbol.to_s, *args)
57
+ end
58
+ end
59
+
60
+ # This piece deals with errors names
61
+ # and simply strips "predecessor." part from all the predecessor errors.
62
+ after_validation do
63
+ new_errors = {}
64
+ keys_to_delete = []
65
+ errors.each do |e|
66
+ if e =~ /^predecessor/
67
+ new_e = e.to_s.sub("predecessor.", '')
68
+ new_errors[new_e] = errors[e].first
69
+ keys_to_delete << e
70
+ end
71
+ end
72
+ keys_to_delete.each { |k| errors.delete(k) }
73
+ new_errors.each { |k,v| errors.add(k, v) }
74
+ end
75
+ end
76
+
77
+ module ClassMethods
78
+
79
+ end
80
+
81
+ module InstanceMethods
82
+ def heritage
83
+ predecessor
84
+ end
85
+
86
+ def lineage
87
+ self
88
+ end
89
+
90
+ def predecessor_with_build(attributes = {})
91
+ predecessor_without_build || build_predecessor(attributes)
92
+ end
93
+
94
+ def touch_predecessor
95
+ if self.changed?
96
+ predecessor.touch
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,49 @@
1
+ module Cti
2
+ module ActiveRecord
3
+ module ActsAsPredecessor
4
+
5
+ def parent_model(options = {})
6
+ acts_as_predecessor(options)
7
+ end
8
+
9
+ def acts_as_predecessor(options = {})
10
+ extend ClassMethods
11
+ include InstanceMethods
12
+
13
+ options[:exposes] ||= []
14
+ class_attribute :_acts_as_predecessor_settings
15
+ self._acts_as_predecessor_settings = options
16
+
17
+ belongs_to :heir, :polymorphic => true
18
+
19
+ before_update :touch_heir, :unless => lambda { heir.try(:changed?) }
20
+ end
21
+
22
+ module ClassMethods
23
+
24
+ def get_heritage_exposed_methods
25
+ result = self._acts_as_predecessor_settings[:exposes]
26
+ result.is_a?(Array) ? result : [result]
27
+ end
28
+
29
+ end
30
+
31
+ module InstanceMethods
32
+ def heritage
33
+ self
34
+ end
35
+
36
+ def lineage
37
+ heir
38
+ end
39
+
40
+ def touch_heir
41
+ if self.changed?
42
+ heir.try(:touch)
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ require 'rails'
2
+
3
+ module Cti
4
+
5
+ class Railtie < Rails::Railtie
6
+
7
+ initializer 'cti' do |app|
8
+
9
+ ActiveSupport.on_load(:active_record) do
10
+ require 'cti/active_record/acts_as_predecessor'
11
+ require 'cti/active_record/acts_as_heir'
12
+ ::ActiveRecord::Base.send(:extend, Cti::ActiveRecord::ActsAsPredecessor)
13
+ ::ActiveRecord::Base.send(:extend, Cti::ActiveRecord::ActsAsHeir)
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
20
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cti
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Seyed Razavi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rdoc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '3.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '3.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: jeweler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 2.0.1
48
+ - - ~>
49
+ - !ruby/object:Gem::Version
50
+ version: '2.0'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: 2.0.1
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ description: A gem for implementing multiple table inheritance in rails 4
62
+ email: seyed@educationapps.co.uk
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files:
66
+ - README.textile
67
+ files:
68
+ - README.textile
69
+ - lib/cti.rb
70
+ - lib/cti/active_record/acts_as_heir.rb
71
+ - lib/cti/active_record/acts_as_predecessor.rb
72
+ - lib/cti/railtie.rb
73
+ homepage: http://github.com/seyedrazavi/cti
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: A gem for implementing multiple table inheritance in rails 4
97
+ test_files: []