adjustable_schema 0.5.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +42 -6
- data/app/models/adjustable_schema/relationship/role.rb +11 -10
- data/app/models/adjustable_schema/relationship.rb +10 -0
- data/app/models/concerns/adjustable_schema/active_record/associations.rb +12 -9
- data/app/models/concerns/adjustable_schema/active_record/relationships.rb +31 -47
- data/config/initializers/model_names.rb +4 -0
- data/lib/adjustable_schema/active_record/association/naming.rb +40 -35
- data/lib/adjustable_schema/active_record/association/scopes.rb +52 -0
- data/lib/adjustable_schema/active_record/association.rb +43 -4
- data/lib/adjustable_schema/active_record/query_methods.rb +103 -0
- data/lib/adjustable_schema/config.rb +24 -19
- data/lib/adjustable_schema/engine.rb +6 -6
- data/lib/adjustable_schema/version.rb +1 -1
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f905a89a0f7ab6c7c5c1f1feaa92a8fdb90f5e58003b164fed5ac55096fa258
|
4
|
+
data.tar.gz: cf1c34dd8cce3497e64635ab1c59f079e257961979e55e9d31b30ae1aefeb4ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 892d3cc2cdfa8d6cd7d2405289e1417a54be8c8767fefa557208a2d7880d346e4c362adc1a5dc911d2091be1b172a15f88e3060c0c06b29b135a61efb773f5bb
|
7
|
+
data.tar.gz: 145e68868903178eb0a6a82ed224912f657cdb7004f91df9cf70bec4c9c32241b1cd216515ee913369e6914f865007214d1a7aad5b5604664b73408974f5a323
|
data/README.md
CHANGED
@@ -56,10 +56,12 @@ book.editor_people
|
|
56
56
|
|
57
57
|
#### Special cases
|
58
58
|
|
59
|
-
|
59
|
+
##### "Actor-like" models
|
60
|
+
|
61
|
+
In case you have set up relationships with `User` model you'll get a slightly different naming:
|
60
62
|
|
61
63
|
``` ruby
|
62
|
-
AdjustableSchema::Relationship.seed! User => Book, %w[author editor]
|
64
|
+
AdjustableSchema::Relationship.seed! User => Book, roles: %w[author editor]
|
63
65
|
```
|
64
66
|
|
65
67
|
``` ruby
|
@@ -71,11 +73,42 @@ book.editors
|
|
71
73
|
The list of models to be handled this way can be set with `actor_model_names` configuration parameter.
|
72
74
|
It includes `User` by default.
|
73
75
|
|
74
|
-
|
76
|
+
##### Self-referencing models
|
77
|
+
|
78
|
+
You may want to set up self-targeted relationships:
|
79
|
+
|
80
|
+
``` ruby
|
81
|
+
AdjustableSchema::Relationship.seed! Person, roles: %w[friend]
|
82
|
+
```
|
83
|
+
|
84
|
+
In this case you'll get these associations:
|
85
|
+
|
86
|
+
``` ruby
|
87
|
+
person.parents
|
88
|
+
person.children # for all the children
|
89
|
+
person.people # for "roleless" children, not friends
|
90
|
+
person.friends
|
91
|
+
person.friended_people
|
92
|
+
```
|
93
|
+
|
94
|
+
If you prefer a different naming over `parents` & `children`, you can configure it like this:
|
75
95
|
|
76
|
-
|
96
|
+
```ruby
|
97
|
+
AdjustableSchema::Engine.configure do
|
98
|
+
config.names[:associations][:source][:self] = :effect
|
99
|
+
config.names[:associations][:target][:self] = :cause
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
Thus, for the self-referenced `Event`s, you'll get:
|
104
|
+
|
105
|
+
``` ruby
|
106
|
+
event.causes
|
107
|
+
event.effects
|
108
|
+
```
|
77
109
|
|
78
110
|
## Installation
|
111
|
+
|
79
112
|
Add this line to your application's Gemfile:
|
80
113
|
|
81
114
|
```ruby
|
@@ -83,13 +116,15 @@ gem "adjustable_schema"
|
|
83
116
|
```
|
84
117
|
|
85
118
|
And then execute:
|
119
|
+
|
86
120
|
```bash
|
87
|
-
|
121
|
+
bundle
|
88
122
|
```
|
89
123
|
|
90
124
|
Or install it yourself as:
|
125
|
+
|
91
126
|
```bash
|
92
|
-
|
127
|
+
gem install adjustable_schema
|
93
128
|
```
|
94
129
|
|
95
130
|
## Contributing
|
@@ -101,4 +136,5 @@ $ gem install adjustable_schema
|
|
101
136
|
5. Create new Pull Request
|
102
137
|
|
103
138
|
## License
|
139
|
+
|
104
140
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -1,16 +1,15 @@
|
|
1
1
|
module AdjustableSchema
|
2
2
|
class Relationship
|
3
3
|
class Role < ApplicationRecord
|
4
|
-
include Organizer::Identifiable.by :name
|
4
|
+
include Organizer::Identifiable.by :name, symbolized: true
|
5
5
|
|
6
6
|
has_many :relationships
|
7
7
|
|
8
8
|
validates :name, presence: true, uniqueness: true
|
9
9
|
|
10
|
-
|
11
|
-
scope :
|
12
|
-
scope :
|
13
|
-
scope :for, -> target { with_relationships { to target } }
|
10
|
+
scope :available, -> { with_relationships { send Config.shortcuts[:source], :abstract } }
|
11
|
+
scope :of, -> source { with_relationships { send Config.shortcuts[:source], source } }
|
12
|
+
scope :for, -> target { with_relationships { send Config.shortcuts[:target], target } }
|
14
13
|
|
15
14
|
def self.with_relationships(&)
|
16
15
|
joins(:relationships)
|
@@ -19,11 +18,13 @@ module AdjustableSchema
|
|
19
18
|
|
20
19
|
class << self
|
21
20
|
def [] *names, **scopes
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
if scopes.any?
|
22
|
+
with_relationships { self[**scopes] }
|
23
|
+
.distinct
|
24
|
+
else
|
25
|
+
all
|
26
|
+
end
|
27
|
+
.scoping { names.any? ? super(*names) : all }
|
27
28
|
end
|
28
29
|
end
|
29
30
|
end
|
@@ -72,6 +72,16 @@ module AdjustableSchema
|
|
72
72
|
end
|
73
73
|
|
74
74
|
class << self
|
75
|
+
def [] **scopes
|
76
|
+
scopes
|
77
|
+
.map do
|
78
|
+
self
|
79
|
+
.send(Config.shortcuts[:source], _1)
|
80
|
+
.send(Config.shortcuts[:target], _2)
|
81
|
+
end
|
82
|
+
.reduce &:or
|
83
|
+
end
|
84
|
+
|
75
85
|
def seed! *models, roles: [], **_models
|
76
86
|
return seed!({ **Hash[*models], **_models }, roles:) if _models.any? # support keyword arguments syntax
|
77
87
|
|
@@ -5,20 +5,23 @@ module AdjustableSchema
|
|
5
5
|
private
|
6
6
|
|
7
7
|
def adjust_associations
|
8
|
-
relationships
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
relationships
|
9
|
+
.flat_map do |direction, relationships|
|
10
|
+
relationships
|
11
|
+
.select(&:"#{direction}_type")
|
12
|
+
.each do |relationship|
|
13
|
+
setup_association direction, relationship.send("#{direction}_type").constantize, relationship.role
|
14
|
+
end
|
15
|
+
end
|
16
|
+
.presence
|
17
|
+
&.tap do # finally, if any relationships have been set up
|
18
|
+
include Relationships::InstanceMethods
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
22
|
def setup_association direction, target = self, role = nil
|
18
23
|
adjustable_association(direction, target ).define
|
19
24
|
adjustable_association(direction, target, role).define if role
|
20
|
-
|
21
|
-
include Relationships::InstanceMethods
|
22
25
|
end
|
23
26
|
|
24
27
|
def adjustable_association(...)
|
@@ -1,12 +1,15 @@
|
|
1
|
+
require 'memery'
|
2
|
+
|
1
3
|
module AdjustableSchema
|
2
4
|
module ActiveRecord
|
3
5
|
concern :Relationships do
|
4
6
|
class_methods do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
include Memery
|
8
|
+
|
9
|
+
memoize def relationships
|
10
|
+
Config.association_directions.to_h do
|
11
|
+
[ _1, Relationship.abstract.send(Config.shortcuts.opposite[_1], self) ]
|
12
|
+
end
|
10
13
|
end
|
11
14
|
|
12
15
|
def roles(&) = Role.of self, &
|
@@ -14,76 +17,57 @@ module AdjustableSchema
|
|
14
17
|
private
|
15
18
|
|
16
19
|
def define_recursive_methods association_name, method
|
17
|
-
|
20
|
+
define_method method do
|
18
21
|
send(association_name)
|
19
|
-
.
|
20
|
-
tree << node << node.send(tree_method)
|
21
|
-
end
|
22
|
-
.reject &:blank?
|
23
|
-
end
|
24
|
-
|
25
|
-
redefine_method "#{method}_with_distance" do
|
26
|
-
(with_distance = -> (level, distance) {
|
27
|
-
case level
|
28
|
-
when Array
|
29
|
-
level.inject({}) do |hash, node|
|
30
|
-
hash.merge with_distance[node, distance.next]
|
31
|
-
end
|
32
|
-
else
|
33
|
-
{ level => distance }
|
34
|
-
end
|
35
|
-
})[send(tree_method), 0]
|
36
|
-
end
|
37
|
-
|
38
|
-
redefine_method method do
|
39
|
-
send(tree_method).flatten
|
22
|
+
.recursive
|
40
23
|
end
|
41
24
|
end
|
42
25
|
end
|
43
26
|
|
44
27
|
concern :InstanceMethods do # to include when needed
|
45
28
|
included do
|
29
|
+
scope :roleless, -> { merge Relationship.nameless }
|
30
|
+
|
46
31
|
Config.association_directions.recursive
|
47
32
|
.select { reflect_on_association _1 }
|
48
33
|
.reject { method_defined? _2 }
|
49
|
-
.each
|
34
|
+
.each { define_recursive_methods _1, _2 }
|
50
35
|
end
|
51
36
|
|
52
37
|
def related?(...)
|
53
38
|
relationships(...)
|
54
|
-
.values
|
55
|
-
.reduce(&:or)
|
56
39
|
.any?
|
57
40
|
end
|
58
41
|
|
59
42
|
def related(...)
|
60
43
|
relationships(...)
|
61
|
-
.
|
62
|
-
|
63
|
-
|
64
|
-
.map
|
44
|
+
.preload(Config.association_directions)
|
45
|
+
.map do |relationship|
|
46
|
+
Config.association_directions
|
47
|
+
.map { relationship.send _1 } # both objects
|
48
|
+
.without(self) # the related one
|
49
|
+
.first or self # may be self-related
|
65
50
|
end
|
66
51
|
.uniq
|
67
52
|
end
|
68
53
|
|
69
|
-
def relationships
|
70
|
-
if (direction, scope = Config.find_direction options)
|
71
|
-
|
72
|
-
direction
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
54
|
+
def relationships **options
|
55
|
+
if (direction, scope = Config.find_direction **options) # filter by direction & related objects
|
56
|
+
relationships_to(direction)
|
57
|
+
.send Config.shortcuts[direction], scope
|
58
|
+
else # all in both directions
|
59
|
+
Config.association_directions
|
60
|
+
.map { relationships_to _1 }
|
61
|
+
.reduce(&:or)
|
77
62
|
end
|
78
|
-
.compact
|
79
|
-
.tap do |relationships|
|
80
|
-
break relationships.transform_values { _1.named names } if names.any? # filter by role
|
81
|
-
end
|
82
63
|
end
|
83
64
|
|
84
65
|
private
|
85
66
|
|
86
|
-
def relationships_to
|
67
|
+
def relationships_to direction
|
68
|
+
try "#{direction}_relationships" or
|
69
|
+
Relationship.none
|
70
|
+
end
|
87
71
|
end
|
88
72
|
end
|
89
73
|
end
|
@@ -1,62 +1,67 @@
|
|
1
|
+
require 'memery'
|
2
|
+
|
1
3
|
module AdjustableSchema
|
2
4
|
module ActiveRecord
|
3
5
|
class Association
|
4
6
|
concerning :Naming do
|
7
|
+
include Memery
|
8
|
+
|
5
9
|
module Inflections
|
6
10
|
refine String do
|
7
11
|
def passivize
|
8
|
-
|
12
|
+
self
|
13
|
+
.sub(/(author)$/, '\\2ed')
|
14
|
+
.sub(/(e*|ed|ing|[eo]r|ant|(t)ion)$/, '\\2ed')
|
9
15
|
end
|
10
16
|
end
|
11
17
|
end
|
12
18
|
|
13
19
|
using Inflections
|
14
20
|
|
15
|
-
def name
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
.to_sym
|
21
|
+
memoize def name
|
22
|
+
(role ? name_with_role : name_without_role)
|
23
|
+
.to_s
|
24
|
+
.tableize
|
25
|
+
.to_sym
|
21
26
|
end
|
22
27
|
|
23
|
-
def target_name
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
.underscore
|
28
|
+
memoize def target_name
|
29
|
+
target.model_name.unnamespaced
|
30
|
+
.split('::')
|
31
|
+
.reverse
|
32
|
+
.join
|
33
|
+
.underscore
|
30
34
|
end
|
31
35
|
|
32
36
|
def relationships_name = :"#{role ? name_with_role : direction}_relationships"
|
33
37
|
|
34
38
|
private
|
35
39
|
|
36
|
-
def name_with_role
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
40
|
+
memoize def name_with_role
|
41
|
+
if loop?
|
42
|
+
{
|
43
|
+
source: role.name,
|
44
|
+
target: "#{role.name.passivize}_#{target_name}",
|
45
|
+
}[direction]
|
46
|
+
else
|
47
|
+
"#{{
|
48
|
+
source: role.name,
|
49
|
+
target: role.name.passivize,
|
50
|
+
}[direction]}_#{target_name}"
|
51
|
+
end
|
49
52
|
end
|
50
53
|
|
51
|
-
def name_without_role
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
54
|
+
memoize def name_without_role
|
55
|
+
if loop?
|
56
|
+
Config.association_directions
|
57
|
+
.self[direction]
|
58
|
+
else
|
59
|
+
target_name
|
60
|
+
end
|
59
61
|
end
|
62
|
+
|
63
|
+
def name_for_any = :"#{name.to_s.singularize.passivize}"
|
64
|
+
def name_for_none = :"#{name.to_s.singularize}less"
|
60
65
|
end
|
61
66
|
end
|
62
67
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module AdjustableSchema
|
2
|
+
module ActiveRecord
|
3
|
+
module Association::Scopes
|
4
|
+
concern :Recursive do
|
5
|
+
require_relative '../query_methods'
|
6
|
+
|
7
|
+
included do
|
8
|
+
::ActiveRecord::QueryMethods.prepend QueryMethods # HACK: to bring `with.recursive` in
|
9
|
+
end
|
10
|
+
|
11
|
+
def recursive
|
12
|
+
all._exec_scope do
|
13
|
+
all
|
14
|
+
.select(
|
15
|
+
select_values = self.select_values.presence || arel_table[Arel.star],
|
16
|
+
Arel.sql('1').as('distance'),
|
17
|
+
)
|
18
|
+
.with.recursive(recursive_table.name => unscoped
|
19
|
+
.select(
|
20
|
+
select_values,
|
21
|
+
(recursive_table[:distance] + 1).as('distance'),
|
22
|
+
)
|
23
|
+
.joins(inverse_association_name)
|
24
|
+
.arel
|
25
|
+
.join(recursive_table)
|
26
|
+
.on(recursive_table[primary_key].eq inverse_table[primary_key])
|
27
|
+
)
|
28
|
+
.unscope(:select, :joins, :where)
|
29
|
+
.from(recursive_table.alias table_name)
|
30
|
+
.distinct
|
31
|
+
.unscope(:order) # for SELECT DISTINCT, ORDER BY expressions must appear in select list
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def association_name = @association.reflection.name
|
38
|
+
|
39
|
+
def inverse_association_name
|
40
|
+
Config.association_directions
|
41
|
+
.recursive
|
42
|
+
.keys
|
43
|
+
.without(association_name)
|
44
|
+
.sole
|
45
|
+
end
|
46
|
+
|
47
|
+
def recursive_table = Arel::Table.new [ :recursive, association_name, klass.table_name ] * '_'
|
48
|
+
def inverse_table = Arel::Table.new [ inverse_association_name, klass.table_name ] * '_' # HACK: depends on ActiveRecord internals
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -2,31 +2,70 @@ module AdjustableSchema
|
|
2
2
|
module ActiveRecord
|
3
3
|
class Association < Struct.new(:owner, :direction, :target, :role)
|
4
4
|
require_relative 'association/naming'
|
5
|
+
require_relative 'association/scopes'
|
5
6
|
|
6
7
|
def define
|
7
8
|
name.tap do |association_name|
|
8
|
-
|
9
|
+
association = self # save context
|
10
|
+
|
11
|
+
has_many association_name, **(options = {
|
9
12
|
through: define_relationships,
|
10
13
|
source: direction,
|
11
14
|
source_type: target.base_class.name,
|
12
15
|
class_name: target.name
|
16
|
+
}) do
|
17
|
+
include Scopes
|
18
|
+
include Scopes::Recursive if association.loop?
|
19
|
+
end
|
20
|
+
|
21
|
+
define_scopes
|
22
|
+
define_methods
|
23
|
+
|
24
|
+
unless role
|
25
|
+
has_many target_name.tableize.to_sym, -> { roleless }, **options if
|
26
|
+
loop?
|
13
27
|
|
14
|
-
|
28
|
+
define_role_methods
|
29
|
+
end
|
15
30
|
end
|
16
31
|
end
|
17
32
|
|
18
|
-
def
|
33
|
+
def loop? = target == owner
|
19
34
|
|
20
35
|
private
|
21
36
|
|
22
37
|
def define_relationships
|
23
38
|
relationships_name.tap do |association_name|
|
24
|
-
has_many association_name, role && -> { where role: },
|
39
|
+
has_many association_name, (role = self.role) && -> { where role: },
|
25
40
|
as: Config.association_directions.opposite(to: direction),
|
41
|
+
dependent: :destroy_async,
|
26
42
|
class_name: 'AdjustableSchema::Relationship'
|
27
43
|
end
|
28
44
|
end
|
29
45
|
|
46
|
+
def define_scopes
|
47
|
+
name = relationships_name
|
48
|
+
|
49
|
+
{
|
50
|
+
name_for_any => -> { where.associated name },
|
51
|
+
name_for_none => -> { where.missing name },
|
52
|
+
}
|
53
|
+
.reject { owner.singleton_class.method_defined? _1 }
|
54
|
+
.each { owner.scope _1, _2 }
|
55
|
+
end
|
56
|
+
|
57
|
+
def define_methods
|
58
|
+
name = self.name
|
59
|
+
|
60
|
+
{
|
61
|
+
name_for_any => -> { send(name).any? },
|
62
|
+
name_for_none => -> { send(name).none? },
|
63
|
+
}
|
64
|
+
.transform_keys {"#{_1}?" }
|
65
|
+
.reject { owner.method_defined? _1 }
|
66
|
+
.each { owner.define_method _1, &_2 }
|
67
|
+
end
|
68
|
+
|
30
69
|
def define_role_methods
|
31
70
|
name = self.name
|
32
71
|
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module AdjustableSchema
|
2
|
+
module ActiveRecord
|
3
|
+
module QueryMethods
|
4
|
+
class WithChain
|
5
|
+
def initialize scope
|
6
|
+
@scope = scope
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns a new relation expressing WITH RECURSIVE statement.
|
10
|
+
#
|
11
|
+
# #recursive accepts conditions as a hash. See QueryMethods#with for
|
12
|
+
# more details on each format.
|
13
|
+
#
|
14
|
+
# User.with.recursive(
|
15
|
+
# descendants: User.joins('INNER JOIN descendants ON users.parent_id = descendants.id')
|
16
|
+
# )
|
17
|
+
# # WITH RECURSIVE descendants AS (
|
18
|
+
# # SELECT * FROM users
|
19
|
+
# # UNION
|
20
|
+
# # SELECT * FROM users INNER JOIN descendants ON users.parent_id = descendants.id
|
21
|
+
# # ) SELECT * FROM descendants
|
22
|
+
#
|
23
|
+
# WARNING! Due to how Arel works,
|
24
|
+
# * `recursive` can't be chained with any prior non-recursive calls to `with` and
|
25
|
+
# * all subsequent non-recursive calls to `with` will be treated as recursive ones.
|
26
|
+
def recursive *args
|
27
|
+
args.map! do
|
28
|
+
next _1 unless _1.is_a? Hash
|
29
|
+
|
30
|
+
_1
|
31
|
+
.map(&method(:with_recursive_union))
|
32
|
+
.to_h
|
33
|
+
end
|
34
|
+
|
35
|
+
case @scope.with_values
|
36
|
+
in []
|
37
|
+
@scope = @scope.with :recursive, *args
|
38
|
+
in [ :recursive, * ]
|
39
|
+
@scope = @scope.with *args
|
40
|
+
else
|
41
|
+
raise ArgumentError, "can't chain `WITH RECURSIVE` with non-recursive one"
|
42
|
+
end
|
43
|
+
|
44
|
+
@scope
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def with_recursive_union name, scope
|
50
|
+
scope = scope.arel if scope.respond_to? :arel
|
51
|
+
|
52
|
+
[
|
53
|
+
name,
|
54
|
+
@scope
|
55
|
+
.unscope(:order, :group, :having)
|
56
|
+
.arel
|
57
|
+
.union(scope)
|
58
|
+
]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def with *args
|
63
|
+
if args.empty?
|
64
|
+
WithChain.new spawn
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# OVERWRITE: allow a Symbol to be passes as the first argument
|
73
|
+
def build_with(arel)
|
74
|
+
return if with_values.empty?
|
75
|
+
|
76
|
+
with_statements = with_values.map.with_index do |with_value, i|
|
77
|
+
next with_value if with_value.is_a? Symbol and i == 0
|
78
|
+
|
79
|
+
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
80
|
+
|
81
|
+
build_with_value_from_hash(with_value)
|
82
|
+
end
|
83
|
+
|
84
|
+
arel.with(*with_statements)
|
85
|
+
end
|
86
|
+
|
87
|
+
# OVERWRITE: allow Arel Nodes
|
88
|
+
def build_with_value_from_hash(hash)
|
89
|
+
hash.map do |name, value|
|
90
|
+
expression =
|
91
|
+
case value
|
92
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
93
|
+
when ::ActiveRecord::Relation then value.arel
|
94
|
+
when Arel::SelectManager, Arel::Nodes::Node then value
|
95
|
+
else
|
96
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
97
|
+
end
|
98
|
+
Arel::Nodes::TableAlias.new(expression, name)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -1,24 +1,29 @@
|
|
1
|
+
require 'memery'
|
2
|
+
|
1
3
|
module AdjustableSchema
|
2
4
|
module Config
|
5
|
+
include Memery
|
6
|
+
|
3
7
|
module Naming
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
8
|
+
include Memery
|
9
|
+
|
10
|
+
memoize def shortcuts
|
11
|
+
config(:shortcut).tap do |shortcuts|
|
12
|
+
def shortcuts.opposite to: nil
|
13
|
+
if to
|
14
|
+
values.reject { _1 == to }.sole
|
15
|
+
else
|
16
|
+
transform_values { opposite to: _1 }
|
14
17
|
end
|
18
|
+
end
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
|
-
def
|
22
|
+
def self = config :self
|
18
23
|
|
19
24
|
def recursive
|
20
25
|
config.values.to_h do
|
21
|
-
[ _1[:
|
26
|
+
[ _1[:self].to_s.pluralize.to_sym, _1[:recursive].to_sym ]
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
@@ -30,7 +35,7 @@ module AdjustableSchema
|
|
30
35
|
|
31
36
|
def config section = nil
|
32
37
|
if section
|
33
|
-
config.transform_values { _1[section] }
|
38
|
+
config.transform_values { _1[section].to_sym }
|
34
39
|
else
|
35
40
|
Config.association_names # TODO: DRY
|
36
41
|
end
|
@@ -39,13 +44,12 @@ module AdjustableSchema
|
|
39
44
|
|
40
45
|
module_function
|
41
46
|
|
42
|
-
def association_directions
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
include Naming
|
47
|
-
end
|
47
|
+
memoize def association_directions
|
48
|
+
association_names.keys.tap do |directions|
|
49
|
+
class << directions
|
50
|
+
include Naming
|
48
51
|
end
|
52
|
+
end
|
49
53
|
end
|
50
54
|
|
51
55
|
def find_direction(...)
|
@@ -67,6 +71,7 @@ module AdjustableSchema
|
|
67
71
|
|
68
72
|
def normalize **options
|
69
73
|
shortcuts
|
74
|
+
.tap { options.assert_valid_keys _1.keys, _1.values }
|
70
75
|
.select { _2.in? options }
|
71
76
|
.each { options[_1] = options.delete _2 }
|
72
77
|
|
@@ -7,14 +7,14 @@ module AdjustableSchema
|
|
7
7
|
config.names = {
|
8
8
|
associations: {
|
9
9
|
source: {
|
10
|
-
shortcut:
|
11
|
-
|
12
|
-
recursive:
|
10
|
+
shortcut: :of,
|
11
|
+
self: :child,
|
12
|
+
recursive: :descendants,
|
13
13
|
},
|
14
14
|
target: {
|
15
|
-
shortcut:
|
16
|
-
|
17
|
-
recursive:
|
15
|
+
shortcut: :to,
|
16
|
+
self: :parent,
|
17
|
+
recursive: :ancestors,
|
18
18
|
},
|
19
19
|
},
|
20
20
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adjustable_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Senko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -40,6 +40,20 @@ dependencies:
|
|
40
40
|
version: '0.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: organizer-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: memery
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - ">="
|
@@ -77,12 +91,15 @@ files:
|
|
77
91
|
- app/models/concerns/adjustable_schema/active_record/relationships.rb
|
78
92
|
- app/views/layouts/adjustable_schema/application.html.erb
|
79
93
|
- config/initializers/associations.rb
|
94
|
+
- config/initializers/model_names.rb
|
80
95
|
- config/routes.rb
|
81
96
|
- db/migrate/01_create_adjustable_schema_relationship_tables.rb
|
82
97
|
- lib/adjustable_schema.rb
|
83
98
|
- lib/adjustable_schema/active_record.rb
|
84
99
|
- lib/adjustable_schema/active_record/association.rb
|
85
100
|
- lib/adjustable_schema/active_record/association/naming.rb
|
101
|
+
- lib/adjustable_schema/active_record/association/scopes.rb
|
102
|
+
- lib/adjustable_schema/active_record/query_methods.rb
|
86
103
|
- lib/adjustable_schema/authors.rb
|
87
104
|
- lib/adjustable_schema/config.rb
|
88
105
|
- lib/adjustable_schema/engine.rb
|
@@ -94,7 +111,7 @@ licenses:
|
|
94
111
|
metadata:
|
95
112
|
homepage_uri: https://github.com/Alexander-Senko/adjustable_schema
|
96
113
|
source_code_uri: https://github.com/Alexander-Senko/adjustable_schema
|
97
|
-
changelog_uri: https://github.com/Alexander-Senko/adjustable_schema/blob/v0.
|
114
|
+
changelog_uri: https://github.com/Alexander-Senko/adjustable_schema/blob/v0.7.0/CHANGELOG.md
|
98
115
|
post_install_message:
|
99
116
|
rdoc_options: []
|
100
117
|
require_paths:
|