forked from activeadmin/inherited_resources
/
polymorphic_helpers.rb
162 lines (148 loc) · 5.12 KB
/
polymorphic_helpers.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
module InheritedResources
# = polymorphic associations
#
# In some cases you have a resource that belongs to two different resources
# but not at the same time. For example, let's suppose you have File, Message
# and Task as resources and they are all commentable.
#
# Polymorphic associations allows you to create just one controller that will
# deal with each case.
#
# class Comment < InheritedResources::Base
# belongs_to :file, :message, :task, :polymorphic => true
# end
#
# Your routes should be something like:
#
# m.resources :files, :has_many => :comments #=> /files/13/comments
# m.resources :tasks, :has_many => :comments #=> /tasks/17/comments
# m.resources :messages, :has_many => :comments #=> /messages/11/comments
#
# When using polymorphic associations, you get some free helpers:
#
# parent? #=> true
# parent_type #=> :task
# parent_class #=> Task
# parent #=> @task
#
# This polymorphic controllers thing is a great idea by James Golick and he
# built it in resource_controller. Here is just a re-implementation.
#
# = optional polymorphic associations
#
# Let's take another break from ProjectsController. Let's suppose we are
# building a store, which sell products.
#
# On the website, we can show all products, but also products scoped to
# categories, brands, users. In this case case, the association is optional, and
# we deal with it in the following way:
#
# class ProductsController < InheritedResources::Base
# belongs_to :category, :brand, :user, :polymorphic => true, :optional => true
# end
#
# This will handle all those urls properly:
#
# /products/1
# /categories/2/products/5
# /brands/10/products/3
# /user/13/products/11
#
# = nested polymorphic associations
#
# You can have polymorphic associations with nested resources. Let's suppose
# that our File, Task and Message resources in the previous example belongs to
# a project.
#
# This way we can have:
#
# class CommentsController < InheritedResources::Base
# belongs_to :project {
# belongs_to :file, :message, :task, :polymorphic => true
# }
# end
#
# Or:
#
# class CommentsController < InheritedResources::Base
# nested_belongs_to :project
# nested_belongs_to :file, :message, :task, :polymorphic => true
# end
#
# Choose the syntax that makes more sense to you. :)
#
# Finally your routes should be something like:
#
# map.resources :projects do |m|
# m.resources :files, :has_many => :comments #=> /projects/1/files/13/comments
# m.resources :tasks, :has_many => :comments #=> /projects/1/tasks/17/comments
# m.resources :messages, :has_many => :comments #=> /projects/1/messages/11/comments
# end
#
# The helpers work in the same way as above.
#
module PolymorphicHelpers
protected
# Returns the parent type. A Comments class can have :task, :file, :note
# as parent types.
#
def parent_type
unless @parent_type
symbols_for_association_chain
end
@parent_type
end
def parent_class
parent.class if parent_type
end
# Returns the parent object. They are also available with the instance
# variable name: @task, @file, @note...
#
def parent
if parent_type
p = instance_variable_get("@#{parent_type}")
p || instance_variable_set("@#{parent_type}", association_chain[-1])
end
end
# If the polymorphic association is optional, we might not have a parent.
#
def parent?
if resources_configuration[:polymorphic][:optional]
parents_symbols.size > 1 || !parent_type.nil?
else
true
end
end
private
# Maps parents_symbols to build association chain.
#
# If the parents_symbols find :polymorphic, it goes through the
# params keys to see which polymorphic parent matches the given params.
#
# When optional is given, it does not raise errors if the polymorphic
# params are missing.
#
def symbols_for_association_chain #:nodoc:
polymorphic_config = resources_configuration[:polymorphic]
parents_symbols.map do |symbol|
if symbol == :polymorphic
params_keys = params.keys
keys = polymorphic_config[:symbols].map do |poly|
params_keys.include?(resources_configuration[poly][:param].to_s) ? poly : nil
end.compact
if keys.empty?
raise ScriptError, "Could not find param for polymorphic association. The request " <<
"parameters are #{params.keys.inspect} and the polymorphic " <<
"associations are #{polymorphic_config[:symbols].inspect}." unless polymorphic_config[:optional]
nil
else
@parent_type = keys[-1].to_sym
@parent_types = keys.map(&:to_sym)
end
else
symbol
end
end.flatten.compact
end
end
end