acts_as_relation 0.0.5 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
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'