active_record-any_links 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +209 -0
- data/Rakefile +6 -0
- data/active_record-any_links.gemspec +34 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/active_record/any_links/class_methods.rb +224 -0
- data/lib/active_record/any_links/version.rb +5 -0
- data/lib/active_record/any_links.rb +9 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5d991efbc26c4de04ea6203b73356fdb35806afd
|
4
|
+
data.tar.gz: cb1a9f0466c18907fd4ff35fb5a3264310279ea4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bf656d93c9a5cda2cc9e8996df81a93fa41e7193c6427a739b32f2c9a7db1b13f22fb92bb60b4fdf7021877c9ef069f684594af76fb673f344d90bf44ba45c83
|
7
|
+
data.tar.gz: f47fcedc79318c8700c5952bc3fa0b1d78306a2f2184e35a2d947ba37b1af78e2685e19c3a6a67fe194bb3d62df8deb72e806e802cd5477f50415058cbcc1d3d
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017, Samanage
|
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,209 @@
|
|
1
|
+
# AnyLinks
|
2
|
+
|
3
|
+
AnyLinks is an ActiveRecord extension which enables simple linking any model to any other model.
|
4
|
+
|
5
|
+
It requires a single table holding the connection of all types, and a single statement for each linked model.
|
6
|
+
No pass-through model required.
|
7
|
+
|
8
|
+
The links can be many-to-many or one-to-many, and can link
|
9
|
+
also any model to itself, with bidirectional connection.
|
10
|
+
|
11
|
+
It is a simple-to-use extension to the polymorphic schema, but without the complexity and with much more capabilities.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'active_record-any_links'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install active_record-any_links
|
28
|
+
|
29
|
+
## Getting Started
|
30
|
+
|
31
|
+
### 1. Create any_links table
|
32
|
+
|
33
|
+
AnyLinks requires an any_links table to store the links.
|
34
|
+
You can create the table by adding the following migration, and running:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
rake db:migrate
|
38
|
+
```
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class CreateAnyLinks < ActiveRecord::Migration
|
42
|
+
def up
|
43
|
+
create_table :any_links do |t|
|
44
|
+
t.integer :id1, null: false
|
45
|
+
t.string :type1, null: false
|
46
|
+
t.integer :id2, null: false
|
47
|
+
t.string :type2, null: false
|
48
|
+
|
49
|
+
t.timestamps
|
50
|
+
end
|
51
|
+
|
52
|
+
change_table :any_links do |t|
|
53
|
+
t.index [:id1, :type1, :id2, :type2], unique: true
|
54
|
+
t.index [:id1, :type1, :type2]
|
55
|
+
t.index [:id2, :type2, :type1]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def down
|
60
|
+
drop_table :any_links
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
### 2. Linking models
|
66
|
+
|
67
|
+
In the following example we link the User model to Group model, using many to many relationship.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class User < ActiveRecord::Base
|
71
|
+
has_many_to_many :groups
|
72
|
+
end
|
73
|
+
|
74
|
+
class Group < ActiveRecord::Base
|
75
|
+
has_many_to_many :users
|
76
|
+
end
|
77
|
+
```
|
78
|
+
### 3. Using the linked models
|
79
|
+
|
80
|
+
Use the linked models the same way you use any has_many relationship.
|
81
|
+
examples:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
@user.groups = [group, group]
|
85
|
+
@group.user_ids # => [4,7,9]
|
86
|
+
@group.users.exists?
|
87
|
+
@group.users.where(first_name: "James")
|
88
|
+
```
|
89
|
+
|
90
|
+
## AnyLinks statements
|
91
|
+
|
92
|
+
There are three statements added to ActiveRecord:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
has_many_to_many
|
96
|
+
has_many_to_one
|
97
|
+
has_one_to_many
|
98
|
+
```
|
99
|
+
|
100
|
+
Each one of them creates a bundle of helper methods.
|
101
|
+
|
102
|
+
## has_many_to_many
|
103
|
+
|
104
|
+
has_many_to_many Specifies a many-to-many bi-directional association.
|
105
|
+
Regularly, has_many_to_many is declared in each side of the association models.
|
106
|
+
It will also work if declared only on one side of the association. However, doing so will
|
107
|
+
not generate the dynamic methods on the other side, making the connections seen from
|
108
|
+
one side only
|
109
|
+
|
110
|
+
has_many_to_many can be used even on the same model:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
class User < ActiveRecord::Base
|
114
|
+
has_many_to_many :friends, class_name: User
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
The previous link is also bidirectional!
|
119
|
+
The associations, for all types and collections, are stored together in a single table called 'any_links'.
|
120
|
+
The table contain all links between all instances of any type.
|
121
|
+
Every record represent a link, which contains id and type for each side of the connection,
|
122
|
+
which are called id1, type1, and id2, type2 respectively.
|
123
|
+
Since the links are bi-directional, theoretically, any object can be represented as the left
|
124
|
+
side link (#1) or right side (#2).
|
125
|
+
For simplicity and performance, the links are stored thus the "smallest" type is in the left.
|
126
|
+
Any link betwwen 'Incident' and 'Change' is stored as the Change object in the left, since
|
127
|
+
the string types are 'Change' < 'Incident'.
|
128
|
+
This technique is 'Hidden' and transparent for the has_many_to_many consumer.
|
129
|
+
|
130
|
+
For association of the same type - (e.g. `Incident has_many incidents`):
|
131
|
+
do standard declaration in the Class (i.e. has_many_to_many <collection>).
|
132
|
+
In that case, a callbacks of after create and after destroy shall be called in the relevant any_link class.
|
133
|
+
To overcome association methods that skip callbacks (as clear method) we override the delete_all
|
134
|
+
association method of has many by passing a block with the new function.
|
135
|
+
Notice that calling with 'dependent' param in this case shall raise an error since it doesn't have a meaning
|
136
|
+
for once and secondly this param cannot be passed to destroy_all which is the callback that we call.
|
137
|
+
|
138
|
+
A set dynamic methods are generated for altering the set of linked objects.
|
139
|
+
They are the same methods as added by has_many decleration.
|
140
|
+
The following methods for retrieval and query of
|
141
|
+
collections of associated objects will be added:
|
142
|
+
|
143
|
+
#### Auto-generated methods
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
others
|
147
|
+
others=(other,other,...)
|
148
|
+
other_ids
|
149
|
+
other_ids=(id,id,...)
|
150
|
+
others<<
|
151
|
+
others.push
|
152
|
+
others.concat
|
153
|
+
others.build(attributes={})
|
154
|
+
others.create(attributes={})
|
155
|
+
others.create!(attributes={})
|
156
|
+
others.size
|
157
|
+
others.length
|
158
|
+
others.count
|
159
|
+
others.sum(args*,&block)
|
160
|
+
others.empty?
|
161
|
+
others.clear
|
162
|
+
others.delete(other,other,...)
|
163
|
+
others.delete_all
|
164
|
+
others.destroy_all
|
165
|
+
others.find(*args)
|
166
|
+
others.exists?
|
167
|
+
others.uniq
|
168
|
+
others.reset
|
169
|
+
```
|
170
|
+
#### Options
|
171
|
+
|
172
|
+
[:class_name]
|
173
|
+
Specify the class name of the association. Use it only if that name can't be inferred
|
174
|
+
from the association name. So <tt>has_many_to_many :products</tt> will by default be linked
|
175
|
+
to the Product class, but if the real class name is SpecialProduct, you'll have to
|
176
|
+
specify it with this option.
|
177
|
+
|
178
|
+
#### Examples
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
has_many_to_many :authors # linked class is Author
|
182
|
+
has_many_to_many :authors, class_name: "Person" # specify that linked class is Person
|
183
|
+
```
|
184
|
+
|
185
|
+
## has_one_to_many, has_many_to_one
|
186
|
+
|
187
|
+
has_one_to_many Specifies a one-to-many bi-directional association.
|
188
|
+
The has_one_to_many is declared in one side of the association models. The other side must declare the opposite direction with has_many_to_one decleration.
|
189
|
+
Notice: It differ from has_many_to_many, which will work also if only one side of the association is
|
190
|
+
declared.
|
191
|
+
|
192
|
+
The has_one_to_many and has_many_to_one use the has_many_to_many to create the relatioships, in the same
|
193
|
+
table, but with restriction to a single connection from the has_one side.
|
194
|
+
It means that the decleration creates plural helper functions as above in additition to the following
|
195
|
+
singular helper functions.
|
196
|
+
|
197
|
+
#### Singular Auto-generated methods
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
other
|
201
|
+
other=other
|
202
|
+
other_id
|
203
|
+
other_id=id
|
204
|
+
```
|
205
|
+
|
206
|
+
## Contributing
|
207
|
+
|
208
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Hagai-Arzi/active_record-any_links.
|
209
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
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/any_links/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "active_record-any_links"
|
8
|
+
spec.version = ActiveRecord::AnyLinks::VERSION
|
9
|
+
spec.authors = ["Hagai Arzi"]
|
10
|
+
spec.email = ["Hagai.Arzi@Gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Enable link objects of different models in a single table using only a single statement.}
|
13
|
+
spec.description = %q{Enable link objects of different models in a single table using has_many_to_many, has_one_to_many or has_many_to_one methods.}
|
14
|
+
spec.homepage = "https://github.com/Hagai-Arzi/active_record-any_links/tree/master/lib/active_record"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "sqlite3", "~> 1.3"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
|
28
|
+
# spec.add_development_dependency 'byebug'
|
29
|
+
# spec.add_development_dependency 'pry-byebug'
|
30
|
+
# spec.add_development_dependency 'pry-rails'
|
31
|
+
# spec.add_development_dependency 'pry-stack_explorer'
|
32
|
+
|
33
|
+
spec.add_dependency "activerecord", ">= 4.2"
|
34
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "active_record/any_links"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AnyLinks
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
# has_many_to_many Specifies a many-to-many bi-directional association.
|
6
|
+
# Regularly, the has_many_to_many is declared in each side of the association models.
|
7
|
+
# It will also work if declared only on one side of the association. However, doing so will
|
8
|
+
# not generate the dynamic methods on the other side, making the connections seen from
|
9
|
+
# one side only
|
10
|
+
#
|
11
|
+
# The associations, for all types and collections, are stored together in a single table called 'any_links'.
|
12
|
+
# The table contain all links between all instances of any type.
|
13
|
+
# Every record represent a link, which contains id and type for each side of the connection,
|
14
|
+
# which are called id1, type1, and id2, type2 respectively.
|
15
|
+
# Since the links are bi-directional, theoretically, any object can be represented as the left
|
16
|
+
# side link (#1) or right side (#2).
|
17
|
+
# For simplicity and performance, the links are stored thus the "smallest" type is in the left.
|
18
|
+
# Any link betwwen 'Incident' and 'Change' is stored as the Change object in the left, since
|
19
|
+
# the string types are 'Change' < 'Incident'.
|
20
|
+
# This technique is 'Hidden' and transparent for the has_many_to_many consumer.
|
21
|
+
#
|
22
|
+
# For association of the same type - (e.g. Incident has_many incidents):
|
23
|
+
# do standard declaration in the Class (i.e. has_many_to_many <collection>).
|
24
|
+
# In that case, a callbacks of after create and after destroy shall be called in the relevant any_link class.
|
25
|
+
# To overcome association methods that skip callbacks (as clear method) we override the delete_all
|
26
|
+
# association method of has many by passing a block with the new function.
|
27
|
+
# Notice that calling with 'dependent' param in this case shall raise an error since it doesn't have a meaning
|
28
|
+
# for once and secondly this param cannot be passed to destroy_all which is the callback that we call.
|
29
|
+
#
|
30
|
+
# The table should manually generated with a migration such as this:
|
31
|
+
#
|
32
|
+
# class CreateAnyLinks < ActiveRecord::Migration
|
33
|
+
# def up
|
34
|
+
# create_table :any_links do |t|
|
35
|
+
# t.integer :id1, null: false
|
36
|
+
# t.string :type1, null: false
|
37
|
+
# t.integer :id2, null: false
|
38
|
+
# t.string :type2, null: false
|
39
|
+
#
|
40
|
+
# t.timestamps
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# change_table :any_links do |t|
|
44
|
+
# t.index [:id1, :type1, :id2, :type2], unique: true
|
45
|
+
# t.index [:id1, :type1, :type2]
|
46
|
+
# t.index [:id2, :type2, :type1]
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# def down
|
51
|
+
# drop_table :any_links
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
|
55
|
+
# A set dynamic methods are generated for altering the set of linked objects.
|
56
|
+
# They are the same methods as added by has_many decleration.
|
57
|
+
# The following methods for retrieval and query of
|
58
|
+
# collections of associated objects will be added:
|
59
|
+
#
|
60
|
+
# Auto-generated methods
|
61
|
+
# --------------------------------------
|
62
|
+
# others
|
63
|
+
# others=(other,other,...)
|
64
|
+
# other_ids
|
65
|
+
# other_ids=(id,id,...)
|
66
|
+
# others<<
|
67
|
+
# others.push
|
68
|
+
# others.concat
|
69
|
+
# others.build(attributes={})
|
70
|
+
# others.create(attributes={})
|
71
|
+
# others.create!(attributes={})
|
72
|
+
# others.size
|
73
|
+
# others.length
|
74
|
+
# others.count
|
75
|
+
# others.sum(args*,&block)
|
76
|
+
# others.empty?
|
77
|
+
# others.clear
|
78
|
+
# others.delete(other,other,...)
|
79
|
+
# others.delete_all
|
80
|
+
# others.destroy_all
|
81
|
+
# others.find(*args)
|
82
|
+
# others.exists?
|
83
|
+
# others.uniq
|
84
|
+
# others.reset
|
85
|
+
#
|
86
|
+
# === Options
|
87
|
+
#
|
88
|
+
# [:class_name]
|
89
|
+
# Specify the class name of the association. Use it only if that name can't be inferred
|
90
|
+
# from the association name. So <tt>has_many_to_many :products</tt> will by default be linked
|
91
|
+
# to the Product class, but if the real class name is SpecialProduct, you'll have to
|
92
|
+
# specify it with this option.
|
93
|
+
#
|
94
|
+
# === Examples
|
95
|
+
#
|
96
|
+
# has_many_to_many :authors # linked class is Author
|
97
|
+
# has_many_to_many :authors, class_name: "Person" # specify that linked class is Person
|
98
|
+
def has_many_to_many(collection, options = {})
|
99
|
+
class_name = options[:class_name]
|
100
|
+
link_items, foreign_key, source = define_link_class(class_name || collection)
|
101
|
+
has_many link_items, foreign_key: foreign_key
|
102
|
+
if !identical_source_collection_class(class_name || collection)
|
103
|
+
has_many collection, { through: link_items, source: source, dependent: :destroy }.merge(options)
|
104
|
+
else
|
105
|
+
has_many collection, through: link_items, source: source, dependent: :destroy do
|
106
|
+
def delete_all(dependent = nil)
|
107
|
+
if dependent.present?
|
108
|
+
raise "The dependency is not supported nor relevant to the case of has_many_to_many since the relation is through association table any_links!"
|
109
|
+
end
|
110
|
+
destroy_all
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# has_one_to_many Specifies a one-to-many bi-directional association.
|
117
|
+
# The has_one_to_many is declared in one side of the association models. The other side
|
118
|
+
# must declare the opposite direction with has_many_to_one decleration.
|
119
|
+
# Notice: It differ from has_many_to_many, which will work also if only one side of the association is
|
120
|
+
# declared.
|
121
|
+
#
|
122
|
+
# The has_one_to_many and has_many_to_one use the has_many_to_many to create the relatioships, in the same
|
123
|
+
# table, but with restriction to a single connection from the has_one side.
|
124
|
+
# It means that the decleration creates plural helper functions as above in additition to the following
|
125
|
+
# singular helper functions.
|
126
|
+
#
|
127
|
+
# Singular Auto-generated methods
|
128
|
+
# --------------------------------------
|
129
|
+
# other
|
130
|
+
# other=other
|
131
|
+
# other_id
|
132
|
+
# other_id=id
|
133
|
+
#
|
134
|
+
def has_one_to_many(collection, options = {})
|
135
|
+
item = collection.to_s.singularize.to_sym
|
136
|
+
has_many_to_many(
|
137
|
+
item.to_s.pluralize.to_sym,
|
138
|
+
options.merge(before_add: -> (owner, obj) { raise ActiveRecord::RecordNotUnique.new("#{name} can have only one #{obj.class} associated") if owner.send(item).present? })
|
139
|
+
)
|
140
|
+
AnyLinks.define_singular_accessors(self, item, collection)
|
141
|
+
end
|
142
|
+
|
143
|
+
def has_many_to_one(collection, options = {})
|
144
|
+
has_many_to_many(collection, options.merge(before_add: -> (_owner, obj) { obj.send(name.demodulize.underscore.pluralize).clear }))
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def identical_source_collection_class(collection_or_class)
|
150
|
+
name == collection_or_class.to_s.singularize.classify
|
151
|
+
end
|
152
|
+
|
153
|
+
def define_link_class(collection)
|
154
|
+
klass1 = name
|
155
|
+
klass2 = collection.to_s.singularize.classify
|
156
|
+
model1 = name.demodulize.underscore
|
157
|
+
model2 = collection.to_s.demodulize.singularize.underscore
|
158
|
+
foreign_key = :id1
|
159
|
+
source = (klass1 == klass2 ? model2 + "_ghost" : model2).to_sym
|
160
|
+
model1, model2, klass1, klass2, foreign_key = model2, model1, klass2, klass1, :id2 if model1 > model2
|
161
|
+
klass_name = "#{model1}_#{model2}_link".classify
|
162
|
+
model2 = source if klass1 == klass2
|
163
|
+
|
164
|
+
define_class(klass_name, ActiveRecord::Base) do
|
165
|
+
if klass1 == klass2
|
166
|
+
attr_accessor :duplicated_relation
|
167
|
+
after_create :create_opposite_relation
|
168
|
+
after_destroy :destroy_opposite_relation
|
169
|
+
end
|
170
|
+
|
171
|
+
self.table_name = 'any_links'
|
172
|
+
|
173
|
+
belongs_to model1.to_sym, foreign_key: :id1, class_name: klass1
|
174
|
+
belongs_to model2.to_sym, foreign_key: :id2, class_name: klass2
|
175
|
+
|
176
|
+
default_scope { where(type1: klass1, type2: klass2) }
|
177
|
+
|
178
|
+
def create_opposite_relation
|
179
|
+
if !duplicated_relation && id2 != id1
|
180
|
+
self.class.create!(type1: type1, id1: id2, type2: type1, id2: id1, duplicated_relation: true)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def destroy_opposite_relation
|
185
|
+
self.class.find_by(type1: type1, type2: type2, id1: id2, id2: id1).try(:destroy)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
[klass_name.underscore.pluralize.to_sym, foreign_key, source]
|
190
|
+
end
|
191
|
+
|
192
|
+
def define_class(klass_name, ancestor, &block)
|
193
|
+
if !const_defined?(klass_name)
|
194
|
+
klass = Class.new(ancestor, &block)
|
195
|
+
Object.const_set(klass_name, klass)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.define_singular_accessors(model, item, plural_name)
|
201
|
+
mixin = model.generated_association_methods
|
202
|
+
define_readers(mixin, item, plural_name)
|
203
|
+
define_writers(mixin, item, plural_name)
|
204
|
+
define_readers(mixin, "#{item}_id", "#{item}_ids")
|
205
|
+
define_writers(mixin, "#{item}_id", "#{item}_ids")
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.define_readers(mixin, name, plural_name)
|
209
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
210
|
+
def #{name}(*args)
|
211
|
+
send("#{plural_name}")[0]
|
212
|
+
end
|
213
|
+
CODE
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.define_writers(mixin, name, plural_name)
|
217
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
218
|
+
def #{name}=(value)
|
219
|
+
value.nil? ? send("#{plural_name}=", []) : send("#{plural_name}=", [value])
|
220
|
+
end
|
221
|
+
CODE
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_record-any_links
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hagai Arzi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-02-03 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.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activerecord
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.2'
|
83
|
+
description: Enable link objects of different models in a single table using has_many_to_many,
|
84
|
+
has_one_to_many or has_many_to_one methods.
|
85
|
+
email:
|
86
|
+
- Hagai.Arzi@Gmail.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".travis.yml"
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- active_record-any_links.gemspec
|
99
|
+
- bin/console
|
100
|
+
- bin/setup
|
101
|
+
- lib/active_record/any_links.rb
|
102
|
+
- lib/active_record/any_links/class_methods.rb
|
103
|
+
- lib/active_record/any_links/version.rb
|
104
|
+
homepage: https://github.com/Hagai-Arzi/active_record-any_links/tree/master/lib/active_record
|
105
|
+
licenses: []
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.6.8
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: Enable link objects of different models in a single table using only a single
|
127
|
+
statement.
|
128
|
+
test_files: []
|