has_many_polymorphic 0.0.3
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.
- data/MIT-LICENSE.txt +20 -0
- data/README.rdoc +45 -0
- data/lib/has_many_polymorphic.rb +7 -0
- data/lib/has_many_polymorphic/autoload.rb +50 -0
- data/lib/has_many_polymorphic/has_many_polymorphic.rb +179 -0
- data/lib/has_many_polymorphic/version.rb +3 -0
- metadata +87 -0
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007-2011 Collective Idea
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
= HasManyPolymorphic
|
2
|
+
|
3
|
+
This mixin adds a has many polymorphic relationship to a model and creates all the relationships needed by rails to handle it.
|
4
|
+
|
5
|
+
== Params
|
6
|
+
- Name
|
7
|
+
- name of the relationship, there is a convention that whatever name you choose, the polymorphic table columns on your through table should match.
|
8
|
+
|
9
|
+
- Options
|
10
|
+
- through - the model that handles the through relationship
|
11
|
+
- models - models that should be included in this polymophic relationship
|
12
|
+
|
13
|
+
|
14
|
+
== Added methods
|
15
|
+
|
16
|
+
- {name param}
|
17
|
+
- the name of your relationship is used for the method name of this method. it will return an array of the models that are related via the has_many relationships
|
18
|
+
|
19
|
+
There is an after_save call back that will save the relationships when they are added or removed. If you want to remove a relationship the models need to be destroyed and this model reloaded.
|
20
|
+
|
21
|
+
== Example Usage
|
22
|
+
|
23
|
+
class PreferenceType < ActiveRecord::Base
|
24
|
+
has_many_polymorphic :preferenced_records,
|
25
|
+
:through => :valid_preference_types,
|
26
|
+
:models => [:desktops, :organizers]
|
27
|
+
end
|
28
|
+
|
29
|
+
this gives you the following
|
30
|
+
|
31
|
+
preferenceType = PreferenceType.first
|
32
|
+
preferenceType.desktops
|
33
|
+
preferenceType.organizers
|
34
|
+
|
35
|
+
and
|
36
|
+
|
37
|
+
preferenceType.preferenced_records
|
38
|
+
|
39
|
+
which is a concatentated array of the models. You also get the following
|
40
|
+
|
41
|
+
desktop = Desktop.first
|
42
|
+
desktop.preference_types
|
43
|
+
|
44
|
+
organizer = Organizer.first
|
45
|
+
organizer.preference_types
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'initializer' unless defined? ::Rails::Initializer
|
2
|
+
require 'action_controller/dispatcher' unless defined? ::ActionController::Dispatcher
|
3
|
+
|
4
|
+
module RussellEdge
|
5
|
+
module HasManyPolymorphic
|
6
|
+
|
7
|
+
=begin rdoc
|
8
|
+
Searches for models that use <tt>has_many_polymorphic</tt> and makes sure that they get loaded during app initialization.
|
9
|
+
This ensures that helper methods are injected into the target classes.
|
10
|
+
=end
|
11
|
+
|
12
|
+
#define the models that use has_many_polymorphic. has_many_polymorphs combed the file system for models
|
13
|
+
#that had the has_many_polymorphs method. This is not as robust but more efficent. It can be set via
|
14
|
+
#
|
15
|
+
#RussellEdge::HasManyPolymorphic::DEFAULT_OPTIONS = { :models => %w(PreferenceType AnotherModel) }
|
16
|
+
#
|
17
|
+
DEFAULT_OPTIONS = {
|
18
|
+
:models => %w()
|
19
|
+
}
|
20
|
+
|
21
|
+
mattr_accessor :options
|
22
|
+
@@options = HashWithIndifferentAccess.new(DEFAULT_OPTIONS)
|
23
|
+
|
24
|
+
# Dispatcher callback to load polymorphic relationships
|
25
|
+
def self.autoload
|
26
|
+
options[:models].each do |model|
|
27
|
+
#try to load model if it exists.
|
28
|
+
begin
|
29
|
+
model.constantize
|
30
|
+
rescue=>e
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Rails::Initializer #:nodoc:
|
39
|
+
# Make sure it gets loaded in the console, tests, and migrations
|
40
|
+
def after_initialize_with_autoload
|
41
|
+
after_initialize_without_autoload
|
42
|
+
RussellEdge::HasManyPolymorphic.autoload
|
43
|
+
end
|
44
|
+
alias_method_chain :after_initialize, :autoload
|
45
|
+
end
|
46
|
+
|
47
|
+
ActionController::Dispatcher.to_prepare(:morpheus_autoload) do
|
48
|
+
# Make sure it gets loaded in the app
|
49
|
+
RussellEdge::HasManyPolymorphic.autoload
|
50
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module RussellEdge #:nodoc:
|
2
|
+
module HasManyPolymorphic #:nodoc:
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
##
|
11
|
+
# HasManyPolymorphic
|
12
|
+
# This mixin adds a has many polymorphic relationship to a model and creates all the relationships needed
|
13
|
+
# by rails to handle it.
|
14
|
+
#
|
15
|
+
# Params
|
16
|
+
#
|
17
|
+
# Name - name of the relationship, there is a convention that whatever name you choose, the polymorphic
|
18
|
+
# table columns on your through table should match.
|
19
|
+
#
|
20
|
+
# Options
|
21
|
+
# - through - the model that handles the through relationship
|
22
|
+
# - models - models that should be included in this polymophic relationship
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# One method is added for you to use
|
26
|
+
#
|
27
|
+
# - {name param}
|
28
|
+
# - the name of your relationship is used for the method name of this method. it will return
|
29
|
+
# an array of the models that are related via the has_many relationships
|
30
|
+
#
|
31
|
+
# There is an after_save call back that will save the relationships when they are added or removed
|
32
|
+
# If you want to remove a relationship the models need to be destroyed and this model reloaded.
|
33
|
+
#
|
34
|
+
# Example Usage
|
35
|
+
#
|
36
|
+
# class PreferenceType < ActiveRecord::Base
|
37
|
+
# has_morpheus :preferenced_records,
|
38
|
+
# :through => :valid_preference_types,
|
39
|
+
# :models => [:desktops, :organizers]
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# this gives you the following
|
43
|
+
#
|
44
|
+
# preferenceType = PreferenceType.first
|
45
|
+
# preferenceType.desktops
|
46
|
+
# preferenceType.organizers
|
47
|
+
#
|
48
|
+
# and
|
49
|
+
#
|
50
|
+
# preferenceType.preferenced_records
|
51
|
+
#
|
52
|
+
# which is a concatentated array of the models. You also get the following
|
53
|
+
#
|
54
|
+
# desktop = Desktop.first
|
55
|
+
# desktop.preference_types
|
56
|
+
#
|
57
|
+
# organizer = Organizer.first
|
58
|
+
# organizer.preference_types
|
59
|
+
##
|
60
|
+
|
61
|
+
def has_many_polymorphic(name, options = {})
|
62
|
+
target_class_name = self.name
|
63
|
+
instance_array_name = "#{name}_array".to_sym
|
64
|
+
|
65
|
+
#declare array to related models
|
66
|
+
attr_accessor instance_array_name
|
67
|
+
|
68
|
+
#create the has_many relationship
|
69
|
+
has_many options[:through], :dependent => :destroy
|
70
|
+
|
71
|
+
#create the has_many relationship for each model
|
72
|
+
options[:models].each do |model|
|
73
|
+
has_many model, :through => options[:through], :source => model.to_s.singularize,
|
74
|
+
:conditions => ["#{options[:through]}.#{name.to_s.singularize}_type = ?", model.to_s.classify], :dependent => :destroy
|
75
|
+
end
|
76
|
+
|
77
|
+
#modify the through class to add the belongs to relationships
|
78
|
+
options[:through].to_s.classify.constantize.class_exec do
|
79
|
+
options[:models].each do |model|
|
80
|
+
belongs_to model.to_s.singularize.to_sym, :class_name => model.to_s.classify, :foreign_key => "#{name.to_s.singularize}_id"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#I want to keep the << and push methods of array so this helps to keep them.
|
85
|
+
define_method name do
|
86
|
+
#used the declared instance variable array
|
87
|
+
records = self.send(instance_array_name.to_s)
|
88
|
+
records = records || []
|
89
|
+
options[:models].each do |model|
|
90
|
+
records = records | self.send(model.to_s)
|
91
|
+
end
|
92
|
+
|
93
|
+
#set it back to the instance variable
|
94
|
+
self.send("#{instance_array_name.to_s}=", records)
|
95
|
+
|
96
|
+
records
|
97
|
+
end
|
98
|
+
|
99
|
+
#before we save this model make sure you save all the relationships.
|
100
|
+
before_save do |record|
|
101
|
+
record.send(name).each do |reln_record|
|
102
|
+
conditions = "#{name.to_s.singularize}_id = #{reln_record.id} and #{name.to_s.singularize}_type = '#{reln_record.class.name}'"
|
103
|
+
exisiting_record = record.send("#{options[:through]}").find(:first,
|
104
|
+
:conditions => conditions)
|
105
|
+
|
106
|
+
if exisiting_record.nil?
|
107
|
+
values_hash = {}
|
108
|
+
values_hash["#{record.class.name.underscore}_id"] = record.id
|
109
|
+
values_hash["#{name.to_s.singularize}_type"] = reln_record.class.name
|
110
|
+
values_hash["#{name.to_s.singularize}_id"] = reln_record.id
|
111
|
+
|
112
|
+
options[:through].to_s.classify.constantize.create(values_hash)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#include instance methods into this model
|
118
|
+
include RussellEdge::HasManyPolymorphic::InstanceMethods
|
119
|
+
|
120
|
+
#add the relationship to the models.
|
121
|
+
options[:models].each do |model|
|
122
|
+
model.to_s.classify.constantize.class_exec do
|
123
|
+
#build the has many polymorphic relationship via finder_sql
|
124
|
+
has_many target_class_name.underscore.pluralize.to_sym,
|
125
|
+
:class_name => "#{target_class_name}",
|
126
|
+
:finder_sql => 'SELECT DISTINCT target.* from '+target_class_name.tableize+' target join '+
|
127
|
+
options[:through].to_s+' join_table on join_table.'+target_class_name.underscore.singularize+'_id = target.id '+
|
128
|
+
'and join_table.'+name.to_s.singularize+'_type = \'#{model_class_name}\' and '+
|
129
|
+
'join_table.'+name.to_s.singularize+'_id = #{id}'
|
130
|
+
|
131
|
+
#we want to save the relantionships when the model is saved
|
132
|
+
before_save do |record|
|
133
|
+
record.send(target_class_name.underscore.pluralize).each do |reln_record|
|
134
|
+
|
135
|
+
db_result = ActiveRecord::Base.connection.select_all("SELECT count(*) as num_rows FROM #{options[:through]}
|
136
|
+
where #{name.to_s.singularize}_id = #{record.id}
|
137
|
+
and #{name.to_s.singularize}_type = '#{record.class.name}'
|
138
|
+
and #{target_class_name.underscore.singularize}_id = #{reln_record.id}")
|
139
|
+
|
140
|
+
#make sure that the relantionship does not already exist
|
141
|
+
num_rows = db_result[0]['num_rows'] unless db_result == -1
|
142
|
+
if num_rows.nil? || num_rows.to_i == 0
|
143
|
+
values_hash = {}
|
144
|
+
values_hash["#{reln_record.class.name.underscore}_id"] = reln_record.id
|
145
|
+
values_hash["#{name.to_s.singularize}_type"] = record.class.name
|
146
|
+
values_hash["#{name.to_s.singularize}_id"] = record.id
|
147
|
+
options[:through].to_s.classify.constantize.create(values_hash)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
model.to_s.classify.constantize.class_eval do
|
154
|
+
#check if this is using STI if so use the type attribute else use the class name
|
155
|
+
def model_class_name
|
156
|
+
if attributes['type']
|
157
|
+
attributes['type']
|
158
|
+
else
|
159
|
+
self.class.to_s
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
module InstanceMethods
|
169
|
+
#clear array on reload
|
170
|
+
def reload(*args)
|
171
|
+
@records = []
|
172
|
+
|
173
|
+
super args
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_many_polymorphic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Russell Holmes
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-26 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activerecord
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 9
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 3
|
33
|
+
- 5
|
34
|
+
version: 2.3.5
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: Simple replacement for has_many_polymorphs
|
38
|
+
email: rholmes@tnsolutionsinc.com
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- lib/has_many_polymorphic/autoload.rb
|
47
|
+
- lib/has_many_polymorphic/has_many_polymorphic.rb
|
48
|
+
- lib/has_many_polymorphic/version.rb
|
49
|
+
- lib/has_many_polymorphic.rb
|
50
|
+
- MIT-LICENSE.txt
|
51
|
+
- README.rdoc
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: https://github.com/russ1985/has_many_polymorphic
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 1.3.7
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: Simple replacement for has_many_polymorphs
|
86
|
+
test_files: []
|
87
|
+
|