active_record-acts_as 1.0.0.pre
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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +4 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +159 -0
- data/Rakefile +7 -0
- data/active_record-acts_as.gemspec +30 -0
- data/lib/active_record/acts_as/instance_methods.rb +56 -0
- data/lib/active_record/acts_as/migration.rb +24 -0
- data/lib/active_record/acts_as/querying.rb +23 -0
- data/lib/active_record/acts_as/relation.rb +47 -0
- data/lib/active_record/acts_as/version.rb +6 -0
- data/lib/active_record/acts_as.rb +24 -0
- data/spec/actable_spec.rb +37 -0
- data/spec/active_record_spec.rb +21 -0
- data/spec/acts_as_spec.rb +162 -0
- data/spec/database_helper.rb +26 -0
- data/spec/migrations_spec.rb +25 -0
- data/spec/models.rb +39 -0
- data/spec/spec_helper.rb +4 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0e30b0cf3d5bebc22c5d3e9a63f81bced680e579
|
4
|
+
data.tar.gz: 11a3af36ca6331fe13cf84a962e6ba1728f9572a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 64f7cc31a9033eb4954981f24b6ae5edd5ac8c73ca05de836d2a01d62b5f8c59c41983caa91cf0e76b365a35e7d32ed96f5e02567ad125ef2d23be046ee0f496
|
7
|
+
data.tar.gz: 97c3f506fa35cfb4fa391b16f5ca3636db662b1f6963b8fc74d4f9e4fe02f8b50585f31142efe1c6f99b9aa7f06aafc222b8ebedc55f2e0b28f4f085cb21d82e
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Hassan Zamani
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
[](https://travis-ci.org/hzamani/active_record-acts_as)
|
2
|
+
[](https://codeclimate.com/github/hzamani/active_record-acts_as)
|
3
|
+
|
4
|
+
# ActiveRecord::ActsAs
|
5
|
+
|
6
|
+
This is a refactor of [`acts_as_relation`](https://github.com/hzamani/acts_as_relation)
|
7
|
+
|
8
|
+
Simulates multiple-table-inheritance (MTI) for ActiveRecord models.
|
9
|
+
By default, ActiveRecord only supports single-table inheritance (STI).
|
10
|
+
MTI gives you the benefits of STI but without having to place dozens of empty fields into a single table.
|
11
|
+
|
12
|
+
Take a traditional e-commerce application for example:
|
13
|
+
A product has common attributes (`name`, `price`, `image` ...),
|
14
|
+
while each type of product has its own attributes:
|
15
|
+
for example a `pen` has `color`, a `book` has `author` and `publisher` and so on.
|
16
|
+
With multiple-table-inheritance you can have a `products` table with common columns and
|
17
|
+
a seprate table for each product type, i.e. a `pens` table with `color` column.
|
18
|
+
|
19
|
+
## Requirements
|
20
|
+
|
21
|
+
`AciveRecord ~> 4`
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Add this line to your application's Gemfile:
|
26
|
+
|
27
|
+
gem 'active_record-acts_as'
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
|
31
|
+
$ bundle
|
32
|
+
|
33
|
+
Or install it yourself as:
|
34
|
+
|
35
|
+
$ gem install active_record-acts_as
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
Back to example above, all you have to do is to mark `Product` as `actable` and all product type models as `acts_as :product`:
|
40
|
+
|
41
|
+
```Ruby
|
42
|
+
class Product < ActiveRecord::Base
|
43
|
+
actable
|
44
|
+
|
45
|
+
validates_presence_of :name, :price
|
46
|
+
|
47
|
+
def display
|
48
|
+
"#{name} $#{price}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Pen < ActiveRecord::Base
|
53
|
+
acts_as :product
|
54
|
+
end
|
55
|
+
|
56
|
+
class Book < ActiveRecord::Base
|
57
|
+
acts_as :product
|
58
|
+
end
|
59
|
+
|
60
|
+
class Store < ActiveRecord::Base
|
61
|
+
has_many :products
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
and add foreign key and type columns to products table as in a polymorphic relation.
|
66
|
+
You may prefere using a migration:
|
67
|
+
|
68
|
+
```Ruby
|
69
|
+
change_table :products do |t|
|
70
|
+
t.integer :actable_id
|
71
|
+
t.string :actable_type
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
or use shortcut `actable`
|
76
|
+
|
77
|
+
```Ruby
|
78
|
+
change_table :products do |t|
|
79
|
+
t.actable
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
Now `Pen` and `Book` *acts as* `Product`, i.e. they inherit `Product`s *attributes*,
|
84
|
+
*methods* and *validations*. Now you can do things like these:
|
85
|
+
|
86
|
+
```Ruby
|
87
|
+
Pen.create name: 'Penie!', price: 0.8, color: 'red'
|
88
|
+
# => #<Pen id: 1, color: "red">
|
89
|
+
Pen.where price: 0.8
|
90
|
+
# => [#<Pen id: 1, color: "red">]
|
91
|
+
Product.where price: 0.8
|
92
|
+
# => [#<Product id: 1, name: "Penie!", price: 0.8, store_id: nil, actable_id: 1, actable_type: "Pen">]
|
93
|
+
pen = Pen.new
|
94
|
+
pen.valid?
|
95
|
+
# => false
|
96
|
+
pen.errors.full_messages
|
97
|
+
# => ["Name can't be blank", "Price can't be blank", "Color can't be blank"]
|
98
|
+
Pen.first.display
|
99
|
+
# => "Penie! $0.8"
|
100
|
+
```
|
101
|
+
|
102
|
+
On the other hand you can always access a specific object from its parent by calling `specific` method on it:
|
103
|
+
|
104
|
+
```Ruby
|
105
|
+
Product.first.specific
|
106
|
+
# => #<Pen ...>
|
107
|
+
```
|
108
|
+
|
109
|
+
In +has_many+ case you can use subclasses:
|
110
|
+
|
111
|
+
```Ruby
|
112
|
+
store = Store.create
|
113
|
+
store.products << Pen.create
|
114
|
+
store.products.first
|
115
|
+
# => #<Product: ...>
|
116
|
+
```
|
117
|
+
|
118
|
+
You can give a name to all methods in `:as` option:
|
119
|
+
|
120
|
+
```Ruby
|
121
|
+
class Product < ActiveRecord::Base
|
122
|
+
actable as: producible
|
123
|
+
end
|
124
|
+
|
125
|
+
class Pen < ActiveRecord::Base
|
126
|
+
acts_as :product, as: :producible
|
127
|
+
end
|
128
|
+
|
129
|
+
change_table :products do |t|
|
130
|
+
t.actable as: :producible
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
`acts_as` support all `has_one` options, where defauls are there:
|
135
|
+
`as: :actable, dependent: :destroy, validate: false, autosave: true`
|
136
|
+
|
137
|
+
Make sure you know what you are doing when ovrewriting `validate` or `autodave` options.
|
138
|
+
|
139
|
+
You can pass scope to `acts_as` as in `has_one`:
|
140
|
+
|
141
|
+
```Ruby
|
142
|
+
acts_as :person, -> { includes(:friends) }
|
143
|
+
```
|
144
|
+
|
145
|
+
`actable` support all `belongs_to` options, where defaults are these:
|
146
|
+
`polymorphic: true, dependent: :delete, autosave: true`
|
147
|
+
|
148
|
+
Make sure you know what you are doing when ovrewriting `polymorphic` option.
|
149
|
+
|
150
|
+
|
151
|
+
## Contributing
|
152
|
+
|
153
|
+
1. Fork it ( https://github.com/hzamani/active_record-acts_as/fork )
|
154
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
155
|
+
3. Test changes don't breake anything (`rspec`)
|
156
|
+
4. Add specs for your new feature
|
157
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
158
|
+
6. Push to the branch (`git push origin my-new-feature`)
|
159
|
+
7. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_record/acts_as/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "active_record-acts_as"
|
8
|
+
spec.version = ActiveRecord::ActsAs::VERSION
|
9
|
+
spec.authors = ["Hassan Zamani"]
|
10
|
+
spec.email = ["hsn.zamani@gmail.com"]
|
11
|
+
spec.summary = %q{Simulate multi-table inheritance for activerecord models}
|
12
|
+
spec.description = %q{Simulate multi-table inheritance for activerecord models using a plymorphic association}
|
13
|
+
spec.homepage = "http://github.com/hzamani/active_record-acts_as"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = ">= 1.9"
|
22
|
+
|
23
|
+
spec.add_development_dependency "sqlite3", "~> 1.3"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3"
|
26
|
+
spec.add_development_dependency "rake", "~> 10"
|
27
|
+
|
28
|
+
spec.add_dependency "activesupport", "~> 4"
|
29
|
+
spec.add_dependency "activerecord", "~> 4"
|
30
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
module ActsAs
|
4
|
+
module InstanceMethods
|
5
|
+
def acting_as?(other = nil)
|
6
|
+
self.class.acting_as? other
|
7
|
+
end
|
8
|
+
|
9
|
+
def is_a?(klass)
|
10
|
+
super || acting_as?(klass)
|
11
|
+
end
|
12
|
+
|
13
|
+
def actable_must_be_valid
|
14
|
+
unless acting_as.valid?
|
15
|
+
acting_as.errors.each do |att, message|
|
16
|
+
errors.add(att, message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
protected :actable_must_be_valid
|
21
|
+
|
22
|
+
def read_attribute(attr_name, *args, &block)
|
23
|
+
if attribute_method?(attr_name)
|
24
|
+
super
|
25
|
+
else
|
26
|
+
acting_as.read_attribute(attr_name, *args, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_attribute(attr_name, value, *args, &block)
|
31
|
+
if attribute_method?(attr_name)
|
32
|
+
super
|
33
|
+
else
|
34
|
+
acting_as.send(:write_attribute, attr_name, value, *args, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
private :write_attribute
|
38
|
+
|
39
|
+
def attributes
|
40
|
+
acting_as.attributes.except(acting_as_reflection.type, acting_as_reflection.foreign_key).merge(super)
|
41
|
+
end
|
42
|
+
|
43
|
+
def respond_to?(name, include_private = false)
|
44
|
+
super || acting_as.respond_to?(name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(method, *args, &block)
|
48
|
+
if acting_as.respond_to?(method)
|
49
|
+
acting_as.send(method, *args, &block)
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
module ActsAs
|
4
|
+
module Migration
|
5
|
+
module TableDefinition
|
6
|
+
def actable(options = {})
|
7
|
+
name = options.delete(:as) || :actable
|
8
|
+
options[:polymorphic] = true
|
9
|
+
references(name, options)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Table
|
14
|
+
include TableDefinition
|
15
|
+
|
16
|
+
def remove_actable(options = {})
|
17
|
+
name = options.delete(:as) || :actable
|
18
|
+
options[:polymorphic] = true
|
19
|
+
@base.remove_reference(@table_name, name, options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
module ActsAs
|
4
|
+
module Querying
|
5
|
+
def where(opts = :chain, *rest)
|
6
|
+
if opts.is_a? Hash
|
7
|
+
opts, acts_as_opts = opts.partition { |k,v| attribute_names.include?(k.to_s) }
|
8
|
+
opts, acts_as_opts = Hash[opts], Hash[acts_as_opts]
|
9
|
+
opts[acting_as_model.table_name] = acts_as_opts
|
10
|
+
end
|
11
|
+
super(opts, *rest)
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_by(*args)
|
15
|
+
where(*args).take
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_by!(*args)
|
19
|
+
where(*args).take!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
module ActiveRecord
|
3
|
+
module ActsAs
|
4
|
+
module Relation
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def acts_as(name, scope = nil, options = {})
|
9
|
+
options, scope = scope, nil if Hash === scope
|
10
|
+
options = {as: :actable, dependent: :destroy, validate: false, autosave: true}.merge options
|
11
|
+
|
12
|
+
reflections = has_one name, scope, options
|
13
|
+
default_scope -> { eager_load(name) }
|
14
|
+
validate :actable_must_be_valid
|
15
|
+
|
16
|
+
cattr_reader(:acting_as_reflection) { reflections[name.to_sym] }
|
17
|
+
cattr_reader(:acting_as_name) { name.to_s }
|
18
|
+
cattr_reader(:acting_as_model) { name.to_s.camelize.constantize }
|
19
|
+
class_eval "def acting_as() #{name} || build_#{name} end"
|
20
|
+
|
21
|
+
include ActsAs::InstanceMethods
|
22
|
+
extend ActsAs::Querying
|
23
|
+
end
|
24
|
+
|
25
|
+
def acting_as?(other = nil)
|
26
|
+
if respond_to? :acting_as_name
|
27
|
+
other.nil? || acting_as_name == other.to_s.underscore
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def is_a?(klass)
|
34
|
+
super || acting_as?(klass)
|
35
|
+
end
|
36
|
+
|
37
|
+
def actable(options = {})
|
38
|
+
name = options.delete(:as) || :actable
|
39
|
+
|
40
|
+
belongs_to name, {polymorphic: true, dependent: :delete, autosave: true}.merge(options)
|
41
|
+
|
42
|
+
alias_method :specific, name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_record/acts_as/version'
|
2
|
+
require 'active_record/acts_as/relation'
|
3
|
+
require 'active_record/acts_as/migration'
|
4
|
+
require 'active_record/acts_as/instance_methods'
|
5
|
+
require 'active_record/acts_as/querying'
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
class Base
|
9
|
+
include ActsAs::Relation
|
10
|
+
end
|
11
|
+
|
12
|
+
module ConnectionAdapters
|
13
|
+
class TableDefinition
|
14
|
+
include ActsAs::Migration::TableDefinition
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ConnectionAdapters
|
19
|
+
class Table
|
20
|
+
include ActsAs::Migration::Table
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'models'
|
2
|
+
|
3
|
+
RSpec.describe "ActiveRecord::Base subclass with #actable" do
|
4
|
+
subject { Product }
|
5
|
+
|
6
|
+
let(:pen_attributes) { {name: 'pen', price: 0.8, color: 'red'} }
|
7
|
+
let(:pen) { Pen.new pen_attributes }
|
8
|
+
|
9
|
+
it "has a polymorphic belongs_to :actable relation" do
|
10
|
+
association = subject.reflect_on_all_associations.find { |r| r.name == :actable }
|
11
|
+
expect(association).to_not be_nil
|
12
|
+
expect(association.macro).to eq(:belongs_to)
|
13
|
+
expect(association).to be_polymorphic
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ".specific" do
|
17
|
+
it "return specific submodel" do
|
18
|
+
pen.save
|
19
|
+
expect(Product.find(pen.acting_as.id).specific).to eq(pen)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "deletes specific subclass on destroy" do
|
24
|
+
pen.save
|
25
|
+
pen.product.destroy
|
26
|
+
expect { pen.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "saves submodel on save" do
|
30
|
+
pen.save
|
31
|
+
product = pen.acting_as
|
32
|
+
product.specific.color = 'blue'
|
33
|
+
product.save
|
34
|
+
pen.reload
|
35
|
+
expect(pen.color).to eq('blue')
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/acts_as'
|
3
|
+
|
4
|
+
class Model < ActiveRecord::Base
|
5
|
+
end
|
6
|
+
|
7
|
+
RSpec.describe ActiveRecord, "Model" do
|
8
|
+
subject { Model }
|
9
|
+
|
10
|
+
it { is_expected.to respond_to(:acts_as) }
|
11
|
+
it { is_expected.to respond_to(:actable) }
|
12
|
+
it { is_expected.to respond_to(:acting_as?) }
|
13
|
+
|
14
|
+
describe "#acting_as?" do
|
15
|
+
it "returns false with any arg" do
|
16
|
+
expect(subject.acting_as?).to be false
|
17
|
+
expect(subject.acting_as?(String)).to be false
|
18
|
+
expect(subject.acting_as?(:product)).to be false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'models'
|
2
|
+
|
3
|
+
RSpec.describe "ActiveRecord::Base model with #acts_as called" do
|
4
|
+
subject { Pen }
|
5
|
+
|
6
|
+
let(:pen_attributes) { {name: 'pen', price: 0.8, color: 'red'} }
|
7
|
+
let(:pen) { Pen.new pen_attributes }
|
8
|
+
let(:store) { Store.new name: 'biggerman' }
|
9
|
+
|
10
|
+
it "has a has_one relation" do
|
11
|
+
association = subject.reflect_on_all_associations.find { |r| r.name == :product }
|
12
|
+
expect(association).to_not be_nil
|
13
|
+
expect(association.macro).to eq(:has_one)
|
14
|
+
expect(association.options).to have_key(:as)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#acting_as?" do
|
18
|
+
it "returns true for supermodel class and name" do
|
19
|
+
expect(Pen.acting_as? :product).to be true
|
20
|
+
expect(Pen.acting_as? Product).to be true
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns false for anything other than supermodel" do
|
24
|
+
expect(Pen.acting_as? :model).to be false
|
25
|
+
expect(Pen.acting_as? String).to be false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".acting_as?" do
|
30
|
+
it "returns true for supermodel class and name" do
|
31
|
+
expect(pen.acting_as? :product).to be true
|
32
|
+
expect(pen.acting_as? Product).to be true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns false for anything other than supermodel" do
|
36
|
+
expect(pen.acting_as? :model).to be false
|
37
|
+
expect(pen.acting_as? String).to be false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#is_a?" do
|
42
|
+
it "responds true when supermodel passed to" do
|
43
|
+
expect(Pen.is_a? Product).to be true
|
44
|
+
expect(Pen.is_a? Object).to be true
|
45
|
+
expect(Pen.is_a? String).to be false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe ".is_a?" do
|
50
|
+
it "responds true when supermodel passed to" do
|
51
|
+
expect(pen.is_a? Product).to be true
|
52
|
+
expect(pen.is_a? Object).to be true
|
53
|
+
expect(pen.is_a? String).to be false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#acting_as_name" do
|
58
|
+
it "return acts_as model name" do
|
59
|
+
expect(pen.acting_as_name).to eq('product')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#acting_as" do
|
64
|
+
it "returns autobuilded acts_as model" do
|
65
|
+
expect(pen.acting_as).to_not be_nil
|
66
|
+
expect(pen.acting_as).to be_instance_of(Product)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "have supermodel attributes accessible on creation" do
|
71
|
+
expect{Pen.create(pen_attributes)}.to_not raise_error
|
72
|
+
end
|
73
|
+
|
74
|
+
context "instance" do
|
75
|
+
it "responds to supermodel methods" do
|
76
|
+
%w(name name= name? name_change name_changed? name_was name_will_change! price color).each do |name|
|
77
|
+
expect(pen).to respond_to(name)
|
78
|
+
end
|
79
|
+
expect(pen.present).to eq("pen - $0.8")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "saves supermodel attributes on save" do
|
83
|
+
pen.save
|
84
|
+
pen.reload
|
85
|
+
expect(pen.name).to eq('pen')
|
86
|
+
expect(pen.price).to eq(0.8)
|
87
|
+
expect(pen.color).to eq('red')
|
88
|
+
end
|
89
|
+
|
90
|
+
it "raises NoMethodEror on unexisting method call" do
|
91
|
+
expect { pen.unexisted_method }.to raise_error(NoMethodError)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "destroies Supermodel on destroy" do
|
95
|
+
pen.save
|
96
|
+
product_id = pen.product.id
|
97
|
+
pen.destroy
|
98
|
+
expect { Product.find(product_id) }.to raise_error(ActiveRecord::RecordNotFound)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "validates supermodel attribures upon validation" do
|
102
|
+
p = Pen.new
|
103
|
+
expect(p).to be_invalid
|
104
|
+
expect(p.errors.keys).to include(:name, :price, :color)
|
105
|
+
p.name = 'testing'
|
106
|
+
expect(p).to be_invalid
|
107
|
+
p.color = 'red'
|
108
|
+
expect(p).to be_invalid
|
109
|
+
p.price = 0.8
|
110
|
+
expect(p).to be_valid
|
111
|
+
end
|
112
|
+
|
113
|
+
it "can set assosications defined in supermodel" do
|
114
|
+
store.save
|
115
|
+
pen.store = store
|
116
|
+
pen.save
|
117
|
+
pen.reload
|
118
|
+
expect(pen.store).to eq(store)
|
119
|
+
expect(pen.product.store).to eq(store)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should be appendable in an has_many relation using << operator" do
|
123
|
+
store.save
|
124
|
+
store.products << pen
|
125
|
+
expect(pen.store).to eq(store)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "includes supermodel attributes in .to_json responce" do
|
129
|
+
expect(pen.to_json).to eq('{"id":null,"name":"pen","price":0.8,"store_id":null,"color":"red"}')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "Querying" do
|
134
|
+
before(:each) { clear_database }
|
135
|
+
|
136
|
+
it ".where works with supermodel attributes" do
|
137
|
+
red_pen = Pen.create(name: 'red pen', price: 0.8, color: 'red')
|
138
|
+
blue_pen = Pen.create(name: 'blue pen', price: 0.8, color: 'blue')
|
139
|
+
black_pen = Pen.create(name: 'black pen', price: 0.9, color: 'black')
|
140
|
+
|
141
|
+
expect{Pen.where(price: 0.8)}.to_not raise_error
|
142
|
+
expect(Pen.where(price: 0.8)).to include(red_pen, blue_pen)
|
143
|
+
expect(Pen.where(price: 0.8)).to_not include(black_pen)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "find_by works with supermodel attributes" do
|
147
|
+
red_pen = Pen.create(name: 'red pen', price: 0.8, color: 'red')
|
148
|
+
blue_pen = Pen.create(name: 'blue pen', price: 0.8, color: 'blue')
|
149
|
+
black_pen = Pen.create(name: 'black pen', price: 0.9, color: 'black')
|
150
|
+
|
151
|
+
expect(Pen.find_by(name: 'red pen')).to eq(red_pen)
|
152
|
+
expect(Pen.find_by(name: 'blue pen')).to eq(blue_pen)
|
153
|
+
expect(Pen.find_by(name: 'black pen')).to eq(black_pen)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "includes supermodel attributes in Relation.scope_for_create" do
|
157
|
+
relation = Pen.where(name: 'new name')
|
158
|
+
expect(relation.scope_for_create.keys).to include(:name)
|
159
|
+
expect(relation.scope_for_create[:name]).to eq('new name')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
def connect_to_databse
|
4
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
5
|
+
end
|
6
|
+
|
7
|
+
def clear_database
|
8
|
+
ActiveRecord::Base.descendants.each do |model|
|
9
|
+
model.delete_all if model.table_exists?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def reset_database
|
14
|
+
ActiveRecord::Base.descendants.map(&:reset_column_information)
|
15
|
+
ActiveRecord::Base.connection.disconnect!
|
16
|
+
connect_to_databse
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_database(&block)
|
20
|
+
reset_database
|
21
|
+
ActiveRecord::Schema.define(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
I18n.enforce_available_locales = false
|
25
|
+
ActiveRecord::Migration.verbose = false
|
26
|
+
connect_to_databse
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'database_helper'
|
2
|
+
require 'active_record/acts_as'
|
3
|
+
|
4
|
+
|
5
|
+
class Product < ActiveRecord::Base
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
RSpec.describe ".actable" do
|
10
|
+
context "in .create_table block" do
|
11
|
+
context "with :as options" do
|
12
|
+
it "creates plymorphic reference columns with given name" do
|
13
|
+
initialize_database { create_table(:products) { |t| t.actable(as: :produceable) } }
|
14
|
+
expect(Product.column_names).to include('produceable_id', 'produceable_type')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with no args" do
|
19
|
+
it "creates plymorphic reference columns" do
|
20
|
+
initialize_database { create_table(:products) { |t| t.actable } }
|
21
|
+
expect(Product.column_names).to include('actable_id', 'actable_type')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/models.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'database_helper'
|
2
|
+
require 'active_record/acts_as'
|
3
|
+
|
4
|
+
class Product < ActiveRecord::Base
|
5
|
+
actable
|
6
|
+
belongs_to :store
|
7
|
+
validates_presence_of :name, :price
|
8
|
+
|
9
|
+
def present
|
10
|
+
"#{name} - $#{price}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Pen < ActiveRecord::Base
|
15
|
+
acts_as :product
|
16
|
+
|
17
|
+
validates_presence_of :color
|
18
|
+
end
|
19
|
+
|
20
|
+
class Store < ActiveRecord::Base
|
21
|
+
has_many :products
|
22
|
+
end
|
23
|
+
|
24
|
+
initialize_database do
|
25
|
+
create_table :pens do |t|
|
26
|
+
t.string :color
|
27
|
+
end
|
28
|
+
|
29
|
+
create_table :products do |t|
|
30
|
+
t.string :name
|
31
|
+
t.float :price
|
32
|
+
t.integer :store_id
|
33
|
+
t.actable
|
34
|
+
end
|
35
|
+
|
36
|
+
create_table :stores do |t|
|
37
|
+
t.string :name
|
38
|
+
end
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_record-acts_as
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hassan Zamani
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sqlite3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
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.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4'
|
97
|
+
description: Simulate multi-table inheritance for activerecord models using a plymorphic
|
98
|
+
association
|
99
|
+
email:
|
100
|
+
- hsn.zamani@gmail.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- ".rspec"
|
107
|
+
- ".travis.yml"
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- active_record-acts_as.gemspec
|
113
|
+
- lib/active_record/acts_as.rb
|
114
|
+
- lib/active_record/acts_as/instance_methods.rb
|
115
|
+
- lib/active_record/acts_as/migration.rb
|
116
|
+
- lib/active_record/acts_as/querying.rb
|
117
|
+
- lib/active_record/acts_as/relation.rb
|
118
|
+
- lib/active_record/acts_as/version.rb
|
119
|
+
- spec/actable_spec.rb
|
120
|
+
- spec/active_record_spec.rb
|
121
|
+
- spec/acts_as_spec.rb
|
122
|
+
- spec/database_helper.rb
|
123
|
+
- spec/migrations_spec.rb
|
124
|
+
- spec/models.rb
|
125
|
+
- spec/spec_helper.rb
|
126
|
+
homepage: http://github.com/hzamani/active_record-acts_as
|
127
|
+
licenses:
|
128
|
+
- MIT
|
129
|
+
metadata: {}
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.9'
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">"
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: 1.3.1
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 2.2.2
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: Simulate multi-table inheritance for activerecord models
|
150
|
+
test_files:
|
151
|
+
- spec/actable_spec.rb
|
152
|
+
- spec/active_record_spec.rb
|
153
|
+
- spec/acts_as_spec.rb
|
154
|
+
- spec/database_helper.rb
|
155
|
+
- spec/migrations_spec.rb
|
156
|
+
- spec/models.rb
|
157
|
+
- spec/spec_helper.rb
|