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 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,7 @@
1
+ require 'has_many_polymorphic/has_many_polymorphic'
2
+ ActiveRecord::Base.send :include, RussellEdge::HasManyPolymorphic
3
+
4
+ #used to tie into Rails initialization load the models so the relationships are created
5
+ require 'has_many_polymorphic/autoload'
6
+
7
+
@@ -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
@@ -0,0 +1,3 @@
1
+ module HasManyPolymorphic
2
+ VERSION = '0.0.3' unless defined?(::HasManyPolymorphic::VERSION)
3
+ 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
+