Module: ActiveAdmin::NestedNamespace

Defined in:
lib/active_admin/nested_namespace.rb,
lib/active_admin/nested_namespace/version.rb

Constant Summary collapse

VERSION =
"0.1.1"

Class Method Summary collapse

Class Method Details

.setupObject



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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/active_admin/nested_namespace.rb', line 5

def self.setup
  ActiveAdmin::Application.class_eval do

    def namespace(name)
      name ||= :root

      namespace = namespaces[build_name_path(name)] ||= begin
        namespace = Namespace.new(self, name)
        ActiveSupport::Notifications.publish ActiveAdmin::Namespace::RegisterEvent, namespace
        namespace
      end

      yield(namespace) if block_given?

      namespace
    end

    def build_name_path(name)
      Array(name).map(&:to_s).map(&:underscore).map(&:to_sym)
    end

  end

  ActiveAdmin::BaseController.class_eval do
    def authenticate_active_admin_user
      if active_admin_namespace.authentication_method
        auth_method = active_admin_namespace.authentication_method
        if auth_method.is_a?(Proc)
          namespace = active_admin_namespace.name_path
          send(auth_method.call(namespace))
        else
          send(auth_method)
        end
      end
    end

    def current_active_admin_user
      if active_admin_namespace.current_user_method
        user_method = active_admin_namespace.current_user_method
        if user_method.is_a?(Proc)
          namespace = active_admin_namespace.name_path
          send(user_method.call(namespace))
        else
          send(user_method)
        end
      end
    end
  end

  ActiveAdmin::Namespace.class_eval do

    attr_reader :name_path

    def initialize(application, name)
      @application = application
      @name_path = ActiveAdmin.application.build_name_path(name)
      @resources = ResourceCollection.new
      register_module unless root?
      build_menu_collection
    end

    def name
      Deprecation.warn "name replaced by name_path now that namespaces can be nested."
      name_path.first
    end

    def root?
      name_path.first == :root
    end

    def module_name
      root? ? nil : name_path.map(&:to_s).map(&:camelize).join('::')
    end

    def route_prefix
      root? ? nil : name_path.map(&:to_s).join('_').underscore
    end

    def add_logout_button_to_menu(menu, priority = 20, html_options = {})
      computed_logout_link_path = logout_link_path.is_a?(Proc) ? logout_link_path.call(name_path) : logout_link_path

      if computed_logout_link_path
        html_options = html_options.reverse_merge(method: logout_link_method || :get)
        menu.add id: 'logout', priority: priority, html_options: html_options,
                 label: -> {I18n.t 'active_admin.logout'},
                 url: computed_logout_link_path,
                 if: :current_active_admin_user?
      end
    end

    # dynamically create nested modules
    def register_module
      module_names = module_name.split("::").inject([]) {|n, c| n << (n.empty? ? [c] : [n.last] + [c]).flatten}
      module_names.each do |module_name_array|
        eval "module ::#{module_name_array.join("::")}; end"
      end
    end

  end

  ActiveAdmin::Namespace::Store.class_eval do

    def [](key)
      @namespaces[Array(key)]
    end

    def names
      Deprecation.warn "names replaced by name_paths now that namespaces can be nested."
      @namespaces.keys.first
    end

    def name_paths
      @namespaces.keys
    end

  end
  
  ActiveAdmin::Comment.class_eval do
    def self.find_for_resource_in_namespace(resource, name)
      where(
          resource_type: resource_type(resource),
          resource_id: resource,
          namespace: name.to_s
      ).order(ActiveAdmin.application.namespaces[ActiveAdmin.application.build_name_path(name)].comments_order)
    end
  end

  ActiveAdmin::Comments::Views::Comments.class_eval do

    def build(resource)
      @resource = resource
      @comments = ActiveAdmin::Comment.find_for_resource_in_namespace(resource, active_admin_namespace.name_path).includes(:author).page(params[:page])
      super(title, for: resource)
      build_comments
    end

    def comments_url(*args)
      parts = []
      parts << active_admin_namespace.name_path unless active_admin_namespace.root?
      parts << active_admin_namespace.comments_registration_name.underscore
      parts << 'path'
      send parts.join('_'), *args
    end

    def comment_form_url
      parts = []
      parts << active_admin_namespace.name_path unless active_admin_namespace.root?
      parts << active_admin_namespace.comments_registration_name.underscore.pluralize
      parts << 'path'
      send parts.join '_'
    end

  end

  ActiveAdmin::Page.class_eval do
    def namespace_name
      Deprecation.warn "namespace_name replaced by namespace_name now that namespaces can be nested."
      namespace.name.to_s
    end

    def namespace_name_path
      namespace.name_path
    end
  end

  ActiveAdmin::Resource::BelongsTo::TargetNotFound.class_eval do
    def initialize(key, namespace)
      super "Could not find #{key} in #{namespace.name_path} " +
                "with #{namespace.resources.map(&:resource_name)}"
    end
  end

  ActiveAdmin::Router.class_eval do

    def define_root_routes(router)
      router.instance_exec @application.namespaces do |namespaces|
        namespaces.each do |namespace|
          if namespace.root?
            root namespace.root_to_options.merge(to: namespace.root_to)
          else
            proc = Proc.new do
              root namespace.root_to_options.merge(to: namespace.root_to, as: :root)
            end
            (namespace.name_path.reverse.inject(proc) {|n, c| Proc.new {namespace c, namespace.route_options.dup, &n}}).call
          end
        end
      end
    end

    # Defines the routes for each resource
    def define_resource_routes(router)
      router.instance_exec @application.namespaces, self do |namespaces, aa_router|
        resources = namespaces.flat_map {|n| n.resources.values}
        resources.each do |config|
          routes = aa_router.resource_routes(config)

          # Add in the parent if it exists
          if config.belongs_to?
            belongs_to = routes
            routes = Proc.new do
              # If it's optional, make the normal resource routes
              instance_exec &belongs_to if config.belongs_to_config.optional?

              # Make the nested belongs_to routes
              # :only is set to nothing so that we don't clobber any existing routes on the resource
              resources config.belongs_to_config.target.resource_name.plural, only: [] do
                instance_exec &belongs_to
              end
            end
          end

          # Add on the namespace if required
          unless config.namespace.root?
            nested = routes
            routes = Proc.new do

              proc = Proc.new do
                instance_exec &nested
              end
              (config.namespace.name_path.reverse.inject(proc) {|n, c| Proc.new {namespace c, config.namespace.route_options.dup, &n}}).call
            end
          end

          instance_exec &routes
        end
      end
    end

  end

  ActiveAdmin::Scope.class_eval do
    def initialize(name, method = nil, options = {}, &block)
      @name, @scope_method = name, method.try(:to_sym)

      if name.is_a? Proc
        raise "A string/symbol is required as the second argument if your label is a proc." unless method
        @id = ActiveAdmin::Dependency.rails.parameterize method.to_s
      else
        @scope_method ||= (name.is_a?(Array) ? name.join('_').underscore : name).to_sym
        @id = ActiveAdmin::Dependency.rails.parameterize name.to_s
      end

      @scope_method = nil if @scope_method == :all
      @scope_method, @scope_block = nil, block if block_given?

      @localizer = options[:localizer]
      @show_count = options.fetch(:show_count, true)
      @display_if_block = options[:if] || proc {true}
      @default_block = options[:default] || proc {false}
    end
  end

  ActiveAdmin::Views::Pages::Base.class_eval do
    def add_classes_to_body
      @body.add_class(params[:action])
      @body.add_class(params[:controller].tr('/', '_'))
      @body.add_class("active_admin")
      @body.add_class("logged_in")
      @body.add_class(active_admin_namespace.name_path.map(&:to_s).join('_') + '_namespace')
    end
  end

  # patch - active_admin/orm/active_record/comments.rb
  ActiveAdmin.after_load do |app|
    app.namespaces.each do |namespace|

      # Un-register default comment pages
      namespace.resources.instance_variable_get(:@collection).delete_if {|k,v| v.instance_variable_get(:@resource_class_name) == '::ActiveAdmin::Comment' }

      # Re-register comments pages with nested namespaces
      namespace.register ActiveAdmin::Comment, as: namespace.comments_registration_name do
        actions :index, :show, :create, :destroy

        menu namespace.comments ? namespace.comments_menu : false

        config.comments = false # Don't allow comments on comments
        config.batch_actions = false # The default destroy batch action isn't showing up anyway...

        scope :all, show_count: false
        # Register a scope for every namespace that exists.
        # The current namespace will be the default scope.
        app.namespaces.map(&:name_path).sort { |x,y| x[0] <=> y[0] }.each do |name_path|
          scope "/#{name_path.join('/')}", default: namespace.name_path == name_path do |scope|
            scope.where namespace: name_path.to_s
          end
        end

        # Store the author and namespace
        before_save do |comment|
          comment.namespace = active_admin_config.namespace.name_path
          comment.author = current_active_admin_user
        end

        controller do
          # Prevent N+1 queries
          def scoped_collection
            super.includes(:author, :resource)
          end

          # Redirect to the resource show page after comment creation
          def create
            create! do |success, failure|
              success.html do
                ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
              end
              failure.html do
                flash[:error] = I18n.t 'active_admin.comments.errors.empty_text'
                ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
              end
            end

            def destroy
              destroy! do |success, failure|
                success.html do
                  ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
                end
                failure.html do
                  ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
                end
              end
            end
          end
        end

        permit_params :body, :namespace, :resource_id, :resource_type

        index do
          column I18n.t('active_admin.comments.resource_type'), :resource_type
          column I18n.t('active_admin.comments.author_type'), :author_type
          column I18n.t('active_admin.comments.resource'), :resource
          column I18n.t('active_admin.comments.author'), :author
          column I18n.t('active_admin.comments.body'), :body
          column I18n.t('active_admin.comments.created_at'), :created_at
          actions
        end
      end
    end
  end
  
end