cardboard_cms 0.2.2 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -0
  3. data/Gemfile +2 -2
  4. data/Gemfile.lock +47 -42
  5. data/README.md +12 -2
  6. data/app/assets/javascripts/cardboard/admin.js +1 -1
  7. data/app/controllers/pages_controller.rb +3 -35
  8. data/app/controllers/url_controller.rb +33 -0
  9. data/app/decorators/controllers/application_controller_decorator.rb +3 -1
  10. data/app/helpers/cardboard/public_helper.rb +1 -1
  11. data/app/helpers/cardboard/resource_helper.rb +6 -0
  12. data/app/models/cardboard/field.rb +2 -1
  13. data/app/models/cardboard/field/rich_text.rb +5 -5
  14. data/app/models/cardboard/page.rb +30 -72
  15. data/app/models/cardboard/setting.rb +0 -2
  16. data/app/models/cardboard/template.rb +5 -0
  17. data/app/models/cardboard/url.rb +91 -0
  18. data/app/views/cardboard/pages/_sidebar.html.slim +1 -1
  19. data/app/views/layouts/cardboard/_main_topbar.html.slim +1 -1
  20. data/cardboard.gemspec +4 -2
  21. data/config/routes.rb +3 -14
  22. data/db/migrate/1_create_cardboard.rb +13 -4
  23. data/lib/cardboard/concerns/url_concern.rb +29 -0
  24. data/lib/cardboard/constraints/page_constraint.rb +7 -0
  25. data/lib/cardboard/dynamic_router.rb +34 -0
  26. data/lib/cardboard/engine.rb +3 -1
  27. data/lib/cardboard/helpers/content_for_in_controllers.rb +23 -0
  28. data/lib/cardboard/helpers/seed.rb +17 -10
  29. data/lib/cardboard/version.rb +1 -1
  30. data/lib/generators/cardboard/resource/resource_generator.rb +2 -2
  31. data/lib/generators/cardboard/resource/templates/slim/edit.html.slim +1 -1
  32. data/lib/generators/cardboard/resource/templates/slim/new.html.slim +1 -1
  33. data/test/dummy/app/controllers/blog_controller.rb +2 -0
  34. data/test/dummy/app/views/blog/index.html.slim +1 -0
  35. data/test/dummy/app/views/layouts/application.html.slim +1 -0
  36. data/test/dummy/app/views/pages/about_us.html.slim +1 -0
  37. data/test/dummy/app/views/pages/history.html.slim +3 -0
  38. data/test/dummy/app/views/{templates → pages}/home.html.slim +0 -0
  39. data/test/dummy/config/cardboard.yml +6 -2
  40. data/test/dummy/db/migrate/20140312180204_create_cardboard.rb +72 -0
  41. data/test/dummy/db/schema.rb +19 -6
  42. data/test/models/url_test.rb +11 -0
  43. metadata +32 -28
  44. data/.ruby-gemset +0 -1
  45. data/.ruby-version +0 -1
  46. data/app/views/cardboard/pages/_error.html.slim +0 -7
  47. data/app/views/cardboard/pages/_seo.html.slim +0 -6
  48. data/app/views/cardboard/pages/show.html.slim +0 -9
  49. data/test/dummy/app/views/templates/about-us.html.slim +0 -1
  50. data/test/dummy/test/fixtures/admins.yml +0 -7
  51. data/test/dummy/test/functional/admins_controller_test.rb +0 -49
  52. data/test/dummy/test/unit/admin_test.rb +0 -7
  53. data/test/dummy/test/unit/helpers/admins_helper_test.rb +0 -4
@@ -1,7 +1,7 @@
1
1
  module Cardboard
2
2
  class Field::RichText < Field
3
3
  validate :is_required
4
- before_save :sanitize_value
4
+ # before_save :sanitize_value
5
5
 
6
6
  def value
7
7
  self.value_uid.try(:html_safe)
@@ -13,10 +13,10 @@ module Cardboard
13
13
 
14
14
  private
15
15
 
16
- def sanitize_value
17
- return nil unless value_changed?
18
- self.value = ActionController::Base.helpers.sanitize(self.value, :tags => %w(strong b i u em br p div span ul ol li a pre code blockquote h1 h2 h3), :attributes => %w(class style href src width height alt))
19
- end
16
+ # def sanitize_value
17
+ # return nil unless value_changed?
18
+ # self.value = ActionController::Base.helpers.sanitize(self.value, :tags => %w(strong b i u em br p div span ul ol li a pre code blockquote h1 h2 h3 table), :attributes => %w(class style href src width height alt))
19
+ # end
20
20
 
21
21
  def is_required
22
22
  errors.add(:value, "is required") if required_field? && ActionController::Base.helpers.strip_tags(value_uid).blank?
@@ -1,38 +1,32 @@
1
1
  module Cardboard
2
2
  class Page < ActiveRecord::Base
3
-
4
3
  has_many :parts, class_name: "Cardboard::PagePart", :dependent => :destroy, :validate => true
5
4
 
6
5
  belongs_to :template, class_name: "Cardboard::Template"
7
6
 
8
- attr_accessor :parent_url, :is_root
7
+ attr_accessor :parent_url
9
8
 
10
9
  accepts_nested_attributes_for :parts, allow_destroy: true, :reject_if => :all_blank
11
10
  # TODO: allow destroy and allow all blank only if repeatable
12
11
 
13
- serialize :meta_seo, Hash
14
- serialize :slugs_backup, Array
15
-
16
- before_validation :default_values
17
- before_save :update_slugs_backup
18
-
19
- #gems
20
- acts_as_url :title, :url_attribute => :slug, :scope => :path, only_when_blank: true
21
-
12
+ include UrlConcern
22
13
  include RankedModel
23
- ranks :position, :with_same => :path
14
+ ranks :position
24
15
 
25
16
  #validations
26
- # validates_associated :parts
27
- validates :title, :path, :template, presence:true
28
- validates :slug, uniqueness: { :case_sensitive => false, :scope => :path }, presence: true
17
+ validates :title, :template, presence:true
29
18
  validates :identifier, uniqueness: {:case_sensitive => false}, :format => { :with => /\A[a-z\_0-9]+\z/,
30
19
  :message => "Only downcase letters, numbers and underscores are allowed" }, presence: true
31
- #validate all seo keys are valid meta keys + title
32
20
 
33
21
  #scopes
34
- scope :preordered, -> {order("path ASC, position ASC, slug ASC")}
22
+ scope :preordered, -> {joins(:url_object).order("cardboard_urls.path ASC, position ASC, cardboard_urls.slug ASC")}
23
+ scope :with_path, -> (p) {joins(:url_object).where("cardboard_urls.path = ?",p) }
24
+
25
+
26
+ # Hooks
27
+ before_validation :default_values
35
28
 
29
+ #delegates
36
30
 
37
31
  #class variables
38
32
  after_commit do
@@ -40,51 +34,34 @@ module Cardboard
40
34
  end
41
35
 
42
36
  #overwritten setters/getters
43
- def slug=(value)
44
- # the user can overwrite the auto generated slug
45
- self[:slug] = value.present? ? value.to_url : nil
46
- end
47
37
 
48
- def is_root=(val)
49
- self.position_position = :first if val
38
+ def self.root
39
+ #TODO: check that join work correctly
40
+ with_path('/').rank(:position).first
50
41
  end
42
+ def self.homepage; self.root; end
51
43
 
52
- def using_slug_backup?
53
- @using_slug_backup || false
44
+ def root?
45
+ return @root unless @root.nil?
46
+ @root = self.id == Page.root.id
54
47
  end
55
48
 
56
- def using_slug_backup=(value)
57
- @using_slug_backup = value
49
+ def meta_seo=(hash)
50
+ self.meta_tags = meta_tags.merge(hash)
51
+ end
52
+ def meta_seo
53
+ meta_tags.slice("description", "title")
58
54
  end
59
55
 
60
56
  #class methods
61
57
  def self.find_by_url(full_url)
62
- return nil unless full_url
63
- path, slug = self.path_and_slug(full_url)
64
- page = self.where(path: path, slug: slug).first
65
-
66
- if slug && page.nil?
67
- #use arel instead of LIKE/ILIKE
68
- page = self.where(path: path).where(self.arel_table[:slugs_backup].matches("% #{slug}\n%")).first
69
- page.using_slug_backup = true if page
70
- end
71
-
72
- page
58
+ Cardboard::Url.urlable_for(full_url, type: self.name)
73
59
  end
74
60
 
75
- def self.root
76
- # Homepage is the highest position in the root path
77
- where(path: "/").rank(:position).first
78
- end
79
- def self.homepage; self.root; end
80
-
81
- def root?
82
- @root_id ||= Page.root.id
83
- @root_id == self.id
84
- end
85
61
 
86
62
  #instance methods
87
63
 
64
+
88
65
  def template_hash
89
66
  @template_hash ||= self.template.fields
90
67
  end
@@ -128,14 +105,8 @@ module Cardboard
128
105
  self.meta_seo = hash.to_hash
129
106
  @_seo = nil
130
107
  end
131
-
132
- def url
133
- return "/" if slug.blank? #|| self.root?
134
- "#{path}#{slug}/"
135
- end
136
108
 
137
109
  def split_path
138
- # path.sub(/^\//,'').split("/") # "/path/" => ["path"]
139
110
  path[1..-1].split("/")
140
111
  end
141
112
 
@@ -172,11 +143,11 @@ module Cardboard
172
143
  end
173
144
 
174
145
  def children
175
- Cardboard::Page.where(path: url)
146
+ Cardboard::Page.with_path(url)
176
147
  end
177
148
 
178
149
  def siblings
179
- Cardboard::Page.where("path = ? AND id != ?", path, id)
150
+ Cardboard::Page.with_path(path).where("cardboard_pages.id != ?", id)
180
151
  end
181
152
 
182
153
  def depth
@@ -218,25 +189,12 @@ module Cardboard
218
189
  Rails.cache.delete("arranged_pages")
219
190
  end
220
191
 
221
- # def to_param
222
- # "#{id}-#{slug}"
223
- # end
224
-
225
192
  private
226
- def self.path_and_slug(full_url)
227
- *path, slug = full_url.sub(/^\//, '').split('/')
228
- [path.blank? ? '/' : "/#{path.join('/')}/", slug]
229
- end
230
-
231
-
232
- def update_slugs_backup
233
- return nil if !self.slug_changed? || self.slug_was.nil?
234
- self.slugs_backup |= [self.slug_was] #Yes, that's a single pipe...
235
- end
236
193
 
237
194
  def default_values
238
- self.path ||= '/'
239
- self.title ||= self.identifier.parameterize("_")
195
+ self.title ||= self.identifier.try(:parameterize, "_")
196
+ self.path ||= "/"
197
+ self.slug = self.title.try(:to_url) if self.slug.blank?
240
198
  end
241
199
  end
242
200
  end
@@ -1,7 +1,5 @@
1
1
  module Cardboard
2
2
  class Setting < ActiveRecord::Base
3
- # self.table_name = "cardboard_settings"
4
- # attr_accessible :name, :fields_attributes
5
3
 
6
4
  has_many :fields, :as => :object_with_field
7
5
  accepts_nested_attributes_for :fields, :allow_destroy => true
@@ -7,6 +7,11 @@ module Cardboard
7
7
 
8
8
  validates :identifier, uniqueness: {:case_sensitive => false}, :format => { :with => /\A[a-z\_0-9]+\z/,
9
9
  :message => "Only downcase letters, numbers and underscores are allowed" }
10
+
11
+ after_save :reload_routes
12
+ def reload_routes
13
+ DynamicRouter.reload
14
+ end
10
15
 
11
16
  def name
12
17
  self[:name] || self.identifier
@@ -0,0 +1,91 @@
1
+ module Cardboard
2
+ class Url < ActiveRecord::Base
3
+ belongs_to :urlable, polymorphic: true
4
+
5
+ serialize :slugs_backup, Array
6
+ serialize :meta_tags, Hash
7
+
8
+ before_save :update_slugs_backup
9
+
10
+ validates :path, presence: true
11
+ validates :slug, uniqueness: { :case_sensitive => false, :scope => :path }, presence: true
12
+
13
+ after_save :reload_routes
14
+
15
+ # TODO: Should we use the homepage boolean?
16
+ # before_save :update_homepage
17
+ # def update_homepage
18
+ # return unless homepage_changed?
19
+ # self.class.where('id != ? AND homepage', self.id).update_all(homepage: false)
20
+ # end
21
+
22
+ def self.urlable_for(full_url, options = {})
23
+ #TODO: refactor
24
+ return nil unless full_url
25
+ path, slug = self.path_and_slug(full_url)
26
+ url_hash = {path: path, slug: slug}
27
+ url_hash.merge!(urlable_type: options[:type]) if options[:type]
28
+
29
+ page = self.where(url_hash).first
30
+
31
+ if slug && page.nil?
32
+ #use arel instead of LIKE/ILIKE
33
+ page = self.where(path: path).where(self.arel_table[:slugs_backup].matches("% #{slug}\n%")).where(urlable_type: options[:type]).first
34
+ page.using_slug_backup = true if page
35
+ end
36
+
37
+ page.try(:urlable)
38
+ end
39
+
40
+ def slug=(value)
41
+ # the user can overwrite the auto generated slug
42
+ self[:slug] = value.present? ? value.to_url : nil
43
+ end
44
+
45
+ def path=(value)
46
+ return if value.nil?
47
+ value = value.gsub(/\//, '')
48
+ self[:path] = value.blank?? "/" : "/#{value}/"
49
+ end
50
+
51
+ def using_slug_backup?
52
+ @using_slug_backup || false
53
+ end
54
+
55
+ def using_slug_backup=(value)
56
+ @using_slug_backup = value
57
+ end
58
+
59
+ def slugs_backup=(value)
60
+ if value.is_a?(String)
61
+ self[:slugs_backup] = value.split(",").map(&:strip)
62
+ else
63
+ self[:slugs_backup] = value
64
+ end
65
+ end
66
+
67
+ def to_s
68
+ return "/" if slug.blank?
69
+ "#{path}#{slug}/"
70
+ end
71
+
72
+ private
73
+
74
+ def reload_routes
75
+ DynamicRouter.reload
76
+ end
77
+
78
+ def self.path_and_slug(full_url)
79
+ *path, slug = full_url.sub(/^\//, '').split('/')
80
+ [path.blank? ? '/' : "/#{path.join('/')}/", slug]
81
+ end
82
+
83
+
84
+ def update_slugs_backup
85
+ return nil if !self.slug_changed? || self.slug_was.nil?
86
+ self.slugs_backup |= [self.slug_was] #Yes, that's a single pipe...
87
+ self.slugs_backup = slugs_backup - [self.slug] #in case we are going back to a link that was in the backup
88
+ end
89
+
90
+ end
91
+ end
@@ -1,6 +1,6 @@
1
1
  form class="form-inline"
2
2
  - if Cardboard.used_with_templates?
3
- => link_to pages_new_path, class:"btn btn-primary" do
3
+ => link_to new_page_path, class:"btn btn-primary" do
4
4
  i.icon-plus
5
5
 
6
6
  .input-append style=(Cardboard.used_with_templates?? "width: 160px" : nil)
@@ -3,7 +3,7 @@
3
3
  #cardboard_logo CB
4
4
 
5
5
  = link_to main_app.root_path, id: "brand" do
6
- = Cardboard::Setting.company_name || Cardboard.application.site_title
6
+ = Cardboard::Setting.company_name rescue Cardboard.application.site_title
7
7
 
8
8
  ul.nav.pull-right
9
9
 
@@ -13,6 +13,8 @@ Gem::Specification.new do |s|
13
13
  s.summary = "Rails CMS made simple"
14
14
  s.description = "Rails CMS made simple"
15
15
 
16
+ s.required_ruby_version = ">= 1.9.3"
17
+
16
18
  # s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
17
19
  s.files = `git ls-files`.split("\n").sort - %w(.rvmrc .gitignore)
18
20
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -28,7 +30,7 @@ Gem::Specification.new do |s|
28
30
  s.add_dependency "jquery-rails"
29
31
  s.add_dependency 'bootstrap-sass', '~> 2.2'
30
32
  s.add_dependency 'bootstrap-datepicker-rails'
31
- s.add_dependency 'bootstrap-wysihtml5-rails'
33
+ s.add_dependency 'bootstrap-wysihtml5-rails', '~> 0.3.1.24'
32
34
  s.add_dependency 'kaminari-bootstrap', '~> 0.1.3'
33
35
  s.add_dependency 'font-awesome-sass-rails', '>= 3.0.0.1'
34
36
  s.add_dependency 'simple_form', '>= 3.0.0'
@@ -43,7 +45,7 @@ Gem::Specification.new do |s|
43
45
  s.add_dependency 'ransack', '>= 1.0.0'
44
46
  s.add_dependency 'turbolinks'
45
47
  s.add_dependency 'decorators'
46
- s.add_dependency 'jquery-ui-rails'
48
+ s.add_dependency 'jquery-ui-rails', '~> 5.0.0'
47
49
  s.add_dependency 'select2-rails'
48
50
 
49
51
  s.add_development_dependency "guard-minitest"
@@ -4,10 +4,7 @@ Cardboard::Engine.routes.draw do
4
4
  get "my_account", to: "my_account#edit"
5
5
  patch "my_account", to: "my_account#update"
6
6
 
7
- get "pages/new", to: "pages#new"
8
7
  post "pages/sort", to: "pages#sort"
9
- get "pages/:id", to: "pages#edit"
10
-
11
8
  resources :pages
12
9
 
13
10
  get "/yoda", to: "super_user#index"
@@ -15,10 +12,8 @@ Cardboard::Engine.routes.draw do
15
12
  get "/settings", to: "settings#index"
16
13
  patch "/settings/update", to: "settings#update", as: "setting"
17
14
 
18
- get "/", to: "dashboard#index", as: "dashboard"
19
- #Don't put a root path here, use "/" instead... (to be able to use root_path in the pages)
15
+ get "/", to: "dashboard#index", as: "dashboard" #Don't put a root path here
20
16
 
21
-
22
17
  scope as: 'cardboard' do
23
18
  #generate routes for custom cardboard resources controllers
24
19
  Cardboard.resource_controllers.each do |controller|
@@ -36,15 +31,9 @@ Cardboard::Engine.routes.draw do
36
31
  end
37
32
 
38
33
  # Routes for public pages
39
- Rails.application.routes.draw do
40
- scope :constraints => { :format => 'html' } do #:format => true,
41
- get "*id", to: "pages#show"
42
- end
43
-
44
- root :to => "pages#show" unless @set.named_routes.routes[:root] #has_named_route?
45
- end
46
-
34
+ Cardboard::DynamicRouter.load
47
35
 
36
+ #legacy support
48
37
  Rails.application.routes.named_routes.module.module_eval do
49
38
  def page_path(identifier, options = {})
50
39
  url = Cardboard::Page.where(identifier: identifier.to_s).first.try(:url)
@@ -27,9 +27,6 @@ class CreateCardboard < ActiveRecord::Migration
27
27
  #Pages
28
28
  create_table :cardboard_pages do |t|
29
29
  t.string :title
30
- t.string :path
31
- t.string :slug
32
- t.text :slugs_backup
33
30
  t.integer :position
34
31
  t.text :meta_seo
35
32
  t.boolean :in_menu, default: true
@@ -38,7 +35,6 @@ class CreateCardboard < ActiveRecord::Migration
38
35
 
39
36
  t.timestamps
40
37
  end
41
- add_index :cardboard_pages, [:path, :slug], :unique => true
42
38
  add_index :cardboard_pages, :identifier, :unique => true
43
39
 
44
40
  #Settings
@@ -57,7 +53,20 @@ class CreateCardboard < ActiveRecord::Migration
57
53
  t.text :fields
58
54
  t.string :identifier
59
55
  t.boolean :is_page
56
+ t.string :controller_action
57
+ t.timestamps
58
+ end
59
+ add_index :cardboard_templates, :identifier, :unique => true
60
+
61
+ create_table :cardboard_urls do |t|
62
+ t.string :slug, index: true
63
+ t.string :path, index: true
64
+ t.text :slugs_backup
65
+ t.text :meta_tags
66
+ t.references :urlable, polymorphic: true
67
+
60
68
  t.timestamps
61
69
  end
70
+ add_index :cardboard_urls, [:path, :slug], :unique => true
62
71
  end
63
72
  end
@@ -0,0 +1,29 @@
1
+ require 'active_support/concern'
2
+
3
+ module Cardboard
4
+ module UrlConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_one :url_object, class_name: "Cardboard::Url", :as => :urlable, :autosave => true, :dependent => :destroy
9
+
10
+ def url_object_with_auto_build
11
+ build_url_object unless url_object_without_auto_build
12
+ url_object_without_auto_build #to continue the association chain
13
+ end
14
+ alias_method_chain :url_object, :auto_build
15
+
16
+ accepts_nested_attributes_for :url_object
17
+
18
+ delegate :slug, :slug=,
19
+ :path, :path=,
20
+ :meta_tags, :meta_tags=,
21
+ :using_slug_backup?,
22
+ to: :url_object, allow_nil: true
23
+ end
24
+
25
+ def url
26
+ url_object.to_s
27
+ end
28
+ end
29
+ end