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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43cb344fd8b7ef4cd4f603260a48ad84c2fd3010
4
- data.tar.gz: 121001d4ff410084fe0e1306735a8f78bf40e931
3
+ metadata.gz: 2e750c6c7f3dff5b63c11d91a5636cb7aeffc31b
4
+ data.tar.gz: c8f63f3d0831bbe148c3ebde7563f36548d078e5
5
5
  SHA512:
6
- metadata.gz: ce004cfa30905fbfd731e1be5e084ccb10bbadf1018966d4a45f3cda25fd93258f1b330c70ffad9871ae3cb768b6a88358c8b0caa696fbd835adc786782c4270
7
- data.tar.gz: 46953fe6aafe0cc8b6e7b3d15890d54f786f310f27ad4bf9b8d9aefac2947c0c6475406724d68a30ea57baa00ae3cae99b2ad7c6bb62e3616f6fcc1164b93e52
6
+ metadata.gz: ed9168cf7e38072e7971ecf38a9550d5a0c30ad27cd2a998e943d8cc7c7211243c94bfd7b475a326e0c1466debe3352b8ed0832de66defb86d57a027adcecd22
7
+ data.tar.gz: 6fb4f259c4915c9fe4c6edb2223e8ebe8376315c937c31e3f2ffb7a95fcc8afd04ff79382c72efa923d75f5035a71377020c92dc20d642f9efd57bb967b8d135
@@ -5,14 +5,18 @@ class Relation < ActiveRecord::Base
5
5
 
6
6
  delegate :name, to: :role, allow_nil: true
7
7
 
8
- RailsDynamicAssociations.directions.each &-> (association, method) do
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 method, object
15
+ send("#{method}_abstract").
16
+ send method, object
12
17
  else
13
- all
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
- def self.seed source, target, roles = nil
41
- (roles.present? ? by_roles(roles) : [ self ]).map do |scope|
42
- scope.create source_type: source,
43
- target_type: target
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.by_roles *names
48
- Role.find_or_create_named(*names).
49
- map &:relations
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
- includes(:relations).
12
- where relations: { id: Relation.of_abstract } # TODO: simplify
11
+ with_relations { of_abstract }
13
12
  }
14
13
 
15
14
  scope :in, -> (object) {
16
- where relations: { id: Relation.to(object) } # TODO: simplify
15
+ with_relations { to object }
17
16
  }
18
17
 
19
18
  scope :for, -> (subject) {
20
- where relations: { id: Relation.of(subject) } # TODO: simplify
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)).all +
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
@@ -0,0 +1,5 @@
1
+ ActiveSupport.on_load :active_record do
2
+ include RailsDynamicAssociations::Config
3
+ include RailsDynamicAssociations::ActiveRecord::Associations
4
+ include RailsDynamicAssociations::ActiveRecord::Relations
5
+ end
@@ -1,5 +1,5 @@
1
1
  ActiveSupport.on_load :model_class do
2
- for type in RailsDynamicAssociations.directions.keys do
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
- name: 'index_relations_on_source_and_target_and_role'
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 RailsDynamicAssociations.self_referential_recursive do
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
- as: (RailsDynamicAssociations.directions.keys - [type]).first, class_name: 'Relation'
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
- through: define_relations_association(type, target, role),
34
- source: type,
35
- source_type: target.base_class.name,
36
- class_name: target.name
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
- if target == self || target <= User then
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
- if target == self then
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.opposite_directions.each &-> (association, method) do
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
- require 'rails_dynamic_associations/active_record/associations'
2
- require 'rails_dynamic_associations/active_record/relations'
3
-
4
- ActiveSupport.on_load :active_record do
5
- include RailsDynamicAssociations::ActiveRecord::Associations
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
@@ -1,3 +1,3 @@
1
1
  module RailsDynamicAssociations
2
- VERSION = '0.2.0.real'
2
+ VERSION = '0.2.1.real'
3
3
  end
@@ -4,32 +4,6 @@ require 'rails_dynamic_associations/engine'
4
4
  require 'core_ext/string'
5
5
 
6
6
  module RailsDynamicAssociations
7
- mattr_accessor :directions,
8
- :self_referential,
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.0.real
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-05-04 00:00:00.000000000 Z
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