rails_dynamic_associations 0.2.0.real → 0.2.1.real
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 +4 -4
- data/app/models/relation.rb +39 -12
- data/app/models/role.rb +10 -5
- data/config/initializers/helpers.rb +5 -0
- data/config/initializers/model_associations.rb +1 -1
- data/db/migrate/02_create_relations.rb +3 -1
- data/lib/rails_dynamic_associations/active_record/associations.rb +32 -22
- data/lib/rails_dynamic_associations/active_record/relations.rb +56 -1
- data/lib/rails_dynamic_associations/active_record.rb +5 -6
- data/lib/rails_dynamic_associations/config.rb +62 -0
- data/lib/rails_dynamic_associations/engine.rb +15 -0
- data/lib/rails_dynamic_associations/version.rb +1 -1
- data/lib/rails_dynamic_associations.rb +2 -28
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e750c6c7f3dff5b63c11d91a5636cb7aeffc31b
|
4
|
+
data.tar.gz: c8f63f3d0831bbe148c3ebde7563f36548d078e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed9168cf7e38072e7971ecf38a9550d5a0c30ad27cd2a998e943d8cc7c7211243c94bfd7b475a326e0c1466debe3352b8ed0832de66defb86d57a027adcecd22
|
7
|
+
data.tar.gz: 6fb4f259c4915c9fe4c6edb2223e8ebe8376315c937c31e3f2ffb7a95fcc8afd04ff79382c72efa923d75f5035a71377020c92dc20d642f9efd57bb967b8d135
|
data/app/models/relation.rb
CHANGED
@@ -5,14 +5,18 @@ class Relation < ActiveRecord::Base
|
|
5
5
|
|
6
6
|
delegate :name, to: :role, allow_nil: true
|
7
7
|
|
8
|
-
|
8
|
+
default_scope {
|
9
|
+
references(:roles).includes :role
|
10
|
+
}
|
11
|
+
|
12
|
+
association_directions.shortcuts.each &-> (association, method) do
|
9
13
|
scope "#{method}_abstract", -> (object = nil) {
|
10
14
|
if object then
|
11
|
-
send
|
15
|
+
send("#{method}_abstract").
|
16
|
+
send method, object
|
12
17
|
else
|
13
|
-
|
14
|
-
end
|
15
|
-
where "#{association}_id" => nil
|
18
|
+
where "#{association}_id" => nil
|
19
|
+
end
|
16
20
|
}
|
17
21
|
|
18
22
|
scope "#{method}_general", -> {
|
@@ -22,6 +26,8 @@ class Relation < ActiveRecord::Base
|
|
22
26
|
|
23
27
|
scope method, -> (object) {
|
24
28
|
case object
|
29
|
+
when nil then
|
30
|
+
# all
|
25
31
|
when Symbol then
|
26
32
|
send "#{method}_#{object}"
|
27
33
|
when Class then
|
@@ -37,16 +43,34 @@ class Relation < ActiveRecord::Base
|
|
37
43
|
of_abstract.to_abstract
|
38
44
|
}
|
39
45
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
46
|
+
scope :applied, -> {
|
47
|
+
where.not source_id: nil,
|
48
|
+
target_id: nil
|
49
|
+
}
|
50
|
+
|
51
|
+
scope :named, -> (*names) {
|
52
|
+
case names
|
53
|
+
when [[]] then # i.e. `named []`
|
54
|
+
# all
|
55
|
+
when [] then # i.e. `named`
|
56
|
+
where.not role_id: nil
|
57
|
+
else
|
58
|
+
with_roles { named *names }
|
44
59
|
end
|
60
|
+
}
|
61
|
+
|
62
|
+
def self.with_roles &block
|
63
|
+
joins(:role).merge(
|
64
|
+
Role.instance_eval &block
|
65
|
+
).uniq
|
45
66
|
end
|
46
67
|
|
47
|
-
def self.
|
48
|
-
Role.find_or_create_named(
|
49
|
-
|
68
|
+
def self.seed source, target, roles = nil
|
69
|
+
(roles.present? ? Role.find_or_create_named(roles) : [ nil ]).map do |role|
|
70
|
+
create source_type: source,
|
71
|
+
target_type: target,
|
72
|
+
role: role
|
73
|
+
end
|
50
74
|
end
|
51
75
|
|
52
76
|
|
@@ -55,6 +79,9 @@ class Relation < ActiveRecord::Base
|
|
55
79
|
Role.find_or_initialize_by(name: role_name)
|
56
80
|
end
|
57
81
|
|
82
|
+
def abstract?
|
83
|
+
not (source_id or target_id)
|
84
|
+
end
|
58
85
|
|
59
86
|
# Using polymorphic associations in combination with single table inheritance (STI) is
|
60
87
|
# a little tricky. In order for the associations to work as expected, ensure that you
|
data/app/models/role.rb
CHANGED
@@ -8,25 +8,30 @@ class Role < ActiveRecord::Base
|
|
8
8
|
}
|
9
9
|
|
10
10
|
scope :available, -> {
|
11
|
-
|
12
|
-
where relations: { id: Relation.of_abstract } # TODO: simplify
|
11
|
+
with_relations { of_abstract }
|
13
12
|
}
|
14
13
|
|
15
14
|
scope :in, -> (object) {
|
16
|
-
|
15
|
+
with_relations { to object }
|
17
16
|
}
|
18
17
|
|
19
18
|
scope :for, -> (subject) {
|
20
|
-
|
19
|
+
with_relations { of subject }
|
21
20
|
}
|
22
21
|
|
23
22
|
def self.find_or_create_named *names
|
24
23
|
names.flatten!
|
25
24
|
names.compact!
|
26
25
|
|
27
|
-
(existing = named(names)).
|
26
|
+
(existing = named(names)).to_a +
|
28
27
|
(names - existing.map(&:name)).map { |name|
|
29
28
|
create name: name
|
30
29
|
}
|
31
30
|
end
|
31
|
+
|
32
|
+
def self.with_relations &block
|
33
|
+
joins(:relations).merge(
|
34
|
+
Relation.instance_eval &block
|
35
|
+
).uniq
|
36
|
+
end
|
32
37
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
ActiveSupport.on_load :model_class do
|
2
|
-
for type in
|
2
|
+
for type in association_directions do
|
3
3
|
for relation in send("#{type}_relations").abstract.select(&:"#{type}_type") do
|
4
4
|
setup_relation type, relation.send("#{type}_type").constantize, relation.role
|
5
5
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
class CreateRelations < ActiveRecord::Migration
|
2
2
|
def change
|
3
3
|
create_table :relations do |t|
|
4
|
+
t.string :type
|
5
|
+
|
4
6
|
t.references :source, polymorphic: { default: 'User' }
|
5
7
|
t.references :target, polymorphic: true
|
6
8
|
t.references :role
|
@@ -9,6 +11,6 @@ class CreateRelations < ActiveRecord::Migration
|
|
9
11
|
end
|
10
12
|
|
11
13
|
add_index :relations, [ :source_id, :source_type, :target_id, :target_type, :role_id ], unique: true,
|
12
|
-
|
14
|
+
name: 'index_relations_on_source_and_target_and_role'
|
13
15
|
end
|
14
16
|
end
|
@@ -5,13 +5,15 @@ module RailsDynamicAssociations::ActiveRecord
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
+
include RailsDynamicAssociations::Config
|
9
|
+
|
8
10
|
protected
|
9
11
|
|
10
12
|
def setup_relation type, target = self, role = nil
|
11
13
|
define_association type, target
|
12
14
|
define_association type, target, role if role
|
13
15
|
|
14
|
-
for association, method in
|
16
|
+
for association, method in association_directions.recursive do
|
15
17
|
define_recursive_methods association, method if association.in? reflections and not method_defined? method
|
16
18
|
end
|
17
19
|
end
|
@@ -22,7 +24,7 @@ module RailsDynamicAssociations::ActiveRecord
|
|
22
24
|
:"#{role ? association_name(type, target, role).to_s.singularize : type}_relations".tap do |association|
|
23
25
|
unless association.in? reflections then
|
24
26
|
has_many association, role && -> { where role_id: role.id },
|
25
|
-
|
27
|
+
as: association_directions.opposite(type), class_name: 'Relation'
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -30,10 +32,10 @@ module RailsDynamicAssociations::ActiveRecord
|
|
30
32
|
def define_association type, target = self, role = nil
|
31
33
|
unless (association = association_name(type, target, role)).in? reflections then
|
32
34
|
has_many association,
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
through: define_relations_association(type, target, role),
|
36
|
+
source: type,
|
37
|
+
source_type: target.base_class.name,
|
38
|
+
class_name: target.name
|
37
39
|
|
38
40
|
define_association_with_roles association unless role
|
39
41
|
|
@@ -77,25 +79,33 @@ module RailsDynamicAssociations::ActiveRecord
|
|
77
79
|
|
78
80
|
def association_name type, target = self, role = nil
|
79
81
|
if role then
|
80
|
-
|
81
|
-
{
|
82
|
-
source: role.name,
|
83
|
-
target: "#{role.name.passivize}_#{target.name.split('::').reverse.join}",
|
84
|
-
}[type]
|
85
|
-
else
|
86
|
-
"#{{
|
87
|
-
source: role.name,
|
88
|
-
target: role.name.passivize,
|
89
|
-
}[type]}_#{association_name type, target}"
|
90
|
-
end
|
82
|
+
association_name_with_role type, target, role
|
91
83
|
else
|
92
|
-
|
93
|
-
RailsDynamicAssociations.self_referential[type].to_s
|
94
|
-
else
|
95
|
-
target.name.split('::').reverse.join
|
96
|
-
end
|
84
|
+
association_name_without_role type, target
|
97
85
|
end.tableize.to_sym
|
98
86
|
end
|
87
|
+
|
88
|
+
def association_name_with_role type, target, role
|
89
|
+
if target == self or target <= User then
|
90
|
+
{
|
91
|
+
source: role.name,
|
92
|
+
target: "#{role.name.passivize}_#{target.name.split('::').reverse.join}",
|
93
|
+
}[type]
|
94
|
+
else
|
95
|
+
"#{{
|
96
|
+
source: role.name,
|
97
|
+
target: role.name.passivize,
|
98
|
+
}[type]}_#{association_name_without_role type, target}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def association_name_without_role type, target
|
103
|
+
if target == self then
|
104
|
+
association_directions.selfed[type].to_s
|
105
|
+
else
|
106
|
+
target.name.split('::').reverse.join
|
107
|
+
end
|
108
|
+
end
|
99
109
|
end
|
100
110
|
end
|
101
111
|
end
|
@@ -4,12 +4,67 @@ module RailsDynamicAssociations::ActiveRecord
|
|
4
4
|
module Relations
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
+
included do
|
8
|
+
extend ClassAndInstanceMethods
|
9
|
+
include ClassAndInstanceMethods
|
10
|
+
end
|
11
|
+
|
7
12
|
module ClassMethods
|
8
|
-
RailsDynamicAssociations.
|
13
|
+
RailsDynamicAssociations::Config.association_directions.opposite_shortcuts.each &-> (association, method) do
|
9
14
|
define_method "#{association}_relations" do
|
10
15
|
Relation.send method, self
|
11
16
|
end
|
12
17
|
end
|
13
18
|
end
|
19
|
+
|
20
|
+
module ClassAndInstanceMethods
|
21
|
+
def relative? args = {}
|
22
|
+
find_relations(args).
|
23
|
+
present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def relatives args = {}
|
27
|
+
find_relations(args).
|
28
|
+
map { |r|
|
29
|
+
# TODO: optimize queries
|
30
|
+
(association_directions.map { |d| r.send d } - [ self ]).first
|
31
|
+
}.uniq
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
# TODO: use keyword arguments
|
37
|
+
def find_relations args = {}
|
38
|
+
# Rearrange arguments
|
39
|
+
for direction, method in association_directions.shortcuts do
|
40
|
+
args[direction] = args.delete method if
|
41
|
+
method.in? args
|
42
|
+
end
|
43
|
+
|
44
|
+
roles = :as.in?(args) ?
|
45
|
+
[ args[:as] ].flatten :
|
46
|
+
[]
|
47
|
+
|
48
|
+
if direction = association_directions.find { |a| a.in? args } then # direction specified
|
49
|
+
find_relations_with_direction(direction, roles).send(
|
50
|
+
association_directions.shortcuts[direction], args[direction] # apply a filtering scope
|
51
|
+
)
|
52
|
+
else # both directions
|
53
|
+
association_directions.map do |direction|
|
54
|
+
find_relations_with_direction direction, roles
|
55
|
+
end.sum
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def find_relations_with_direction direction, roles = []
|
62
|
+
if respond_to? association = "#{direction}_relations" then
|
63
|
+
send(association).named roles
|
64
|
+
else
|
65
|
+
Relation.none
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
14
69
|
end
|
15
70
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
include RailsDynamicAssociations::ActiveRecord::Relations
|
1
|
+
module RailsDynamicAssociations
|
2
|
+
module ActiveRecord
|
3
|
+
autoload :Associations, 'rails_dynamic_associations/active_record/associations'
|
4
|
+
autoload :Relations, 'rails_dynamic_associations/active_record/relations'
|
5
|
+
end
|
7
6
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module RailsDynamicAssociations
|
2
|
+
module Config
|
3
|
+
module Naming
|
4
|
+
def shortcuts
|
5
|
+
config :shortcut
|
6
|
+
end
|
7
|
+
|
8
|
+
def selfed
|
9
|
+
config :selfed
|
10
|
+
end
|
11
|
+
|
12
|
+
def recursive
|
13
|
+
config.each_with_object({}) do |(key, value), hash|
|
14
|
+
hash[
|
15
|
+
value[:selfed].to_s.pluralize.to_sym
|
16
|
+
] = value[:recursive]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def opposite direction
|
21
|
+
find { |d| d != direction }
|
22
|
+
end
|
23
|
+
|
24
|
+
def opposite_shortcuts
|
25
|
+
shortcuts.each_with_object({}) do |(key, value), hash|
|
26
|
+
hash[key] = shortcuts.values.find do |v|
|
27
|
+
v != value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def config section = nil
|
35
|
+
if section
|
36
|
+
config.each_with_object({}) do |(key, value), hash|
|
37
|
+
hash[key] = value[section]
|
38
|
+
end
|
39
|
+
else
|
40
|
+
Config.association_names[:directions] # TODO: DRY
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
module_function
|
48
|
+
|
49
|
+
def association_names
|
50
|
+
Engine.config.names
|
51
|
+
end
|
52
|
+
|
53
|
+
def association_directions
|
54
|
+
@association_directions ||=
|
55
|
+
association_names[:directions].keys.tap do |directions|
|
56
|
+
class << directions
|
57
|
+
include Naming
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,5 +1,20 @@
|
|
1
1
|
module RailsDynamicAssociations
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
isolate_namespace RailsDynamicAssociations
|
4
|
+
|
5
|
+
config.names = {
|
6
|
+
directions: {
|
7
|
+
source: {
|
8
|
+
shortcut: :of,
|
9
|
+
selfed: :child,
|
10
|
+
recursive: :descendants,
|
11
|
+
},
|
12
|
+
target: {
|
13
|
+
shortcut: :to,
|
14
|
+
selfed: :parent,
|
15
|
+
recursive: :ancestors,
|
16
|
+
},
|
17
|
+
},
|
18
|
+
}
|
4
19
|
end
|
5
20
|
end
|
@@ -4,32 +4,6 @@ require 'rails_dynamic_associations/engine'
|
|
4
4
|
require 'core_ext/string'
|
5
5
|
|
6
6
|
module RailsDynamicAssociations
|
7
|
-
|
8
|
-
|
9
|
-
:self_referential_recursive
|
10
|
-
|
11
|
-
self.directions = {
|
12
|
-
source: :of,
|
13
|
-
target: :to,
|
14
|
-
}
|
15
|
-
|
16
|
-
self.self_referential = {
|
17
|
-
source: :child,
|
18
|
-
target: :parent,
|
19
|
-
}
|
20
|
-
|
21
|
-
self.self_referential_recursive = {
|
22
|
-
parents: :ancestors,
|
23
|
-
children: :descendants,
|
24
|
-
}
|
25
|
-
|
26
|
-
def self.opposite_directions
|
27
|
-
directions.each_with_object({}) do |(key, value), hash|
|
28
|
-
hash[key] = directions.values.find do |v|
|
29
|
-
v != value
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
7
|
+
autoload :Config, 'rails_dynamic_associations/config'
|
8
|
+
autoload :ActiveRecord, 'rails_dynamic_associations/active_record'
|
33
9
|
end
|
34
|
-
|
35
|
-
require 'rails_dynamic_associations/active_record'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_dynamic_associations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1.real
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Senko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- Rakefile
|
66
66
|
- app/models/relation.rb
|
67
67
|
- app/models/role.rb
|
68
|
+
- config/initializers/helpers.rb
|
68
69
|
- config/initializers/model_associations.rb
|
69
70
|
- db/migrate/01_create_roles.rb
|
70
71
|
- db/migrate/02_create_relations.rb
|
@@ -73,6 +74,7 @@ files:
|
|
73
74
|
- lib/rails_dynamic_associations/active_record.rb
|
74
75
|
- lib/rails_dynamic_associations/active_record/associations.rb
|
75
76
|
- lib/rails_dynamic_associations/active_record/relations.rb
|
77
|
+
- lib/rails_dynamic_associations/config.rb
|
76
78
|
- lib/rails_dynamic_associations/engine.rb
|
77
79
|
- lib/rails_dynamic_associations/version.rb
|
78
80
|
- lib/tasks/rails_dynamic_associations_tasks.rake
|