adva-categories 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ class Admin::CategoriesController < Admin::BaseController
2
+ belongs_to :site
3
+ belongs_to :section
4
+ end
@@ -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,11 @@
1
+ require_dependency 'url_helper'
2
+
3
+ UrlHelper.module_eval do
4
+ include do
5
+ def public_url_for(resources, options = {})
6
+ options[:category_id] = resources.pop.id if resources.last.class.name == 'Category'
7
+ super
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,5 @@
1
+ class Categorization < ActiveRecord::Base
2
+ belongs_to :category
3
+ accepts_nested_attributes_for :category
4
+ end
5
+
@@ -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,2 @@
1
+ Content.categorizable
2
+
@@ -0,0 +1,4 @@
1
+ Section.class_eval do
2
+ has_many :categories, :foreign_key => :section_id
3
+ accepts_nested_attributes_for :categories
4
+ end
@@ -0,0 +1,17 @@
1
+ class Admin::Categories::Form < Adva::View::Form
2
+ include do
3
+ def fields
4
+ fieldset do
5
+ form.input :name
6
+ end
7
+ end
8
+
9
+ def sidebar
10
+ tab :options do
11
+ form.input :slug
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+
@@ -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,9 @@
1
+ class Admin::Categories::Edit < Adva::View::Form
2
+ include do
3
+ def to_html
4
+ h2 :'.title'
5
+ render :partial => 'form'
6
+ end
7
+ end
8
+ end
9
+
@@ -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,10 @@
1
+ class Admin::Categories::New < Adva::View::Form
2
+ include do
3
+ def to_html
4
+ h2 :'.title'
5
+ render :partial => 'form'
6
+ end
7
+ end
8
+ end
9
+
10
+
@@ -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
@@ -0,0 +1,4 @@
1
+ Adva::Registry.set :redirect, {
2
+ 'admin/categories#create' => lambda { |c| c.edit_url },
3
+ 'admin/categories#update' => lambda { |c| c.edit_url }
4
+ }
data/config/routes.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'adva/routing_filters/categories'
2
+
3
+ Rails.application.routes.draw do
4
+ filter :categories
5
+
6
+ namespace :admin do
7
+ resources :sites do
8
+ resources :sections do
9
+ resources :categories
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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,3 @@
1
+ module AdvaCategories
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,4 @@
1
+ Factory.define :category do |f|
2
+ f.section { Section.first || Factory(:section) }
3
+ f.name 'Category'
4
+ end
@@ -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: 25
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 3
10
- version: 0.0.3
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-11-19 00:00:00 +01:00
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
- - lib/bundler/repository.rb
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: []
@@ -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