adva-categories 0.0.3 → 0.0.4
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/app/controllers/admin/categories_controller.rb +4 -0
- data/app/controllers/base_controller_slice.rb +15 -0
- data/app/helpers/url_helper_slice.rb +11 -0
- data/app/models/categorization.rb +5 -0
- data/app/models/category.rb +13 -0
- data/app/models/content_slice.rb +2 -0
- data/app/models/section_slice.rb +4 -0
- data/app/views/admin/categories/_form.html.rb +17 -0
- data/app/views/admin/categories/_menu.html.rb +21 -0
- data/app/views/admin/categories/edit.html.rb +9 -0
- data/app/views/admin/categories/index.html.rb +20 -0
- data/app/views/admin/categories/new.html.rb +10 -0
- data/config/locales/en.yml +32 -0
- data/config/redirects.rb +4 -0
- data/config/routes.rb +13 -0
- data/lib/adva/active_record/categorizable.rb +26 -0
- data/lib/adva/categories.rb +13 -0
- data/lib/adva/routing_filters/categories.rb +81 -0
- data/lib/adva/views/categories_tab.rb +14 -0
- data/lib/adva-categories.rb +1 -0
- data/lib/adva_categories/version.rb +3 -0
- data/lib/testing/factories.rb +4 -0
- data/lib/testing/paths.rb +15 -0
- data/lib/testing/step_definitions.rb +21 -0
- metadata +28 -5
- data/lib/bundler/repository.rb +0 -118
@@ -0,0 +1,15 @@
|
|
1
|
+
require_dependency 'base_controller'
|
2
|
+
|
3
|
+
BaseController.class_eval do
|
4
|
+
include do
|
5
|
+
def collection
|
6
|
+
params[:category_id] ? super.categorized(params[:category_id]) : super
|
7
|
+
end
|
8
|
+
|
9
|
+
def category
|
10
|
+
params[:category_id] ? Category.find(params[:category_id]) : nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
helper_method :category
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Category < ActiveRecord::Base
|
2
|
+
belongs_to :section
|
3
|
+
validates_presence_of :section, :name
|
4
|
+
|
5
|
+
acts_as_nested_set :scope => :section_id
|
6
|
+
has_slug :scope => :section_id
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def categorizable(table)
|
10
|
+
Category.has_many_polymorphs :categorizables, :through => :categorizations, :foreign_key => 'category_id', :from => [table]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Admin::Categories::Menu < Adva::View::Menu::Admin::Actions
|
2
|
+
include do
|
3
|
+
def main
|
4
|
+
parent_resource_label
|
5
|
+
index
|
6
|
+
edit_parent
|
7
|
+
end
|
8
|
+
|
9
|
+
def right
|
10
|
+
collection? ? new : destroy
|
11
|
+
reorder if index? && collection.size > 1
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def reorder
|
17
|
+
super( :'data-resource_type' => 'section', :'data-sortable_type' => 'categories')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Admin::Categories::Index < Minimal::Template
|
2
|
+
include do
|
3
|
+
def to_html
|
4
|
+
table_for collection, :class => 'categories list tree' do |t|
|
5
|
+
t.column :category, :actions
|
6
|
+
|
7
|
+
t.row do |r, category|
|
8
|
+
r.options[:id] = dom_id(category)
|
9
|
+
r.options[:class] = "level_#{category.level.to_i}"
|
10
|
+
r.cell capture { link_to_edit(category.name, category) }
|
11
|
+
r.cell links_to_actions([:edit, :destroy], category)
|
12
|
+
end
|
13
|
+
|
14
|
+
t.empty :p, :class => 'categories list empty' do
|
15
|
+
self.t(:'.empty', :link => capture { link_to(:'.create_item', new_path(:category)) }).html_safe
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
en:
|
2
|
+
menu:
|
3
|
+
categories: Categories
|
4
|
+
columns:
|
5
|
+
category: Category
|
6
|
+
actions:
|
7
|
+
categories:
|
8
|
+
columns:
|
9
|
+
category: Category
|
10
|
+
actions:
|
11
|
+
admin:
|
12
|
+
categories:
|
13
|
+
# menu:
|
14
|
+
# index: Categories
|
15
|
+
# products: Products
|
16
|
+
# new: New Category
|
17
|
+
# edit_parent: Settings
|
18
|
+
# destroy: Delete
|
19
|
+
# reorder: Reorder
|
20
|
+
# confirm_destroy: Do you really want to delete this category?
|
21
|
+
index:
|
22
|
+
create_item: Create Category
|
23
|
+
edit: Edit
|
24
|
+
destroy: Delete
|
25
|
+
confirm_destroy: Do you really want to delete this category?
|
26
|
+
empty: "There are no categories. %{link}"
|
27
|
+
new:
|
28
|
+
title: Create a Category
|
29
|
+
edit:
|
30
|
+
title: Edit Category
|
31
|
+
tabs:
|
32
|
+
categories: Categories
|
data/config/redirects.rb
ADDED
data/config/routes.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Adva
|
2
|
+
module Categorizable
|
3
|
+
def categorizable
|
4
|
+
unless categorizable?
|
5
|
+
Category.categorizable(name.underscore.pluralize.to_sym)
|
6
|
+
has_many :categories, :through => :categorizations, :as => :categorizable
|
7
|
+
accepts_nested_attributes_for :categorizations, :allow_destroy => true
|
8
|
+
extend ClassMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def categorizable?
|
13
|
+
singleton_class.included_modules.include?(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def categorized(category_id)
|
18
|
+
category = Category.find(category_id)
|
19
|
+
category_ids = category.self_and_descendants.map(&:id)
|
20
|
+
includes(:categorizations).where(:categorizations => { :category_id => category_ids })
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveRecord::Base.extend(self)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'adva/core'
|
2
|
+
require 'adva/active_record/categorizable'
|
3
|
+
require 'adva/views/categories_tab'
|
4
|
+
|
5
|
+
module Adva
|
6
|
+
class Categories < ::Rails::Engine
|
7
|
+
include Adva::Engine
|
8
|
+
|
9
|
+
initializer 'adva-categories.configure_routing_filters' do
|
10
|
+
RoutingFilter::SectionRoot.anchors << 'categories'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'routing_filter'
|
2
|
+
|
3
|
+
module RoutingFilter
|
4
|
+
class Categories < Filter
|
5
|
+
cattr_accessor :default_port
|
6
|
+
self.default_port = '80'
|
7
|
+
|
8
|
+
cattr_accessor :exclude
|
9
|
+
self.exclude = %r(^/admin)
|
10
|
+
|
11
|
+
def around_recognize(path, env, &block)
|
12
|
+
# p "#{self.class.name}: #{path}"
|
13
|
+
unless excluded?(path)
|
14
|
+
category_id = extract_category_id(env, path)
|
15
|
+
yield.tap do |params|
|
16
|
+
params[:category_id] = category_id if category_id
|
17
|
+
end
|
18
|
+
else
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def around_generate(params, &block)
|
24
|
+
category_id = params.delete('category_id') || params.delete(:category_id)
|
25
|
+
yield.tap do |path|
|
26
|
+
# p "#{self.class.name}: #{path}"
|
27
|
+
insert_category_path(path, category_id) if !excluded?(path) && category_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def excluded?(path)
|
34
|
+
path =~ exclude
|
35
|
+
end
|
36
|
+
|
37
|
+
def extract_category_id(env, path)
|
38
|
+
if section = section_for(env, path) and path =~ recognition_pattern(section)
|
39
|
+
if category = section.categories.where(:path => $2).first
|
40
|
+
path.gsub!("#{$1}#{$2}", '')
|
41
|
+
path.replace('/') if path.blank?
|
42
|
+
category.id.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def recognition_pattern(section)
|
48
|
+
paths = section.categories.map(&:path).reject(&:blank?)
|
49
|
+
paths = paths.sort { |a, b| b.size <=> a.size }.join('|')
|
50
|
+
paths.empty? ? %r(^$) : %r(^.*(/categories/)(#{paths})(?=/|\.|\?|$))
|
51
|
+
end
|
52
|
+
|
53
|
+
def insert_category_path(path, category_id)
|
54
|
+
category = Category.find(category_id)
|
55
|
+
if path =~ section_pattern
|
56
|
+
path.sub!("/#{$1}/#{$2}", "/#{$1}/#{$2}/categories/#{category.path}")
|
57
|
+
elsif category.section.root?
|
58
|
+
path.sub!(%r(^/), "/categories/#{category.path}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def section_pattern
|
63
|
+
types = Section.types.map { |type| type.downcase.pluralize }.join('|')
|
64
|
+
%r(/(sections|#{types})/(\d+)(?=/|\.|\?|$))
|
65
|
+
end
|
66
|
+
|
67
|
+
def section_for(env, path)
|
68
|
+
if path =~ section_pattern
|
69
|
+
Section.find($2)
|
70
|
+
elsif path =~ %r(^/categories/\w+) && site = Site.by_host(host(env))
|
71
|
+
site.sections.root
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def host(env)
|
76
|
+
host, port = env.values_at('SERVER_NAME', 'SERVER_PORT')
|
77
|
+
port == default_port ? host : [host, port].compact.join(':')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# this should rather be a code slice, shouldn't it? but slices don't support files
|
2
|
+
# that aren't loadable through require_dependency
|
3
|
+
|
4
|
+
require 'adva/view/form'
|
5
|
+
|
6
|
+
Adva::View::Form.class_eval do
|
7
|
+
def categories_tab(categories)
|
8
|
+
tab :categories do
|
9
|
+
fieldset do
|
10
|
+
form.has_many_through_collection_check_boxes(:categorizations, categories, :name)
|
11
|
+
end
|
12
|
+
end if categories.present?
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'adva/categories'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Adva::Categories::Paths
|
2
|
+
def path_to(page)
|
3
|
+
case page
|
4
|
+
when /^the admin "([^"]*)" section categories page$/
|
5
|
+
site = Site.first
|
6
|
+
section = Section.where(:name => $1).first || raise("could not find section named #{$1}")
|
7
|
+
polymorphic_path([:admin, site, section, :categories])
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
World(Adva::Categories::Paths)
|
15
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
When /^I drag the category "([^"]*)" below the category "([^"]*)"$/ do |category, target|
|
2
|
+
category = Category.find_by_name(category)
|
3
|
+
target = Category.find_by_name(target)
|
4
|
+
section = Section.first
|
5
|
+
put(admin_site_section_path(section.site, section), {
|
6
|
+
:section => { :categories_attributes => [{ :id => category.id, :left_id => target.id }] },
|
7
|
+
:return_to => @request.url
|
8
|
+
})
|
9
|
+
follow_redirect!
|
10
|
+
end
|
11
|
+
|
12
|
+
Then /^the ([\w]+) (name|title)d "([^"]+)" should be categorized as "([^"]+)"$/ do |model, attribute, value, category|
|
13
|
+
record = model.classify.constantize.where(attribute => value).first
|
14
|
+
assert record.categories.map(&:name).include?(category), "expected #{record.inspect} to be categorized as #{category}"
|
15
|
+
end
|
16
|
+
|
17
|
+
Then /^the ([\w]+) (name|title)d "([^"]+)" should not be categorized as "([^"]+)"$/ do |model, attribute, value, category|
|
18
|
+
record = model.classify.constantize.where(attribute => value).first
|
19
|
+
assert !record.categories.map(&:name).include?(category), "expected #{record.inspect} not to be categorized as #{category}"
|
20
|
+
end
|
21
|
+
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adva-categories
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ingo Weiss
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-
|
19
|
+
date: 2010-12-03 00:00:00 +01:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -42,7 +42,30 @@ extensions: []
|
|
42
42
|
extra_rdoc_files: []
|
43
43
|
|
44
44
|
files:
|
45
|
-
-
|
45
|
+
- app/controllers/admin/categories_controller.rb
|
46
|
+
- app/controllers/base_controller_slice.rb
|
47
|
+
- app/helpers/url_helper_slice.rb
|
48
|
+
- app/views/admin/categories/index.html.rb
|
49
|
+
- app/views/admin/categories/edit.html.rb
|
50
|
+
- app/views/admin/categories/new.html.rb
|
51
|
+
- app/views/admin/categories/_form.html.rb
|
52
|
+
- app/views/admin/categories/_menu.html.rb
|
53
|
+
- app/models/content_slice.rb
|
54
|
+
- app/models/category.rb
|
55
|
+
- app/models/categorization.rb
|
56
|
+
- app/models/section_slice.rb
|
57
|
+
- config/redirects.rb
|
58
|
+
- config/routes.rb
|
59
|
+
- config/locales/en.yml
|
60
|
+
- lib/adva-categories.rb
|
61
|
+
- lib/adva_categories/version.rb
|
62
|
+
- lib/adva/categories.rb
|
63
|
+
- lib/adva/active_record/categorizable.rb
|
64
|
+
- lib/adva/routing_filters/categories.rb
|
65
|
+
- lib/adva/views/categories_tab.rb
|
66
|
+
- lib/testing/factories.rb
|
67
|
+
- lib/testing/paths.rb
|
68
|
+
- lib/testing/step_definitions.rb
|
46
69
|
has_rdoc: true
|
47
70
|
homepage: http://github.com/svenfuchs/adva-cms2
|
48
71
|
licenses: []
|
data/lib/bundler/repository.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
require 'pathname'
|
2
|
-
|
3
|
-
# Bundler gemfile support for local/remote workspaces/repositories for work in
|
4
|
-
# development teams.
|
5
|
-
#
|
6
|
-
# Usage:
|
7
|
-
#
|
8
|
-
# # define paths to be searched for repositories:
|
9
|
-
# workspace '~/.projects ~/Development/{projects,work}'
|
10
|
-
#
|
11
|
-
# # define developer preferences for using local or remote repositories (uses ENV['user']):
|
12
|
-
# developer :sven, :prefer => :local
|
13
|
-
#
|
14
|
-
# # define repositories to be used for particular gems:
|
15
|
-
# adva_cms = repository('adva-cms2', :git => 'git@github.com:svenfuchs/adva-cms2.git', :ref => 'c2af0de')
|
16
|
-
# adva_shop = repository('adva-shop', :source => :local)
|
17
|
-
#
|
18
|
-
# # now use repositories to define gems:
|
19
|
-
# adva_cms.gem 'adva-core'
|
20
|
-
# adva_shop.gem 'adva-catalog'
|
21
|
-
#
|
22
|
-
# # The gem definition will now be proxied to Bundler with arguments according
|
23
|
-
# # to the setup defined earlier. E.g. as:
|
24
|
-
#
|
25
|
-
# gem 'adva-core', :path => 'Development/projects/adva-cms2/adva-core' # for developer 'sven'
|
26
|
-
# gem 'adva-core', :git => 'git@github.com:svenfuchs/adva-cms2.git', :ref => 'c2af0de' # for other developers
|
27
|
-
# gem 'adva-catalog', :path => 'Development/projects/adva-shop/adva-catalog' # for all developers
|
28
|
-
#
|
29
|
-
# One can also set an environment variable FORCE_REMOTE which will force remote
|
30
|
-
# repositories to be used *except* when a repository was defined with :source => :local
|
31
|
-
# which always forces the local repository to be used.
|
32
|
-
#
|
33
|
-
class Repository
|
34
|
-
class << self
|
35
|
-
def paths
|
36
|
-
@paths ||= []
|
37
|
-
end
|
38
|
-
|
39
|
-
def path(*paths)
|
40
|
-
paths.join(' ').split(' ').each do |path|
|
41
|
-
self.paths.concat(Pathname.glob(File.expand_path(path)))
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def developer(name, preferences)
|
46
|
-
developers[name] = preferences
|
47
|
-
workspaces(preferences[:workspace])
|
48
|
-
end
|
49
|
-
|
50
|
-
def current_developer
|
51
|
-
developers[ENV['USER'].to_sym] || {}
|
52
|
-
end
|
53
|
-
|
54
|
-
def developers(developers = nil)
|
55
|
-
@developers ||= {}
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
class Gem < Array
|
60
|
-
def initialize(name, repository)
|
61
|
-
if repository.local?
|
62
|
-
sub_path = repository.path.join(name)
|
63
|
-
super([name, { :path => sub_path.exist? ? sub_path.to_s : repository.path.to_s }])
|
64
|
-
else
|
65
|
-
super([name, repository.options.dup])
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
attr_reader :bundler, :name, :options, :source
|
71
|
-
|
72
|
-
def initialize(bundler, name, options)
|
73
|
-
@bundler = bundler
|
74
|
-
@name = name
|
75
|
-
@source = options.delete(:source)
|
76
|
-
@options = options
|
77
|
-
end
|
78
|
-
|
79
|
-
def gem(name)
|
80
|
-
bundler.gem(*Gem.new(name, self))
|
81
|
-
end
|
82
|
-
|
83
|
-
def local?
|
84
|
-
source == :local # && path
|
85
|
-
end
|
86
|
-
|
87
|
-
def source
|
88
|
-
@source ||= forced_source || preferred_source || :remote
|
89
|
-
end
|
90
|
-
|
91
|
-
def forced_source
|
92
|
-
:remote if ENV['FORCE_REMOTE']
|
93
|
-
end
|
94
|
-
|
95
|
-
def preferred_source
|
96
|
-
self.class.current_developer[:prefer] || self.class.current_developer[name.to_sym]
|
97
|
-
end
|
98
|
-
|
99
|
-
def path
|
100
|
-
@path ||= begin
|
101
|
-
path = self.class.paths.detect { |path| path.join(name).exist? }
|
102
|
-
path ? path.join(name) : Pathname.new('.')
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def workspace(*paths)
|
108
|
-
Repository.path(*paths)
|
109
|
-
end
|
110
|
-
alias :workspaces :workspace
|
111
|
-
|
112
|
-
def developer(name, preferences)
|
113
|
-
Repository.developer(name, preferences)
|
114
|
-
end
|
115
|
-
|
116
|
-
def repository(*args)
|
117
|
-
Repository.new(self, *args)
|
118
|
-
end
|