File: polymorphic_helpers.rb

package info (click to toggle)
ruby-inherited-resources 1.13.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 824 kB
  • sloc: ruby: 4,388; makefile: 6
file content (175 lines) | stat: -rw-r--r-- 5,475 bytes parent folder | download | duplicates (2)
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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:
  #
  #   resources :files do
  #     resources :comments #=> /files/13/comments
  #   end
  #   resources :tasks do
  #     resources :comments #=> /tasks/17/comments
  #   end
  #   resources :messages do
  #     resources :comments #=> /messages/11/comments
  #   end
  #
  # 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:
  #
  #   resources :projects do
  #     resources :files do
  #       resources :comments #=> /projects/1/files/13/comments
  #     end
  #     resources :tasks do
  #       resources :comments #=> /projects/1/tasks/17/comments
  #     end
  #     resources :messages do
  #       resources :comments #=> /projects/1/messages/11/comments
  #     end
  #   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 instance_variable_defined?(:@parent_type)
          symbols_for_association_chain
        end

        if instance_variable_defined?(:@parent_type)
          @parent_type
        end
      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_defined?("@#{parent_type}") && 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