rails_dynamic_associations 0.2.0.real → 0.2.1.real

Sign up to get free protection for your applications and to get access to all the features.
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