acts_as_relation 0.0.5 → 0.1

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 CHANGED
@@ -3,4 +3,7 @@
3
3
  .rvmrc
4
4
  Gemfile.lock
5
5
  pkg/*
6
-
6
+ tmp/*
7
+ doc/*
8
+ *.sqlite3
9
+ *.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/README.rdoc ADDED
@@ -0,0 +1,98 @@
1
+ = ActsAsRelation
2
+
3
+ A +acts_as+ relationship sets up a one-to-one connection with another model,
4
+ such that declaring model inherits from other model (with separate database
5
+ tabels). For example in a shop all products have common attributes (+name+,
6
+ +price+, +image+ ...), while each type of them has their specific attributes,
7
+ +pen+ has +color+, +book+ has +author+ and +publisher+ and so on.
8
+
9
+ ActiveRecord only supports singletable inheritance, but with single table inheritance
10
+ number of attributes on parent model (product in this example) grow exponentially,
11
+ and must of them will always stay +NULL+.
12
+
13
+ +acts_as+ use a polymorphic +has_one+
14
+ association to simulate a multi-table inheritance. For the shop example you'd
15
+ declare the product as a +supermodel+ and all types of it as +acts_as+ +:product+
16
+ (if you prefer you can use their aliases +is_a+ and +is_a_superclass+)
17
+
18
+ class Product < ActiveRecord::Base
19
+ acts_as_superclass
20
+ end
21
+
22
+ class Pen < ActiveRecord::Base
23
+ acts_as :product
24
+ end
25
+
26
+ class Book < ActiveRecord::Base
27
+ acts_as :product
28
+ end
29
+
30
+ To make this work, you need to declare both a foreign key column and a type column
31
+ in the model that declares superclass. To do this you can set +:as_relation_superclass+
32
+ option to +true+ on +products+ +create_table+ (or pass it name of the association):
33
+
34
+ create_table :products, :as_relation_superclass => true do |t|
35
+ # ...
36
+ end
37
+
38
+ Or declare them as you do on a +polymorphic+ +belongs_to+ association, it this case
39
+ you must pass name to +acts_as+ in +:as+ option:
40
+
41
+ change_table :products do |t|
42
+ t.integer :producible_id
43
+ t.string :producible_type
44
+ end
45
+
46
+ class Pen < ActiveRecord::Base
47
+ acts_as :product, :as => :producible
48
+ end
49
+
50
+ Now +Pen+ and +Book+ *act* *as* +Product+. This means that they inherit +Product+
51
+ _attributes_, _associations_, _validations_ and _methods_.
52
+
53
+ To see its functionality lets add some stuff to product:
54
+
55
+ class Product
56
+ validates_presence_of :name, :price
57
+
58
+ def to_s
59
+ "#{name} $#{price}"
60
+ end
61
+ end
62
+
63
+ now we can to things like this:
64
+
65
+ Pen.create :name => "Nice Pen", :price => 1.3, :color => "Red"
66
+ Pen.where "name = ?", "Some Pen"
67
+ pen = Pen.new
68
+ pen.valid? # => false
69
+ pen.errors.keys # => [:name, :price]
70
+ Pen.first.to_s # => "Nice Pen $1.3"
71
+
72
+ When you declare an +acts_as+ relation, the declaring class automatically gains parent
73
+ methods (includeing accessors) so you can access them directly.
74
+
75
+ On the other hand you can always access a specific object from its parent by calling +specific+ method on it:
76
+
77
+ Product.first.specific # will return a specific product, a pen for example
78
+
79
+ == Options
80
+ The +acts_as+ relation support these options:
81
+
82
+ * +:as+
83
+ * +:auto_join+
84
+ * +:class_name+
85
+ * +:conditions+
86
+ * +:dependent+
87
+ * +:include+
88
+
89
+ when +:auto_join+ option set to +true+ (wich is by default), every query on child
90
+ will automatically joins with paren. For example:
91
+
92
+ Pen.where("name = ?", "somename")
93
+
94
+ will result in the following SQL:
95
+
96
+ SELECT "pens".* FROM "pens" INNER JOIN "products" ON "products"."as_product_id" = "pens"."id" AND "products"."as_product_type" = 'Pen' WHERE (name = 'somename')
97
+
98
+ All other options are same as +has_one+ options.
data/Rakefile CHANGED
@@ -1,12 +1,7 @@
1
1
  require 'rake'
2
- require 'rake/testtask'
2
+ require 'rspec/core/rake_task'
3
3
 
4
- task :default => :test
4
+ task :default => :spec
5
5
 
6
- desc 'Test the acts_as_relation plugin.'
7
- Rake::TestTask.new(:test) do |t|
8
- t.libs << 'lib'
9
- t.libs << 'test'
10
- t.pattern = 'test/**/*_test.rb'
11
- t.verbose = true
12
- end
6
+ desc 'Run specs'
7
+ RSpec::Core::RakeTask.new
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
6
6
 
7
7
  # Description Meta...
8
8
  s.name = 'acts_as_relation'
9
- s.version = ActiveRecord::Acts::AsRelation::VERSION
9
+ s.version = ActiveRecord::ActsAsRelation::VERSION
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.author = 'Hassan Zamani'
12
12
  s.email = 'hsn.zamani@gmail.com'
@@ -23,8 +23,9 @@ Gem::Specification.new do |s|
23
23
 
24
24
 
25
25
  # Dependencies (installed via 'bundle install')...
26
- s.add_development_dependency("bundler")
27
- s.add_development_dependency("rake")
28
- s.add_development_dependency("sqlite3")
29
- s.add_development_dependency("activerecord")
26
+ s.add_development_dependency "rake"
27
+ s.add_development_dependency "bundler"
28
+ s.add_development_dependency "sqlite3"
29
+ s.add_development_dependency "rspec"
30
+ s.add_dependency "activerecord"
30
31
  end
@@ -0,0 +1,137 @@
1
+ module ActiveRecord
2
+
3
+ module ActsAsModules end
4
+
5
+ module ActsAsRelation
6
+ extend ActiveSupport::Concern
7
+
8
+ module AccessMethods
9
+ protected
10
+ def define_acts_as_accessors(attribs, model_name)
11
+ attribs.each do |attrib|
12
+ class_eval <<-EndEval
13
+ def #{attrib}
14
+ #{model_name}.#{attrib}
15
+ end
16
+
17
+ def #{attrib}=(value)
18
+ #{model_name}.#{attrib} = value
19
+ end
20
+
21
+ def #{attrib}?
22
+ #{model_name}.#{attrib}?
23
+ end
24
+ EndEval
25
+ end
26
+ end
27
+ end
28
+
29
+ module ClassMethods
30
+ def acts_as(model_name, options={})
31
+ name = model_name.to_s.underscore.singularize
32
+ class_name = options[:class_name] || name.camelcase
33
+ association_name = options[:as] || acts_as_association_name(name)
34
+ module_name = "ActsAs#{name.camelcase}"
35
+
36
+ unless ActiveRecord::ActsAsModules.const_defined? module_name
37
+ # Create ActsAsModel module
38
+ acts_as_model = Module.new
39
+ ActiveRecord::ActsAsModules.const_set(module_name, acts_as_model)
40
+
41
+ has_one_options = {
42
+ :as => association_name,
43
+ :class_name => class_name,
44
+ :autosave => true,
45
+ :validate => false,
46
+ :dependent => options.fetch(:dependent, :destroy),
47
+ :include => options[:include],
48
+ :conditions => options[:conditions]
49
+ }
50
+
51
+ acts_as_model.module_eval <<-EndEval
52
+ def self.included(base)
53
+ base.has_one :#{name}, #{has_one_options}
54
+ base.validate :#{name}_must_be_valid
55
+ base.alias_method_chain :#{name}, :autobuild
56
+
57
+ base.extend ActiveRecord::ActsAsRelation::AccessMethods
58
+ all_attributes = #{class_name}.content_columns.map(&:name)
59
+ ignored_attributes = ["created_at", "updated_at", "#{association_name}_id", "#{association_name}_type"]
60
+ associations = #{class_name}.reflect_on_all_associations.map! { |assoc| assoc.name } - ["#{association_name}"]
61
+ attributes_to_delegate = all_attributes - ignored_attributes + associations
62
+ base.send :define_acts_as_accessors, attributes_to_delegate, "#{name}"
63
+ end
64
+
65
+ def #{name}_with_autobuild
66
+ #{name}_without_autobuild || build_#{name}
67
+ end
68
+
69
+ def method_missing method, *arg, &block
70
+ raise NoMethodError if method.to_s == 'id' || method.to_s == '#{name}'
71
+
72
+ #{name}.send(method, *arg, &block)
73
+ rescue NoMethodError
74
+ super
75
+ end
76
+
77
+ def respond_to?(method, include_private_methods = false)
78
+ super || #{name}.respond_to?(method, include_private_methods)
79
+ end
80
+
81
+ protected
82
+
83
+ def #{name}_must_be_valid
84
+ unless #{name}.valid?
85
+ #{name}.errors.each do |att, message|
86
+ errors.add(att, message)
87
+ end
88
+ end
89
+ end
90
+ EndEval
91
+ end
92
+
93
+ class_eval do
94
+ include "ActiveRecord::ActsAsModules::#{module_name}".constantize
95
+ end
96
+
97
+ if options.fetch :auto_join, true
98
+ class_eval "default_scope joins(:#{name})"
99
+ end
100
+
101
+ instance_eval <<-EndEval
102
+ def acts_as_other_model?
103
+ true
104
+ end
105
+
106
+ def acts_as_model_name
107
+ :#{name}
108
+ end
109
+ EndEval
110
+ end
111
+ alias :is_a :acts_as
112
+
113
+ def acts_as_superclass
114
+ association_name = acts_as_association_name
115
+
116
+ class_eval <<-EndEval
117
+ belongs_to :#{association_name}, :polymorphic => true
118
+
119
+ def specific
120
+ self.#{association_name}
121
+ end
122
+ alias :specific_class :specific
123
+ EndEval
124
+ end
125
+ alias :is_a_superclass :acts_as_superclass
126
+
127
+ def acts_as_association_name model_name=nil
128
+ model_name ||= self.name
129
+ "as_#{model_name.to_s.demodulize.singularize.underscore}"
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ class ActiveRecord::Base
136
+ include ActiveRecord::ActsAsRelation
137
+ end
@@ -0,0 +1,30 @@
1
+ module ActiveRecord
2
+ module ActsAsRelationSuperclassMigration
3
+ def self.included(base)
4
+ base.class_eval do
5
+ alias_method_chain :create_table, :as_relation_superclass
6
+ end
7
+ end
8
+
9
+ def create_table_with_as_relation_superclass(table_name, options = {})
10
+ create_table_without_as_relation_superclass(table_name, options) do |td|
11
+ if superclass = options[:as_relation_superclass]
12
+ association_name = if superclass.is_a? Symbol or superclass.is_a? String then
13
+ superclass
14
+ else
15
+ ActiveRecord::Base.acts_as_association_name table_name
16
+ end
17
+
18
+ td.integer "#{association_name}_id"
19
+ td.string "#{association_name}_type"
20
+ end
21
+
22
+ yield td if block_given?
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ module ActiveRecord::ConnectionAdapters::SchemaStatements
29
+ include ActiveRecord::ActsAsRelationSuperclassMigration
30
+ end
@@ -1,2 +1,2 @@
1
- require 'active_record/acts/as_relation'
2
- require 'active_record/acts/as_relation_superclass_migration'
1
+ require 'active_record/acts_as_relation'
2
+ require 'active_record/acts_as_relation_superclass_migration'
data/lib/version.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  module ActiveRecord
2
- module Acts
3
- module AsRelation
4
- VERSION = "0.0.5"
5
- end
2
+ module ActsAsRelation
3
+ VERSION = "0.1"
6
4
  end
7
5
  end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe "create_table acts_as_superclass" do
4
+ before :all do
5
+ ActsAsSuperclassSchema.migrate
6
+ end
7
+
8
+ it "creates foreign key and type columns on" do
9
+ name = Product.acts_as_association_name
10
+ Product.attribute_names.should include("#{name}_id")
11
+ Product.attribute_names.should include("#{name}_type")
12
+ end
13
+ end
14
+
15
+ describe "create_table acts_as_superclass option withname" do
16
+ before :all do
17
+ ActsAsSuperclassWithNameSchema.migrate
18
+ end
19
+
20
+ it "creates foreign key and type columns on" do
21
+ OtherProduct.attribute_names.should include("producible_id")
22
+ OtherProduct.attribute_names.should include("producible_type")
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ class Product < ActiveRecord::Base
2
+ acts_as_superclass
3
+ end
4
+
5
+ class OtherProduct < ActiveRecord::Base
6
+ acts_as_superclass
7
+ end
@@ -0,0 +1,33 @@
1
+ module ActsAsSuperclassSchema
2
+ def self.migrate
3
+ ActiveRecord::Base.establish_connection(
4
+ :adapter => "sqlite3",
5
+ :database => ":memory:"
6
+ )
7
+ ActiveRecord::Schema.define do
8
+ suppress_messages do
9
+ create_table :products, :as_relation_superclass => true do |t|
10
+ end
11
+ end
12
+ end
13
+
14
+ require Pathname(__FILE__).parent.join("models.rb")
15
+ end
16
+ end
17
+
18
+ module ActsAsSuperclassWithNameSchema
19
+ def self.migrate
20
+ ActiveRecord::Base.establish_connection(
21
+ :adapter => "sqlite3",
22
+ :database => ":memory:"
23
+ )
24
+ ActiveRecord::Schema.define do
25
+ suppress_messages do
26
+ create_table :other_products, :as_relation_superclass => :producible do |t|
27
+ end
28
+ end
29
+ end
30
+
31
+ require Pathname(__FILE__).parent.join("models.rb")
32
+ end
33
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Submodel" do
4
+ before :all do
5
+ ActsAsRelationSchema.migrate
6
+ end
7
+
8
+ it "inherits Supermodel attributes" do
9
+ pen = Pen.new
10
+ ['name', 'name=', 'name_changed?', 'name_was',
11
+ 'price', 'price=', 'price_changed?', 'price_was'].each do |m|
12
+ pen = Pen.create :name => 'RedPen', :price => 0.8, :color => 'red'
13
+ pen.should respond_to(m)
14
+ end
15
+
16
+ pen = Pen.create :name => 'RedPen', :price => 0.8, :color => 'red'
17
+ pen.name.should == 'RedPen'
18
+ pen.price.should == 0.8
19
+ pen.color.should == 'red'
20
+
21
+ pen.price = 0.9
22
+ pen.price_changed?.should be_true
23
+ pen.price_was.should == 0.8
24
+ end
25
+
26
+ it "inherits Supermodel associations" do
27
+ store = Store.new
28
+ pen = Pen.new
29
+ pen.store = store
30
+ pen.store.should == store
31
+ pen.product.store.should == store
32
+ end
33
+
34
+ it "inherits Supermodel validations" do
35
+ pen = Pen.new
36
+ pen.should be_invalid
37
+ pen.errors.keys.should include(:name, :price, :color)
38
+ end
39
+
40
+ it "inherits Supermodel methods" do
41
+ pen = Pen.create :name => 'RedPen', :price => 0.8, :color => 'red'
42
+ pen.should respond_to('parent_method')
43
+ pen.parent_method.should == "RedPen - 0.8"
44
+ end
45
+
46
+ it "should raise NoMethodEror on unexisting method calls" do
47
+ pen = Pen.create :name => 'RedPen', :price => 0.8, :color => 'red'
48
+ lambda { pen.unexisted_method }.should raise_error(NoMethodError)
49
+ end
50
+
51
+ it "destroies Supermodel on destroy" do
52
+ pen = Pen.create :name => 'RedPen', :price => 0.8, :color => 'red'
53
+ product_id = pen.product.id
54
+ pen.destroy
55
+ lambda { Product.find product_id }.should raise_error(ActiveRecord::RecordNotFound)
56
+ end
57
+
58
+ describe "#acts_as_other_model?" do
59
+ it "return true on models wich acts_as other ones" do
60
+ Pen.acts_as_other_model?.should be_true
61
+ end
62
+ end
63
+
64
+ describe "#acts_as_model_name" do
65
+ it "returns name of model wich it acts as" do
66
+ Pen.acts_as_model_name.should == :product
67
+ end
68
+ end
69
+
70
+ describe "Query Interface" do
71
+ describe "auto_join" do
72
+ it "automaticaly joins Supermodel on Submodel queries" do
73
+ pen = Pen.create :name => 'RedPen', :price => 0.8, :color => 'red'
74
+ Pen.create :name => 'RedPen2', :price => 1.2, :color => 'red'
75
+ Pen.create :name => 'BluePen', :price => 1.2, :color => 'blue'
76
+ lambda { Pen.where("price > 1").to_a }.should_not raise_error(ActiveRecord::StatementInvalid)
77
+ Pen.where("name = ?", "RedPen").should include(pen)
78
+ end
79
+
80
+ it "can be disabled by setting auto_join option to false" do
81
+ lambda { Pencil.where("name = 1").to_a }.should raise_error(ActiveRecord::StatementInvalid)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ describe "Supermodel" do
88
+ describe "#specific" do
89
+ it "returns the specific subclass object" do
90
+ pen = Pen.create :name => 'RedPen', :price => 0.8, :color => 'red'
91
+ pen.product.specific.should == pen
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,25 @@
1
+ class Store < ActiveRecord::Base
2
+ has_many :products
3
+ end
4
+
5
+ class Product < ActiveRecord::Base
6
+ acts_as_superclass
7
+
8
+ belongs_to :store
9
+ validates_presence_of :name, :price
10
+
11
+ def parent_method
12
+ "#{name} - #{price}"
13
+ end
14
+ end
15
+
16
+ class Pen < ActiveRecord::Base
17
+ acts_as_superclass
18
+
19
+ acts_as :product
20
+ validates_presence_of :color
21
+ end
22
+
23
+ class Pencil < ActiveRecord::Base
24
+ acts_as :pen
25
+ end
@@ -0,0 +1,28 @@
1
+ module ActsAsRelationSchema
2
+ def self.migrate
3
+ ActiveRecord::Base.establish_connection(
4
+ :adapter => "sqlite3",
5
+ :database => ":memory:"
6
+ )
7
+ ActiveRecord::Schema.define do
8
+ suppress_messages do
9
+ create_table :stores do |t|
10
+ t.string :store_name
11
+ end
12
+
13
+ create_table :products, :as_relation_superclass => true do |t|
14
+ t.string :name
15
+ t.float :price
16
+ end
17
+
18
+ create_table :pens, :as_relation_superclass => true do |t|
19
+ t.string :color
20
+ end
21
+
22
+ create_table :pencils
23
+ end
24
+ end
25
+
26
+ require Pathname(__FILE__).parent.join("models.rb")
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ require 'active_record'
2
+ require 'acts_as_relation'
3
+
4
+ Dir[Pathname(__FILE__).parent.join "*/*schema.rb"].each {|f| require f}
5
+
6
+ RSpec.configure do |config|
7
+ config.treat_symbols_as_metadata_keys_with_true_values = true
8
+ config.run_all_when_everything_filtered = true
9
+ config.filter_run :focus
10
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_relation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: '0.1'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-23 00:00:00.000000000 Z
12
+ date: 2012-02-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: bundler
16
- requirement: &10491360 !ruby/object:Gem::Requirement
15
+ name: rake
16
+ requirement: &17449100 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *10491360
24
+ version_requirements: *17449100
25
25
  - !ruby/object:Gem::Dependency
26
- name: rake
27
- requirement: &10490800 !ruby/object:Gem::Requirement
26
+ name: bundler
27
+ requirement: &17448680 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *10490800
35
+ version_requirements: *17448680
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: sqlite3
38
- requirement: &10490200 !ruby/object:Gem::Requirement
38
+ requirement: &17448260 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *10490200
46
+ version_requirements: *17448260
47
47
  - !ruby/object:Gem::Dependency
48
- name: activerecord
49
- requirement: &10489620 !ruby/object:Gem::Requirement
48
+ name: rspec
49
+ requirement: &17447840 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,18 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *10489620
57
+ version_requirements: *17447840
58
+ - !ruby/object:Gem::Dependency
59
+ name: activerecord
60
+ requirement: &17447420 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *17447420
58
69
  description: This 'acts_as' extension provides multi-table inheritance for rails models.
59
70
  email: hsn.zamani@gmail.com
60
71
  executables: []
@@ -62,18 +73,22 @@ extensions: []
62
73
  extra_rdoc_files: []
63
74
  files:
64
75
  - .gitignore
76
+ - .rspec
65
77
  - Gemfile
66
- - README.markdown
78
+ - README.rdoc
67
79
  - Rakefile
68
80
  - acts_as_relation.gemspec
69
- - init.rb
70
- - lib/active_record/acts/as_relation.rb
71
- - lib/active_record/acts/as_relation_superclass_migration.rb
81
+ - lib/active_record/acts_as_relation.rb
82
+ - lib/active_record/acts_as_relation_superclass_migration.rb
72
83
  - lib/acts_as_relation.rb
73
84
  - lib/version.rb
74
- - test/acts_as_relation_test.rb
75
- - test/schema.rb
76
- - test/test_helper.rb
85
+ - spec/acts_as_migration/acts_as_migratoin_spec.rb
86
+ - spec/acts_as_migration/models.rb
87
+ - spec/acts_as_migration/schema.rb
88
+ - spec/acts_as_relation/acts_as_relation_spec.rb
89
+ - spec/acts_as_relation/models.rb
90
+ - spec/acts_as_relation/schema.rb
91
+ - spec/spec_helper.rb
77
92
  homepage: http://github.com/hzamani/acts_as_relation
78
93
  licenses: []
79
94
  post_install_message:
@@ -99,6 +114,10 @@ signing_key:
99
114
  specification_version: 3
100
115
  summary: Easy multi-table inheritance for rails
101
116
  test_files:
102
- - test/acts_as_relation_test.rb
103
- - test/schema.rb
104
- - test/test_helper.rb
117
+ - spec/acts_as_migration/acts_as_migratoin_spec.rb
118
+ - spec/acts_as_migration/models.rb
119
+ - spec/acts_as_migration/schema.rb
120
+ - spec/acts_as_relation/acts_as_relation_spec.rb
121
+ - spec/acts_as_relation/models.rb
122
+ - spec/acts_as_relation/schema.rb
123
+ - spec/spec_helper.rb
data/README.markdown DELETED
@@ -1,85 +0,0 @@
1
- Acts As Realation
2
- =================
3
-
4
- Easy multi-table inheritance for rails.
5
- With `acts_as_relation` models inherit parent model:
6
-
7
- * columns
8
- * validations
9
- * methods
10
-
11
- Multi-table inheritance
12
- -----------------------
13
-
14
- Multi-table inheritance happens when each model in the hierarchy is a model all by itself
15
- that corresponds to its own database table and can be queried and created individually.
16
- The inheritance relationship introduces links between the child model and each of its
17
- parents (via an automatically-created `has_one` associations).
18
-
19
- Example
20
- -------
21
-
22
- generate models
23
-
24
- $ rails g model product name:string price:float
25
- $ rails g model pen color:string
26
-
27
- Add next option to Product Migration:
28
-
29
- create_table :products, :as_relation_superclass => true
30
-
31
- add some validations and instance methods
32
-
33
- class Product < ActiveRecord::Base
34
- validates_presence_of :name, :price
35
-
36
- def hello
37
- puts "Hello, My name is '#{name}', my price is $#{price}."
38
- end
39
- end
40
-
41
- product is superclass for all kind of products:
42
-
43
- class Product < ActiveRecord::Base
44
- acts_as_superclass
45
- end
46
-
47
- pen inherits from product
48
-
49
- class Pen < ActiveRecord::Base
50
- acts_as :product
51
- end
52
-
53
- to get some specific class(as example: specific product - Pen) from superclass can be used method specific_class:
54
-
55
- Pen.create :name => 'Pen A', :color=> 'black', :price => 0.42
56
- product = Product.first
57
- product.specific_class # will be instance of Pen class
58
-
59
- to get name of association used between superclass and children can be used method acts_as_association_name:
60
-
61
- Product.acts_as_association_name # 'Productable'
62
-
63
- after deleting specific object will be removed linked superobject:
64
-
65
- Pen.first.destroy # delete as Pen row as linked Product row
66
-
67
- pen inherits products validations and columns
68
-
69
- p = Pen.new
70
- p.valid? => false
71
- p.errors => {:name=>["can't be blank"], :price=>["can't be blank"]}
72
-
73
- pen inherits product methods
74
-
75
- pen = Pen.new(:name=>"Red Pen", :color=>:red, :price=>0.99)
76
- pen.hello => Hello, My name is 'Red Pen', my price is $0.99.
77
-
78
- we can make queries on both models
79
-
80
- Product.where("price <= 1")
81
- Pen.where("color = ?", color)
82
-
83
- ---
84
-
85
- Copyright (c) 2011 Hassan Zamani, released under the MIT license.
data/init.rb DELETED
@@ -1 +0,0 @@
1
- require 'acts_as_relation'
@@ -1,114 +0,0 @@
1
- module ActiveRecord
2
- module Acts
3
- module AsRelation
4
- def self.included(base)
5
- base.extend(ClassMethods)
6
- end
7
-
8
- module AccessMethods
9
- def define_acts_as_accessors(attribs, model_name)
10
- attribs.each do |attrib|
11
- class_eval <<-EndClass
12
- def #{attrib}
13
- #{model_name}.#{attrib}
14
- end
15
-
16
- def #{attrib}=(value)
17
- self.#{model_name}.#{attrib} = value
18
- end
19
-
20
- def #{attrib}?
21
- self.#{model_name}.#{attrib}?
22
- end
23
- EndClass
24
- end
25
- end
26
- end
27
-
28
- module ClassMethods
29
- def acts_as_association_name model_name = nil
30
- suffix = 'able'
31
- model_name = self.name unless model_name
32
-
33
- name = model_name.to_s.demodulize.singularize
34
- if name[-1].chr =~ /[^aeiou]/ || name[-2..-1] =~ /ge|ce/
35
- name = name + suffix
36
- elsif name[-7..-1] == 'ability'
37
- name = name[0..-8] + suffix
38
- else
39
- name = name[0..-2] + suffix
40
- end
41
-
42
- name.underscore
43
- end
44
-
45
- def acts_as(model_name)
46
- name = model_name.to_s.underscore.singularize
47
- association_name = acts_as_association_name name
48
-
49
- # Create A AsModel module
50
- as_model = Module.new
51
- Object.const_set("As#{name.camelcase}", as_model)
52
-
53
- as_model.module_eval <<-EndModule
54
- def self.included(base)
55
- base.has_one :#{name}, :as => :#{association_name}, :autosave => true, :validate => false, :dependent => :destroy
56
- base.validate :#{name}_must_be_valid
57
- base.alias_method_chain :#{name}, :autobuild
58
-
59
- base.extend ActiveRecord::Acts::AsRelation::AccessMethods
60
- all_attributes = #{name.camelcase.constantize}.content_columns.map(&:name)
61
- ignored_attributes = ["created_at", "updated_at", "#{association_name}_type"]
62
- associations = #{name.camelcase.constantize}.reflect_on_all_associations(:belongs_to).map! { |assoc| assoc.name }
63
- attributes_to_delegate = all_attributes - ignored_attributes + associations
64
- base.define_acts_as_accessors(attributes_to_delegate, "#{name}")
65
- end
66
-
67
- def #{name}_with_autobuild
68
- #{name}_without_autobuild || build_#{name}
69
- end
70
-
71
- def method_missing method, *arg, &block
72
- #{name}.send method, *arg, &block
73
- rescue NoMethodError
74
- super
75
- end
76
-
77
- def respond_to?(method, include_private_methods = false)
78
- super || self.#{name}.respond_to?(method, include_private_methods)
79
- end
80
-
81
- protected
82
-
83
- def #{name}_must_be_valid
84
- unless #{name}.valid?
85
- #{name}.errors.each do |att, message|
86
- errors.add(att, message)
87
- end
88
- end
89
- end
90
- EndModule
91
-
92
- class_eval do
93
- include "As#{name.camelcase}".constantize
94
- end
95
- end
96
-
97
- def acts_as_superclass
98
- association_name = acts_as_association_name
99
-
100
- class_eval <<-CLASS
101
- belongs_to :#{association_name}, :polymorphic => true
102
-
103
- def specific_class
104
- self.#{association_name}
105
- end
106
- CLASS
107
- end
108
-
109
- end
110
- end
111
- end
112
- end
113
-
114
- ActiveRecord::Base.send :include, ActiveRecord::Acts::AsRelation
@@ -1,27 +0,0 @@
1
- module ActiveRecord
2
- module Acts
3
- module AsRelationSuperclassMigration
4
-
5
- def self.included(base)
6
- base.class_eval do
7
- alias_method_chain :create_table, :as_relation_superclass
8
- end
9
- end
10
-
11
- def create_table_with_as_relation_superclass(table_name, options = {})
12
- association_name = ActiveRecord::Base.acts_as_association_name table_name
13
-
14
- create_table_without_as_relation_superclass(table_name, options) do |td|
15
- if options[:as_relation_superclass]
16
- td.integer "#{association_name}_id"
17
- td.string "#{association_name}_type"
18
- end
19
-
20
- yield td if block_given?
21
- end
22
- end
23
- end
24
- end
25
- end
26
-
27
- ActiveRecord::ConnectionAdapters::SchemaStatements.send :include, ActiveRecord::Acts::AsRelationSuperclassMigration
@@ -1,121 +0,0 @@
1
- require 'test_helper'
2
-
3
- class ActsAsRelationTest < ActiveSupport::TestCase
4
-
5
- test "acts as validation" do
6
- pen = Pen.new
7
- assert !pen.valid?
8
- assert_equal pen.errors.keys, [:name, :price, :color]
9
-
10
- pen.name = "TestPen"
11
- assert !pen.valid?
12
- assert_equal pen.errors.keys, [:price, :color]
13
-
14
- pencil = Pencil.new
15
- assert !pencil.valid?
16
- assert_equal pencil.errors.keys, [:name, :price, :color]
17
-
18
- pencil.color = "red"
19
- assert !pencil.valid?
20
- assert_equal pencil.errors.keys, [:name, :price]
21
- end
22
-
23
- test "save model" do
24
- assert Pen.new(:name=>"FOO", :color=>"black", :price=>0.89).save
25
- pen = Pen.new
26
- pen.name = "BAR"
27
- pen.color = "red"
28
- pen.price = 0.99
29
- assert pen.save
30
- end
31
-
32
- test "access methods" do
33
- assert_nothing_raised(ActiveRecord::UnknownAttributeError) do
34
- Pen.new(:name=>"RedPen", :price=>0.59, :color=>"red")
35
- Pencil.new(:name=>"RedPencil", :price=>0.59, :color=>"red")
36
- end
37
- end
38
-
39
- test "acts as method missing" do
40
- assert_nothing_raised(NoMethodError) do
41
- pen = Pen.new
42
- pen.name_changed?
43
- pen.price_changed?
44
- pen.name_was
45
-
46
- pencil = Pencil.new
47
- pencil.name_changed?
48
- pencil.price_changed?
49
- pencil.name_was
50
- pencil.color_was
51
- end
52
- end
53
-
54
- test "acts as respond to?" do
55
- pen = Pen.new
56
- assert(pen.respond_to? :name_changed?)
57
- assert(pen.respond_to? :name_was)
58
- assert(pen.respond_to? :price_will_change!)
59
-
60
- pencil = Pencil.new
61
- assert(pencil.respond_to? :name_changed?)
62
- assert(pencil.respond_to? :name_was)
63
- assert(pencil.respond_to? :price_will_change!)
64
- assert(pencil.respond_to? :color_changed?)
65
- assert(pencil.respond_to? :color_was)
66
- end
67
-
68
- test "association reflections" do
69
- store = Store.new
70
- pen = Pen.new
71
- pen.store = store
72
-
73
- assert_equal store, pen.product.store
74
- assert_equal store, pen.store
75
- end
76
-
77
- test "call parent methods" do
78
- pen = Pen.new(:name=>"RedPen", :price=>0.59, :color=>"red")
79
- assert_equal pen.parent_method, "RedPen - 0.59"
80
- end
81
-
82
- test "call unexisted method" do
83
- assert_raise NoMethodError do
84
- pen = Pen.new
85
- pen.unexisted_method
86
- end
87
-
88
- end
89
-
90
- test "acts as association name" do
91
- assert_equal Pencil.acts_as_association_name, 'pencilable'
92
- assert_equal Pencil.acts_as_association_name( Pen ), 'penable'
93
- end
94
-
95
- test "acts as superclass" do
96
- pen = Pen.create(:name => "RedPen", :price => 0.59, :color => "red")
97
- product = pen.product
98
-
99
- assert_equal product.specific_class.class, Pen
100
- end
101
-
102
- test "destroy action" do
103
- pen = Pen.create(:name => "RedPen", :price => 0.59, :color => "red")
104
- product = pen.product
105
-
106
- pen.destroy
107
-
108
- assert_raise ActiveRecord::RecordNotFound do
109
- Product.find product.id
110
- end
111
- assert_raise ActiveRecord::RecordNotFound do
112
- Pen.find pen.id
113
- end
114
- end
115
-
116
- end
117
-
118
- #ActiveRecord::Base.connection.tables.each do |table|
119
- # ActiveRecord::Base.connection.drop_table(table)
120
- #end
121
-
data/test/schema.rb DELETED
@@ -1,52 +0,0 @@
1
- require 'rubygems'
2
- require 'active_record'
3
- require 'acts_as_relation'
4
-
5
- ActiveRecord::Base.establish_connection(
6
- :adapter => "sqlite3",
7
- :database => ":memory:"
8
- )
9
-
10
- ActiveRecord::Schema.define(:version => 1) do
11
-
12
- create_table :stores do |t|
13
- t.string :store_name
14
- end
15
-
16
- create_table :products, :as_relation_superclass => true do |t|
17
- t.string :name
18
- t.float :price
19
- end
20
-
21
- create_table :pens, :as_relation_superclass => true do |t|
22
- t.string :color
23
- end
24
-
25
- create_table :pencils
26
- end
27
-
28
- class Store < ActiveRecord::Base
29
- has_many :products
30
- end
31
-
32
- class Product < ActiveRecord::Base
33
- acts_as_superclass
34
-
35
- belongs_to :store
36
- validates_presence_of :name, :price
37
-
38
- def parent_method
39
- "#{name} - #{price}"
40
- end
41
- end
42
-
43
- class Pen < ActiveRecord::Base
44
- acts_as_superclass
45
-
46
- acts_as :product
47
- validates_presence_of :color
48
- end
49
-
50
- class Pencil < ActiveRecord::Base
51
- acts_as :pen
52
- end
data/test/test_helper.rb DELETED
@@ -1,4 +0,0 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
- require 'active_support'
4
- require 'schema'