activemodel-associations 0.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.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/Rakefile +23 -0
- data/activemodel-associations.gemspec +32 -0
- data/gemfiles/activerecord-40.gemfile +7 -0
- data/gemfiles/activerecord-41.gemfile +7 -0
- data/lib/active_model/associations.rb +51 -0
- data/lib/active_model/associations/active_record_reflection.rb +29 -0
- data/lib/active_model/associations/association_scope_extension.rb +23 -0
- data/lib/active_model/associations/autosave_association.rb +12 -0
- data/lib/active_model/associations/hooks.rb +10 -0
- data/lib/active_model/associations/initialize_extension.rb +16 -0
- data/lib/active_model/associations/override_methods.rb +76 -0
- data/lib/active_model/associations/railtie.rb +7 -0
- data/lib/active_model/associations/version.rb +5 -0
- data/lib/active_record/associations/builder/has_many_for_active_model.rb +7 -0
- data/lib/active_record/associations/has_many_for_active_model_association.rb +31 -0
- data/lib/activemodel/associations.rb +15 -0
- data/spec/db/migrate/10_create_users.rb +9 -0
- data/spec/lib/active_model/association_spec.rb +223 -0
- data/spec/spec_helper.rb +36 -0
- metadata +212 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 78d28b409b8b4a0658df2eb0729e3737a7cf5fe6
|
4
|
+
data.tar.gz: 3b84b0cbff584770cd0e8c247b036111d9a7a300
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 56f110e11d873c9916cb51325753d95f250957b225739df87b6812e33c62a01df41e8f1768f0f6fe08a6e74a5ed8ae429a10658f23a759740fda8c299e3bb898
|
7
|
+
data.tar.gz: 1e83f7e627843b46949b8d6d1c7f48938c09513ccf47d291580df107d3fc84c8a20248c670ebc5c15484818bf9786eba0b7235456e3a838fd7e014ca1e509b48
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
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
|
23
|
+
|
24
|
+
gemfiles/*.lock
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 joker1007
|
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,122 @@
|
|
1
|
+
# Activemodel::Association
|
2
|
+
[](https://travis-ci.org/joker1007/activemodel-associations)
|
3
|
+
[](https://coveralls.io/r/joker1007/activemodel-associations)
|
4
|
+
|
5
|
+
`has_many` and `belongs_to` macro for Plain Ruby Object.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'activemodel-association'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install activemodel-association
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### belongs\_to
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class User < ActiveRecord::Base; end
|
27
|
+
|
28
|
+
class Comment
|
29
|
+
include ActiveModel::Model # need ActiveModel::Model
|
30
|
+
include ActiveModel::Associations # include this
|
31
|
+
|
32
|
+
attr_accessor :body, :user_id # belongs_to association need foreign_key attribute
|
33
|
+
|
34
|
+
belongs_to :user
|
35
|
+
|
36
|
+
# need hash like accessor, used internal Rails
|
37
|
+
def [](attr)
|
38
|
+
self.send(attr)
|
39
|
+
end
|
40
|
+
|
41
|
+
# need hash like accessor, used internal Rails
|
42
|
+
def []=(attr, value)
|
43
|
+
self.send("#{attr}=", value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
user = User.create(name: "joker1007")
|
48
|
+
comment = Comment.new(user_id: user.id)
|
49
|
+
comment.user # => <User {name: "joker1007"}>
|
50
|
+
```
|
51
|
+
|
52
|
+
### Polymorphic belongs\_to
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class User < ActiveRecord::Base; end
|
56
|
+
|
57
|
+
class Comment
|
58
|
+
include ActiveModel::Model # need ActiveModel::Model
|
59
|
+
include ActiveModel::Associations # include this
|
60
|
+
|
61
|
+
attr_accessor :body, :commenter_id, :commenter_type
|
62
|
+
|
63
|
+
belongs_to :commenter, polymorphic: true
|
64
|
+
|
65
|
+
# need hash like accessor, used internal Rails
|
66
|
+
def [](attr)
|
67
|
+
self.send(attr)
|
68
|
+
end
|
69
|
+
|
70
|
+
# need hash like accessor, used internal Rails
|
71
|
+
def []=(attr, value)
|
72
|
+
self.send("#{attr}=", value)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
user = User.create(name: "joker1007")
|
77
|
+
comment = Comment.new(commenter_id: user.id, commenter_type: "User")
|
78
|
+
comment.commenter # => <User {name: "joker1007"}>
|
79
|
+
```
|
80
|
+
|
81
|
+
### has\_many
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class User < ActiveRecord::Base; end
|
85
|
+
|
86
|
+
class Group
|
87
|
+
include ActiveModel::Model
|
88
|
+
include ActiveModel::Associations
|
89
|
+
|
90
|
+
attr_accessor :name
|
91
|
+
attr_reader :user_ids
|
92
|
+
|
93
|
+
has_many :users
|
94
|
+
|
95
|
+
def [](attr)
|
96
|
+
self.send(attr)
|
97
|
+
end
|
98
|
+
|
99
|
+
def []=(attr, value)
|
100
|
+
self.send("#{attr}=", value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
user = User.create(name: "joker1007")
|
105
|
+
group = Group.new(user_ids: [user.id])
|
106
|
+
group.users # => ActiveRecord::Relation (SELECT * from users WHERE id IN (#{user.id}))
|
107
|
+
group.users.find_by(name: "none") # => []
|
108
|
+
```
|
109
|
+
|
110
|
+
## Limitation
|
111
|
+
Support associations is only `belongs_to` and simple `has_many`
|
112
|
+
`has_many` options is limited. following options is unsupported.
|
113
|
+
|
114
|
+
`:through :dependent :source :source_type :counter_cache :as`
|
115
|
+
|
116
|
+
## Contributing
|
117
|
+
|
118
|
+
1. Fork it ( https://github.com/[my-github-username]/activemodel-association/fork )
|
119
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
120
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
121
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
122
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
namespace :spec do
|
9
|
+
%w(activerecord-40 activerecord-41).each do |gemfile|
|
10
|
+
desc "Run Tests by #{gemfile}.gemfile"
|
11
|
+
task gemfile do
|
12
|
+
sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle install --path .bundle"
|
13
|
+
sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle exec rake -t spec"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Run All Tests"
|
18
|
+
task :all do
|
19
|
+
%w(activerecord-40 activerecord-41).each do |gemfile|
|
20
|
+
Rake::Task["spec:#{gemfile}"].invoke
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_model/associations/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "activemodel-associations"
|
8
|
+
spec.version = ActiveModel::Associations::VERSION
|
9
|
+
spec.authors = ["joker1007"]
|
10
|
+
spec.email = ["kakyoin.hierophant@gmail.com"]
|
11
|
+
spec.summary = %q{ActiveRecord Association Helper for PORO}
|
12
|
+
spec.description = %q{ActiveRecord Association Helper for PORO}
|
13
|
+
spec.homepage = ""
|
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.add_runtime_dependency "activerecord", "< 5"
|
22
|
+
spec.add_runtime_dependency "activemodel", "< 5"
|
23
|
+
spec.add_runtime_dependency "activesupport", "< 5"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
spec.add_development_dependency "tapp"
|
29
|
+
spec.add_development_dependency "sqlite3"
|
30
|
+
spec.add_development_dependency "database_cleaner"
|
31
|
+
spec.add_development_dependency "coveralls"
|
32
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'active_model/associations/initialize_extension'
|
2
|
+
require 'active_model/associations/active_record_reflection'
|
3
|
+
require 'active_model/associations/autosave_association'
|
4
|
+
require 'active_model/associations/override_methods'
|
5
|
+
require 'active_record/associations/builder/has_many_for_active_model'
|
6
|
+
require 'active_record/associations/has_many_for_active_model_association'
|
7
|
+
|
8
|
+
module ActiveModel
|
9
|
+
module Associations
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
include InitializeExtension
|
13
|
+
include AutosaveAssociation
|
14
|
+
include ActiveRecordReflection
|
15
|
+
include OverrideMethods
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# define association like ActiveRecord
|
19
|
+
def belongs_to(name, scope = nil, options = {})
|
20
|
+
reflection = ActiveRecord::Associations::Builder::BelongsTo.build(self, name, scope, options)
|
21
|
+
if ActiveRecord.version.to_s >= "4.1"
|
22
|
+
ActiveRecord::Reflection.add_reflection self, name, reflection
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# define association like ActiveRecord
|
27
|
+
def has_many(name, scope = nil, options = {}, &extension)
|
28
|
+
options.reverse_merge!(active_model: true, target_ids: "#{name.to_s.singularize}_ids")
|
29
|
+
if scope.is_a?(Hash)
|
30
|
+
options.merge!(scope)
|
31
|
+
scope = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
reflection = ActiveRecord::Associations::Builder::HasManyForActiveModel.build(self, name, scope, options, &extension)
|
35
|
+
if ActiveRecord.version.to_s >= "4.1"
|
36
|
+
ActiveRecord::Reflection.add_reflection self, name, reflection
|
37
|
+
end
|
38
|
+
|
39
|
+
mixin = generated_association_methods
|
40
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
41
|
+
def #{options[:target_ids]}=(other_ids)
|
42
|
+
@#{options[:target_ids]} = other_ids
|
43
|
+
association(:#{name}).reset
|
44
|
+
association(:#{name}).reset_scope
|
45
|
+
@#{options[:target_ids]}
|
46
|
+
end
|
47
|
+
CODE
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActiveModel::Associations
|
2
|
+
module ActiveRecordReflection
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :reflections
|
7
|
+
self.reflections = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
if ActiveRecord.version.to_s < "4.1"
|
12
|
+
def create_reflection(macro, name, scope, options, active_record)
|
13
|
+
case macro
|
14
|
+
when :has_many, :belongs_to
|
15
|
+
klass = ActiveRecord::Reflection::AssociationReflection
|
16
|
+
reflection = klass.new(macro, name, scope, options, active_record)
|
17
|
+
end
|
18
|
+
|
19
|
+
self.reflections = self.reflections.merge(name => reflection)
|
20
|
+
reflection
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def reflect_on_association(association)
|
25
|
+
reflections[association]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveModel::Associations
|
2
|
+
module AssociationScopeExtension
|
3
|
+
if ActiveRecord.version.to_s < "4.1"
|
4
|
+
def add_constraints(scope)
|
5
|
+
if reflection.options[:active_model]
|
6
|
+
target_ids = reflection.options[:target_ids]
|
7
|
+
return scope.where(id: owner[target_ids])
|
8
|
+
end
|
9
|
+
|
10
|
+
super
|
11
|
+
end
|
12
|
+
else
|
13
|
+
def add_constraints(scope, owner, assoc_klass, refl, tracker)
|
14
|
+
if refl.options[:active_model]
|
15
|
+
target_ids = refl.options[:target_ids]
|
16
|
+
return scope.where(id: owner[target_ids])
|
17
|
+
end
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActiveModel::Associations
|
2
|
+
module AutosaveAssociation
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
include ActiveRecord::AutosaveAssociation
|
6
|
+
|
7
|
+
included do
|
8
|
+
extend ActiveModel::Callbacks
|
9
|
+
define_model_callbacks :save, :create, :update
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ActiveModel::Associations
|
2
|
+
module Hooks
|
3
|
+
def self.init
|
4
|
+
ActiveSupport.on_load(:active_record) do
|
5
|
+
require 'active_model/associations/association_scope_extension'
|
6
|
+
ActiveRecord::Associations::AssociationScope.send(:prepend, ActiveModel::Associations::AssociationScopeExtension)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActiveModel::Associations
|
2
|
+
module InitializeExtension
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
prepend WithAssociationCache
|
7
|
+
end
|
8
|
+
|
9
|
+
module WithAssociationCache
|
10
|
+
def initialize(*args)
|
11
|
+
@association_cache = {}
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ActiveModel::Associations
|
2
|
+
module OverrideMethods
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
# borrow method definition from ActiveRecord::Inheritance
|
7
|
+
# use in Rails internal
|
8
|
+
mod = Module.new do
|
9
|
+
unbound = ActiveRecord::Inheritance::ClassMethods.instance_method(:compute_type)
|
10
|
+
define_method(:compute_type, unbound)
|
11
|
+
protected :compute_type
|
12
|
+
end
|
13
|
+
extend mod
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def generated_association_methods
|
18
|
+
@generated_association_methods ||= begin
|
19
|
+
mod = const_set(:GeneratedAssociationMethods, Module.new)
|
20
|
+
include mod
|
21
|
+
mod
|
22
|
+
end
|
23
|
+
end
|
24
|
+
alias :generated_feature_methods :generated_association_methods \
|
25
|
+
if ActiveRecord.version.to_s < "4.1"
|
26
|
+
|
27
|
+
# override
|
28
|
+
def dangerous_attribute_method?(name)
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
# dummy table name
|
33
|
+
def pluralize_table_names
|
34
|
+
self.to_s.pluralize
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# use by association accessor
|
39
|
+
def association(name) #:nodoc:
|
40
|
+
association = association_instance_get(name)
|
41
|
+
|
42
|
+
if association.nil?
|
43
|
+
reflection = self.class.reflect_on_association(name)
|
44
|
+
if reflection.options[:active_model]
|
45
|
+
association = ActiveRecord::Associations::HasManyForActiveModelAssociation.new(self, reflection)
|
46
|
+
else
|
47
|
+
association = reflection.association_class.new(self, reflection)
|
48
|
+
end
|
49
|
+
association_instance_set(name, association)
|
50
|
+
end
|
51
|
+
|
52
|
+
association
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# override
|
58
|
+
def validate_collection_association(reflection)
|
59
|
+
if association = association_instance_get(reflection.name)
|
60
|
+
if records = associated_records_to_validate_or_save(association, false, reflection.options[:autosave])
|
61
|
+
records.each { |record| association_valid?(reflection, record) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# use in Rails internal
|
67
|
+
def association_instance_get(name)
|
68
|
+
@association_cache[name]
|
69
|
+
end
|
70
|
+
|
71
|
+
# use in Rails internal
|
72
|
+
def association_instance_set(name, association)
|
73
|
+
@association_cache[name] = association
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActiveRecord::Associations
|
2
|
+
class HasManyForActiveModelAssociation < HasManyAssociation
|
3
|
+
# remove conditions: owner.new_record?, foreign_key_present?
|
4
|
+
def find_target?
|
5
|
+
!loaded? && klass
|
6
|
+
end
|
7
|
+
|
8
|
+
# no dependent action
|
9
|
+
def null_scope?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
# full replace simplely
|
14
|
+
def replace(other_array)
|
15
|
+
other_array.each { |val| raise_on_type_mismatch!(val) }
|
16
|
+
target_ids = reflection.options[:target_ids]
|
17
|
+
owner[target_ids] = other_array.map(&:id)
|
18
|
+
end
|
19
|
+
|
20
|
+
# no need load_target, and transaction
|
21
|
+
def concat(*records)
|
22
|
+
flatten_records = records.flatten
|
23
|
+
flatten_records.each { |val| raise_on_type_mismatch!(val) }
|
24
|
+
target_ids = reflection.options[:target_ids]
|
25
|
+
owner[target_ids] ||= []
|
26
|
+
owner[target_ids].concat(flatten_records.map(&:id))
|
27
|
+
reset
|
28
|
+
reset_scope
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "active_model"
|
2
|
+
require "active_record"
|
3
|
+
require "active_support"
|
4
|
+
require "active_model/associations"
|
5
|
+
require "active_model/associations/hooks"
|
6
|
+
|
7
|
+
# Load Railtie
|
8
|
+
begin
|
9
|
+
require "rails"
|
10
|
+
rescue LoadError
|
11
|
+
end
|
12
|
+
|
13
|
+
if defined?(Rails)
|
14
|
+
require "active_model/associations/railtie"
|
15
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveModel::Associations do
|
4
|
+
context "When included Comment class" do
|
5
|
+
class Comment
|
6
|
+
include ActiveModel::Model
|
7
|
+
include ActiveModel::Associations
|
8
|
+
|
9
|
+
attr_accessor :body, :user_id
|
10
|
+
|
11
|
+
belongs_to :user
|
12
|
+
|
13
|
+
def [](attr)
|
14
|
+
self.send(attr)
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(attr, value)
|
18
|
+
self.send("#{attr}=", value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "extends constructor" do
|
23
|
+
comment = Comment.new(body: "foo")
|
24
|
+
expect(comment.body).to eq "foo"
|
25
|
+
expect(comment.instance_variable_get("@association_cache")).to eq({})
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "belongs_to" do
|
29
|
+
it "Add belongs_to macro" do
|
30
|
+
expect(Comment).to be_respond_to(:belongs_to)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ".belongs_to" do
|
34
|
+
let(:comment) { Comment.new }
|
35
|
+
|
36
|
+
it "defines association accessor" do
|
37
|
+
expect(comment).to be_respond_to(:user)
|
38
|
+
expect(comment).to be_respond_to(:user=)
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "defined accessor" do
|
42
|
+
let(:user) { User.create(name: "joker1007") }
|
43
|
+
let(:comment) { Comment.new(user_id: user.id) }
|
44
|
+
|
45
|
+
it "defined accessor loads target ActiveRecord instance" do
|
46
|
+
expect(comment.user).to eq user
|
47
|
+
end
|
48
|
+
|
49
|
+
it "receives target ActiveRecord instance, and set foreign_key attributes" do
|
50
|
+
other_user = User.create(name: "kakyoin")
|
51
|
+
expect { comment.user = other_user }.to change { [comment.user, comment.user_id] }
|
52
|
+
.from([user, user.id]).to([other_user, other_user.id])
|
53
|
+
end
|
54
|
+
|
55
|
+
it "can validate" do
|
56
|
+
expect(comment.valid?).to be_true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "defined builder" do
|
61
|
+
it "sets foreign_key" do
|
62
|
+
comment.create_user(name: "joker1007")
|
63
|
+
expect(comment.user).to be_a(User)
|
64
|
+
expect(comment.user).to be_persisted
|
65
|
+
expect(comment.user_id).not_to be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "When set foreign_key manually" do
|
70
|
+
let!(:user) { User.create(name: "joker1007") }
|
71
|
+
let(:comment) { Comment.new }
|
72
|
+
|
73
|
+
it "can access target ActiveRecord instance" do
|
74
|
+
expect { comment.user_id = user.id }.to change { comment.user }
|
75
|
+
.from(nil).to(user)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can define polymorphic association" do
|
80
|
+
class PolymorhicBelongsToComment < Comment
|
81
|
+
attr_accessor :commenter_id, :commenter_type
|
82
|
+
belongs_to :commenter, polymorphic: true
|
83
|
+
end
|
84
|
+
|
85
|
+
user = User.create(name: "joker1007")
|
86
|
+
comment = PolymorhicBelongsToComment.new(commenter_id: user.id, commenter_type: "User")
|
87
|
+
expect(comment.commenter).to eq user
|
88
|
+
end
|
89
|
+
|
90
|
+
it "can define different class_name association" do
|
91
|
+
class DiffClassNameBelongsToComment < Comment
|
92
|
+
attr_accessor :commenter_id
|
93
|
+
belongs_to :commenter, class_name: "User"
|
94
|
+
end
|
95
|
+
|
96
|
+
user = User.create(name: "joker1007")
|
97
|
+
comment = DiffClassNameBelongsToComment.new(commenter: user)
|
98
|
+
expect(comment.commenter_id).to eq user.id
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "has_many" do
|
104
|
+
class Group
|
105
|
+
include ActiveModel::Model
|
106
|
+
include ActiveModel::Associations
|
107
|
+
|
108
|
+
attr_accessor :name
|
109
|
+
attr_reader :user_ids
|
110
|
+
|
111
|
+
has_many :users
|
112
|
+
|
113
|
+
def [](attr)
|
114
|
+
self.send(attr)
|
115
|
+
end
|
116
|
+
|
117
|
+
def []=(attr, value)
|
118
|
+
self.send("#{attr}=", value)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
let(:group) { Group.new }
|
123
|
+
|
124
|
+
it "Add has_many macro" do
|
125
|
+
expect(Group).to be_respond_to(:has_many)
|
126
|
+
end
|
127
|
+
|
128
|
+
describe ".has_many" do
|
129
|
+
it "defines association accessor" do
|
130
|
+
expect(group).to be_respond_to(:users)
|
131
|
+
expect(group).to be_respond_to(:users=)
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "defined accessor" do
|
135
|
+
let(:user1) { User.create(name: "joker1007") }
|
136
|
+
let(:user2) { User.create(name: "kakyoin") }
|
137
|
+
let(:group) { Group.new(user_ids: [user1.id, user2.id]) }
|
138
|
+
|
139
|
+
it "returns ActiveRecord CollectionProxy of target class" do
|
140
|
+
expect(group.users).to eq [user1, user2]
|
141
|
+
expect(group.users.find_by(id: user1.id)).to eq user1
|
142
|
+
end
|
143
|
+
|
144
|
+
it "receives target ActiveRecord instance Array, and set target_ids attributes" do
|
145
|
+
group = Group.new
|
146
|
+
expect(group.users).to be_empty
|
147
|
+
group.users = [user1, user2]
|
148
|
+
expect(group.users).to eq [user1, user2]
|
149
|
+
end
|
150
|
+
|
151
|
+
it "can replace having association" do
|
152
|
+
user3 = User.create(name: "jotaro")
|
153
|
+
group = Group.new
|
154
|
+
expect(group.users).to be_empty
|
155
|
+
group.users = [user1, user2]
|
156
|
+
group.users = [user3]
|
157
|
+
expect(group.users).to eq [user3]
|
158
|
+
end
|
159
|
+
|
160
|
+
it "can concat records" do
|
161
|
+
expect(group.users).to eq [user1, user2]
|
162
|
+
user3 = User.create(name: "jotaro")
|
163
|
+
group.users << user3
|
164
|
+
expect(group.users).to eq [user1, user2, user3]
|
165
|
+
end
|
166
|
+
|
167
|
+
it "can validate" do
|
168
|
+
expect(group.valid?).to be_true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context "When set target_ids manually" do
|
173
|
+
let!(:user) { User.create(name: "joker1007") }
|
174
|
+
let(:group) { Group.new }
|
175
|
+
|
176
|
+
it "can access target ActiveRecord instance" do
|
177
|
+
expect(group.users).to be_empty
|
178
|
+
group.user_ids = [user.id]
|
179
|
+
expect(group.users).to eq [user]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "When ids attribute is nil" do
|
184
|
+
let!(:user) { User.create(name: "joker1007") }
|
185
|
+
let(:group) { Group.new }
|
186
|
+
|
187
|
+
it "can concat" do
|
188
|
+
expect(group.users).to be_empty
|
189
|
+
group.users << user
|
190
|
+
expect(group.users).to eq [user]
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
it "can define different class_name association" do
|
195
|
+
class DiffClassNameHasManyGroup < Group
|
196
|
+
attr_reader :member_ids
|
197
|
+
has_many :members, class_name: "User"
|
198
|
+
end
|
199
|
+
|
200
|
+
user = User.create(name: "joker1007")
|
201
|
+
group = DiffClassNameHasManyGroup.new(members: [user])
|
202
|
+
expect(group.member_ids).to eq [user.id]
|
203
|
+
expect(group.members).to eq [user]
|
204
|
+
end
|
205
|
+
|
206
|
+
%i(through dependent source source_type counter_cache as).each do |option_name|
|
207
|
+
it "#{option_name} option is unsupported" do
|
208
|
+
expect {
|
209
|
+
eval <<-CODE
|
210
|
+
class #{option_name.to_s.classify}Group
|
211
|
+
include ActiveModel::Model
|
212
|
+
include ActiveModel::Associations
|
213
|
+
|
214
|
+
has_many :users, #{option_name}: true
|
215
|
+
end
|
216
|
+
CODE
|
217
|
+
}.to raise_error(ArgumentError)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'coveralls'
|
3
|
+
Coveralls.wear!
|
4
|
+
|
5
|
+
require 'activemodel/associations'
|
6
|
+
ActiveModel::Associations::Hooks.init
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(
|
9
|
+
adapter: "sqlite3",
|
10
|
+
database: ":memory:"
|
11
|
+
)
|
12
|
+
|
13
|
+
# Test Class
|
14
|
+
class User < ActiveRecord::Base; end
|
15
|
+
|
16
|
+
ActiveRecord::Migration.verbose = false
|
17
|
+
ActiveRecord::Migrator.migrate File.expand_path("../db/migrate", __FILE__), nil
|
18
|
+
|
19
|
+
require 'database_cleaner'
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.order = :random
|
23
|
+
|
24
|
+
config.before(:suite) do
|
25
|
+
DatabaseCleaner.strategy = :transaction
|
26
|
+
DatabaseCleaner.clean_with(:truncation)
|
27
|
+
end
|
28
|
+
|
29
|
+
config.before(:each) do
|
30
|
+
DatabaseCleaner.start
|
31
|
+
end
|
32
|
+
|
33
|
+
config.after(:each) do
|
34
|
+
DatabaseCleaner.clean
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activemodel-associations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- joker1007
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "<"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "<"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "<"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "<"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "<"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.6'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: tapp
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: database_cleaner
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: coveralls
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: ActiveRecord Association Helper for PORO
|
154
|
+
email:
|
155
|
+
- kakyoin.hierophant@gmail.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- ".gitignore"
|
161
|
+
- ".rspec"
|
162
|
+
- ".travis.yml"
|
163
|
+
- Gemfile
|
164
|
+
- LICENSE.txt
|
165
|
+
- README.md
|
166
|
+
- Rakefile
|
167
|
+
- activemodel-associations.gemspec
|
168
|
+
- gemfiles/activerecord-40.gemfile
|
169
|
+
- gemfiles/activerecord-41.gemfile
|
170
|
+
- lib/active_model/associations.rb
|
171
|
+
- lib/active_model/associations/active_record_reflection.rb
|
172
|
+
- lib/active_model/associations/association_scope_extension.rb
|
173
|
+
- lib/active_model/associations/autosave_association.rb
|
174
|
+
- lib/active_model/associations/hooks.rb
|
175
|
+
- lib/active_model/associations/initialize_extension.rb
|
176
|
+
- lib/active_model/associations/override_methods.rb
|
177
|
+
- lib/active_model/associations/railtie.rb
|
178
|
+
- lib/active_model/associations/version.rb
|
179
|
+
- lib/active_record/associations/builder/has_many_for_active_model.rb
|
180
|
+
- lib/active_record/associations/has_many_for_active_model_association.rb
|
181
|
+
- lib/activemodel/associations.rb
|
182
|
+
- spec/db/migrate/10_create_users.rb
|
183
|
+
- spec/lib/active_model/association_spec.rb
|
184
|
+
- spec/spec_helper.rb
|
185
|
+
homepage: ''
|
186
|
+
licenses:
|
187
|
+
- MIT
|
188
|
+
metadata: {}
|
189
|
+
post_install_message:
|
190
|
+
rdoc_options: []
|
191
|
+
require_paths:
|
192
|
+
- lib
|
193
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
|
+
requirements:
|
200
|
+
- - ">="
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
requirements: []
|
204
|
+
rubyforge_project:
|
205
|
+
rubygems_version: 2.2.2
|
206
|
+
signing_key:
|
207
|
+
specification_version: 4
|
208
|
+
summary: ActiveRecord Association Helper for PORO
|
209
|
+
test_files:
|
210
|
+
- spec/db/migrate/10_create_users.rb
|
211
|
+
- spec/lib/active_model/association_spec.rb
|
212
|
+
- spec/spec_helper.rb
|