ecrire 0.22.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +5 -0
  4. data/Gemfile +19 -0
  5. data/README.md +2 -0
  6. data/Rakefile +1 -1
  7. data/lib/ecrire/app/assets/javascripts/admin/editor/content.coffee +46 -76
  8. data/lib/ecrire/app/assets/javascripts/admin/editor/ext.coffee +3 -0
  9. data/lib/ecrire/app/assets/javascripts/admin/editor/extensions/clipboard.coffee +2 -1
  10. data/lib/ecrire/app/assets/javascripts/admin/editor/extensions/code.coffee +1 -2
  11. data/lib/ecrire/app/assets/javascripts/admin/editor/extensions/image.coffee +73 -1
  12. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/image.coffee +6 -115
  13. data/lib/ecrire/app/assets/javascripts/admin/editor.coffee +1 -0
  14. data/lib/ecrire/app/assets/javascripts/admin/{editor/navigation → navigation}/delete.js.coffee +1 -2
  15. data/lib/ecrire/app/assets/javascripts/admin/{editor/navigation → navigation}/draft.js.coffee +1 -3
  16. data/lib/ecrire/app/assets/javascripts/admin/{editor/navigation → navigation}/help.js.coffee +1 -1
  17. data/lib/ecrire/app/assets/javascripts/admin/{editor/navigation → navigation}/save.js.coffee +2 -2
  18. data/lib/ecrire/app/assets/javascripts/admin/{editor/navigation → navigation}/sticky.js.coffee +1 -2
  19. data/lib/ecrire/app/assets/javascripts/admin/{editor/navigation → navigation}/toggle.js.coffee +1 -2
  20. data/lib/ecrire/app/assets/javascripts/admin/posts/header.coffee +96 -40
  21. data/lib/ecrire/app/assets/javascripts/admin/posts/new.js.coffee +1 -2
  22. data/lib/ecrire/app/assets/javascripts/admin/posts/tag.js.coffee +7 -0
  23. data/lib/ecrire/app/assets/javascripts/admin/posts/tags.js.coffee +15 -0
  24. data/lib/ecrire/app/assets/javascripts/admin/posts/title.coffee +38 -9
  25. data/lib/ecrire/app/assets/javascripts/admin/tags/list.js.coffee +34 -0
  26. data/lib/ecrire/app/assets/javascripts/admin.js +1 -1
  27. data/lib/ecrire/app/assets/javascripts/application.js +2 -3
  28. data/lib/ecrire/app/assets/javascripts/shared/overlay.js.coffee +1 -2
  29. data/lib/ecrire/app/assets/javascripts/shared/popup.js.coffee +1 -2
  30. data/lib/ecrire/app/assets/stylesheets/admin/base.css.scss +1 -2
  31. data/lib/ecrire/app/assets/stylesheets/admin/editor/base.css.scss +0 -7
  32. data/lib/ecrire/app/assets/stylesheets/admin/editor/content.css.scss +30 -12
  33. data/lib/ecrire/app/assets/stylesheets/admin/editor/header.css.scss +154 -32
  34. data/lib/ecrire/app/assets/stylesheets/admin/posts.css.scss +8 -1
  35. data/lib/ecrire/app/assets/stylesheets/admin/tags.css.scss +78 -0
  36. data/lib/ecrire/app/assets/stylesheets/admin/title.css.scss +51 -0
  37. data/lib/ecrire/app/assets/stylesheets/admin/variables.css.scss +3 -0
  38. data/lib/ecrire/app/assets/stylesheets/shared/popup.css.scss +2 -0
  39. data/lib/ecrire/app/controllers/admin/application_controller.rb +6 -0
  40. data/lib/ecrire/app/controllers/admin/images_controller.rb +5 -6
  41. data/lib/ecrire/app/controllers/admin/posts/tags_controller.rb +35 -0
  42. data/lib/ecrire/app/controllers/admin/posts_controller.rb +13 -12
  43. data/lib/ecrire/app/controllers/admin/tags_controller.rb +4 -0
  44. data/lib/ecrire/app/controllers/admin/titles_controller.rb +39 -0
  45. data/lib/ecrire/app/controllers/application_controller.rb +13 -3
  46. data/lib/ecrire/app/controllers/ecrire/posts_controller.rb +6 -5
  47. data/lib/ecrire/app/helpers/posts_helper.rb +0 -12
  48. data/lib/ecrire/app/models/admin/image.rb +4 -7
  49. data/lib/ecrire/app/models/admin/post.rb +2 -7
  50. data/lib/ecrire/app/models/admin/tag.rb +4 -0
  51. data/lib/ecrire/app/models/admin/title.rb +4 -0
  52. data/lib/ecrire/app/models/post.rb +30 -22
  53. data/lib/ecrire/app/models/tag.rb +5 -0
  54. data/lib/ecrire/app/models/title.rb +62 -0
  55. data/lib/ecrire/app/views/admin/images/errors.json.jbuilder +1 -0
  56. data/lib/ecrire/app/views/admin/posts/edit.html.erb +61 -21
  57. data/lib/ecrire/app/views/admin/posts/header/_dropping.html.erb +12 -0
  58. data/lib/ecrire/app/views/admin/posts/header/_error.html.erb +18 -0
  59. data/lib/ecrire/app/views/admin/posts/header/_uploading.html.erb +21 -0
  60. data/lib/ecrire/app/views/admin/posts/new.html.erb +23 -0
  61. data/lib/ecrire/app/views/admin/posts/tags/_tag.html.erb +10 -0
  62. data/lib/ecrire/app/views/admin/posts/tags/create.js.erb +1 -0
  63. data/lib/ecrire/app/views/admin/posts/tags/dialog/_tag.html.erb +3 -0
  64. data/lib/ecrire/app/views/admin/posts/tags/dialog/_tags.html.erb +12 -0
  65. data/lib/ecrire/app/views/admin/posts/tags/index.js.erb +1 -0
  66. data/lib/ecrire/app/views/admin/posts/tags/update.js.erb +2 -0
  67. data/lib/ecrire/app/views/admin/titles/_title.html.erb +11 -0
  68. data/lib/ecrire/app/views/admin/titles/_titles.html.erb +22 -0
  69. data/lib/ecrire/app/views/admin/titles/create.js.erb +1 -0
  70. data/lib/ecrire/app/views/admin/titles/edit.js.erb +1 -0
  71. data/lib/ecrire/app/views/admin/titles/errors.js.erb +5 -0
  72. data/lib/ecrire/app/views/admin/titles/index.js.erb +1 -0
  73. data/lib/ecrire/app/views/admin/titles/update.js.erb +1 -0
  74. data/lib/ecrire/app/views/layouts/admin/application.html.erb +1 -1
  75. data/lib/ecrire/app/views/sessions/_navigation.html.erb +1 -1
  76. data/lib/ecrire/application.rb +1 -1
  77. data/lib/ecrire/config/routes.rb +2 -2
  78. data/lib/ecrire/db/migrate/20150305123321_move_labels_to_titles.rb +27 -0
  79. data/lib/ecrire/db/migrate/20150318549479_create_tags.rb +13 -0
  80. data/lib/ecrire/db/schema.rb +25 -22
  81. data/lib/ecrire/onboarding/assets/javascripts/base.js +1 -1
  82. data/lib/ecrire/onboarding/assets/javascripts/databases/information.js.coffee +1 -1
  83. data/lib/ecrire/onboarding/assets/javascripts/message.js.coffee +1 -1
  84. data/lib/ecrire/template/Gemfile +4 -3
  85. data/lib/ecrire/template/assets/javascripts/base.js.coffee +1 -0
  86. data/lib/ecrire/version.rb +1 -1
  87. data/test/editor/controllers/base_controller_test.rb +1 -0
  88. data/test/editor/models/post_test.rb +26 -56
  89. data/test/editor/models/title_test.rb +31 -0
  90. data/test/editor/test_helper.rb +2 -0
  91. data/test/fixtures/posts.yml +0 -4
  92. data/test/fixtures/tags.yml +2 -0
  93. data/test/fixtures/titles.yml +11 -0
  94. data/test/initializations/railtie_test.rb +2 -2
  95. data/test/themes/template/secrets.yml +6 -0
  96. metadata +49 -24
  97. data/lib/ecrire/app/assets/stylesheets/admin/editor/image.css.scss +0 -58
  98. data/lib/ecrire/app/controllers/admin/labels_controller.rb +0 -18
  99. data/lib/ecrire/app/models/label.rb +0 -3
  100. data/lib/ecrire/app/views/admin/images/errors.js.erb +0 -1
  101. data/lib/ecrire/app/views/admin/posts/title.html.erb +0 -10
  102. data/test/editor/controllers/admin/images_controller_test.rb +0 -6
  103. data/test/editor/controllers/admin/posts_controller_test.rb +0 -90
  104. data/test/editor/controllers/images_controller_test.rb +0 -2
  105. data/test/editor/controllers/partials_controller_test.rb +0 -2
  106. data/test/editor/controllers/posts_controller_test.rb +0 -21
  107. data/test/editor/helpers/posts_helper_test.rb +0 -3
  108. data/test/editor/models/partial_test.rb +0 -4
  109. data/test/fixtures/partials.yml +0 -9
  110. /data/lib/ecrire/app/views/admin/images/{update.js.erb → create.js.erb} +0 -0
  111. /data/{lib/ecrire/template/assets/javascripts/post.js.coffee → test/themes/onboarding/.keep} +0 -0
@@ -0,0 +1,78 @@
1
+ #TagsList {
2
+ @include display(flex);
3
+ @include align-items(center);
4
+ margin: 1em 0;
5
+ min-height: 5em;
6
+
7
+ svg {
8
+ height: 20px;
9
+ fill: #AAA;
10
+ }
11
+
12
+ ul.tags {
13
+ @include flex(1 auto);
14
+ @include display(flex);
15
+ @include flex-wrap(wrap);
16
+ @include align-items(center);
17
+ margin: 0 1em;
18
+ outline: none;
19
+
20
+ [as='Post.Tag'] {
21
+ @include display(flex);
22
+ @include flex(0 auto);
23
+ @include align-items(center);
24
+ @include transition(color 0.3s, border-color 0.3s, box-shadow 0.3s, opacity 0.2s);
25
+ @include user-select(none);
26
+
27
+ margin: 1em;
28
+ padding: 4px 8px;
29
+ border-radius: 2px;
30
+ border: 1px solid #C7D4DB;
31
+ box-shadow: inset 0 2px 4px 0 #F5F8FA;
32
+ cursor: pointer;
33
+
34
+ svg {
35
+ @include transition(fill 0.3s);
36
+ margin-left: 6px;
37
+ fill: rgb(219, 196, 196);
38
+ }
39
+
40
+ &:hover svg {
41
+ fill: #AE4D4D;
42
+ }
43
+
44
+ &:active {
45
+ border-color: #B37878;
46
+ box-shadow: inset 0 0px 4px 0 #DBC5C5;
47
+ color: #B37878;
48
+ }
49
+
50
+ &.remove {
51
+ opacity: 0;
52
+ }
53
+ }
54
+
55
+ }
56
+ }
57
+
58
+ [as='Tags.List'] {
59
+ h2, h3 {
60
+ text-align: center;
61
+ }
62
+
63
+ input {
64
+ font-size: 1.5em;
65
+ }
66
+ li {
67
+ cursor: pointer;
68
+ margin: 0;
69
+ padding: 1em;
70
+ font-size: 1.3em;
71
+ &:not(:last-child) {
72
+ border-bottom: 1px solid #C8D9E2;
73
+ }
74
+ }
75
+ li.tagged {
76
+ border-left: 4px solid #AAA;
77
+ }
78
+ }
@@ -0,0 +1,51 @@
1
+ [as='Post.Title'] {
2
+ h3 {
3
+ padding: 0.2em;
4
+ text-align: center;
5
+ }
6
+
7
+ div.input {
8
+ outline: none;
9
+ background: #FFF;
10
+ font-size: 1.6em;
11
+ line-height: 1em;
12
+ padding: 0.3em 0.2em;
13
+ text-align: center;
14
+ box-shadow: inset 0 0 2px #375874;
15
+ }
16
+ & > p {
17
+ text-align: center;
18
+ font-size: 0.8em;
19
+ text-shadow: 0 1px 0 #E7EFF5;
20
+ }
21
+
22
+ ul.titles {
23
+ border-top: 1px solid #B2BCC3;
24
+
25
+ h4 {
26
+ text-align: center;
27
+ margin: 0.5em 0 0 0;
28
+ }
29
+
30
+ li {
31
+ margin: 0;
32
+ padding: 1em;
33
+ p.title {
34
+ font-size: 1.2em;
35
+ margin: 0;
36
+ }
37
+
38
+ p.url {
39
+ font-size: 0.8em;
40
+ }
41
+ }
42
+ li:nth-child(odd) {
43
+ background: #D1DAE0;
44
+ }
45
+ }
46
+
47
+ ul.errors {
48
+ text-align: center;
49
+ color: $red-color;
50
+ }
51
+ }
@@ -1,7 +1,10 @@
1
1
  $fonts: Helvetica, sans-serif;
2
2
 
3
3
  $text-color: #15191B;
4
+ $link-color: rgb(114, 153, 204);
4
5
  $shadow-color: #374C64;
6
+ $dark-red-color: #2F0B0B;
7
+ $red-color: rgb(169, 40, 40);
5
8
 
6
9
  $warning: #F0BC6F;
7
10
  $dropping-color: #EDFFED;
@@ -26,6 +26,7 @@ div.popup {
26
26
  box-shadow: 0 0 3px #1a3642;
27
27
  color: #15191B;
28
28
  z-index: 101;
29
+ overflow: hidden;
29
30
 
30
31
  h1, h2, h3, h4, h5, h6 {
31
32
  font-family: initial;
@@ -67,6 +68,7 @@ div.popup.errors {
67
68
 
68
69
  #PostsListPopup {
69
70
  ol {
71
+ list-style-type: none;
70
72
  font-size: 1.2em;
71
73
  padding: 0;
72
74
  li {
@@ -5,6 +5,12 @@ module Admin
5
5
  before_action :authenticate!
6
6
  before_action :pagination, only: [:index]
7
7
 
8
+ helper_method :posts
9
+
10
+ def posts
11
+ @posts ||= Post.all
12
+ end
13
+
8
14
  protected
9
15
 
10
16
  def pagination
@@ -7,12 +7,11 @@ module Admin
7
7
  end
8
8
  end
9
9
 
10
- def update
11
- @image = post.header
12
- if @image.update(image_params)
13
- @image.touch
14
- else
15
- render 'errors'
10
+ def create
11
+ @image = post.header || post.build_header
12
+
13
+ unless @image.update(image_params)
14
+ render 'errors', status: 500
16
15
  end
17
16
  end
18
17
 
@@ -0,0 +1,35 @@
1
+ module Admin
2
+ module Posts
3
+ class TagsController < Admin::ApplicationController
4
+ def index
5
+ @post = Admin::Post.find(params[:post_id])
6
+ @tags = Admin::Tag.where.not(id: @post.tags.map(&:id))
7
+ end
8
+
9
+ def update
10
+ @post = Admin::Post.find(params[:post_id])
11
+ @tag = Admin::Tag.find(params[:id])
12
+ if @post.tags.include? @tag
13
+ @post.tags = @post.tags.where.not(id: @tag.id)
14
+ else
15
+ @post.tags << @tag
16
+ end
17
+
18
+ @post.save!
19
+ end
20
+
21
+ def create
22
+ @post = Admin::Post.find(params[:post_id])
23
+ @tag = Admin::Tag.find_or_create_by!(tag_params)
24
+ end
25
+
26
+ protected
27
+
28
+ def tag_params
29
+ params.require(:tag).permit('name')
30
+ end
31
+
32
+
33
+ end
34
+ end
35
+ end
@@ -3,9 +3,7 @@ module Admin
3
3
  before_action :fetch_post, only: [:show, :edit, :update]
4
4
 
5
5
  def new
6
- if /new\/title/i =~ request.path
7
- render 'title' and return
8
- end
6
+ @post = Admin::Post.new
9
7
  end
10
8
 
11
9
  def index
@@ -18,15 +16,13 @@ module Admin
18
16
  end
19
17
 
20
18
  def create
21
- @post = Admin::Post.create post_params
19
+ @post = Admin::Post.create(title: title_params[:title])
22
20
 
23
- respond_to do |format|
24
- format.html do
25
- if @post.errors.blank?
26
- redirect_to edit_admin_post_path(@post.id)
27
- end
28
- end
21
+ if @post.errors.any?
22
+ render 'new' and return
29
23
  end
24
+
25
+ redirect_to edit_admin_post_path(@post)
30
26
  end
31
27
 
32
28
  def destroy
@@ -51,7 +47,7 @@ module Admin
51
47
  when 'publish'
52
48
  @post.publish!
53
49
  flash[:notice] = t(".successful", title: @post.title)
54
- redirect_to post_path(@post.published_at.year, @post.published_at.month, @post.slug) and return
50
+ redirect_to post_path(@post) and return
55
51
  when 'unpublish'
56
52
  @post.unpublish!
57
53
  end
@@ -71,9 +67,14 @@ module Admin
71
67
  protected
72
68
 
73
69
  def post_params
74
- params.require(:post).permit(:title, :content, :status, :stylesheet, :javascript, :slug)
70
+ params.require(:post).permit(:content, :status, :stylesheet, :javascript, :slug)
71
+ end
72
+
73
+ def title_params
74
+ params.require(:post).permit(:title)
75
75
  end
76
76
 
77
+
77
78
  def fetch_post
78
79
  @post = Admin::Post.find(params[:id])
79
80
  end
@@ -0,0 +1,4 @@
1
+ module Admin
2
+ class TagsController < Admin::ApplicationController
3
+ end
4
+ end
@@ -0,0 +1,39 @@
1
+ module Admin
2
+ class TitlesController < Admin::ApplicationController
3
+ helper_method :post
4
+
5
+ def index
6
+ if post.draft?
7
+ render 'edit' and return
8
+ end
9
+ end
10
+
11
+ def create
12
+ @title = Admin::Title.new(title_params) do |title|
13
+ title.post = post
14
+ end
15
+
16
+ unless @title.save
17
+ render 'errors'
18
+ end
19
+ end
20
+
21
+ def update
22
+ @title = Admin::Title.find(params[:id])
23
+ unless @title.update(title_params)
24
+ render 'errors'
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ def title_params
31
+ params.require(:title).permit(:name)
32
+ end
33
+
34
+ def post
35
+ @post ||= Post.find(params[:post_id])
36
+ end
37
+
38
+ end
39
+ end
@@ -3,7 +3,7 @@ class ApplicationController < ::ActionController::Base
3
3
  protect_from_forgery with: :exception
4
4
  helper_method :current_user, :signed_in?
5
5
 
6
- helper_method :warden, :posts
6
+ helper_method :warden, :post_path
7
7
 
8
8
  def current_user
9
9
  warden.user
@@ -13,10 +13,20 @@ class ApplicationController < ::ActionController::Base
13
13
  !warden.user.nil?
14
14
  end
15
15
 
16
- def posts
17
- @posts ||= Post.all
16
+ # This is the less ugly hack I could find. The reason why this is here
17
+ # is because helpers are hard to debug due to their anonymous Module use.
18
+ def post_path(post, options = {})
19
+ options[:controller] = :posts
20
+ options[:action] = :show
21
+ options[:year] = post.published_at.year
22
+ options[:month] = post.published_at.month
23
+
24
+ options[:id] = (options.delete(:title) || post.titles.first).slug
25
+
26
+ url_for options
18
27
  end
19
28
 
29
+
20
30
  protected
21
31
 
22
32
  def authenticate!
@@ -1,13 +1,10 @@
1
1
  module Ecrire
2
2
  class PostsController < ::ApplicationController
3
- # TODO: This should be included. I have no idea why this isn't.
4
- # It actually is loaded in dev as soon as the reloader reloads the files
5
- include Rails.application.routes.url_helpers
6
3
 
7
4
  before_action :pagination, only: :index
8
5
  protect_from_forgery except: :index
9
6
 
10
- helper_method :post
7
+ helper_method :post, :posts
11
8
 
12
9
  def index
13
10
  respond_to do |format|
@@ -22,6 +19,9 @@ module Ecrire
22
19
  def show
23
20
  redirect_to :root and return if post.nil?
24
21
  redirect_to :root and return unless post.published?
22
+ if post.titles.first != @title
23
+ redirect_to post_path(post), status: :moved_permanently
24
+ end
25
25
  end
26
26
 
27
27
  protected
@@ -31,7 +31,8 @@ module Ecrire
31
31
  end
32
32
 
33
33
  def post
34
- @post ||= Post.find_by_slug(params[:id])
34
+ @title ||= Title.find_by_slug(params[:id])
35
+ @post ||= @title.post
35
36
  end
36
37
 
37
38
  def pagination
@@ -1,16 +1,4 @@
1
1
  module PostsHelper
2
- def post_path(post, options = {})
3
- if post.published?
4
- options[:year] = post.published_at.year
5
- options[:month] = post.published_at.month
6
- else
7
- options[:year] = post.created_at.year
8
- options[:month] = post.created_at.month
9
- end
10
-
11
- super(post.becomes(::Post), options)
12
- end
13
-
14
2
  def edit_post_link(options = {})
15
3
  return unless signed_in?
16
4
 
@@ -34,18 +34,15 @@ module Admin
34
34
  self.url = @file.url
35
35
  self.key = @file.key
36
36
  rescue StandardError => e
37
- errors.add :remote, e
37
+ errors.add 's3', "Couldn't upload file to S3"
38
38
  return false
39
39
  end
40
40
  return true
41
41
  end
42
42
  end
43
43
 
44
- def remove_file
45
- end
46
-
47
44
  def path(file)
48
- items = [post.id, file.original_filename]
45
+ items = [self.post.id, file.original_filename]
49
46
 
50
47
  items.prepend(s3.path) unless s3.path.blank?
51
48
 
@@ -56,7 +53,7 @@ module Admin
56
53
  include ::S3
57
54
 
58
55
  attr_reader :bucket, :access_key, :secret_key, :path, :errors
59
-
56
+
60
57
  def initialize(options={})
61
58
  @errors = ActiveModel::Errors.new(self)
62
59
  @access_key = options.fetch('access_key', "")
@@ -85,7 +82,7 @@ module Admin
85
82
  @bucket.retrieve
86
83
  @connected = true
87
84
  rescue Error::ResponseError, ArgumentError, SocketError => e
88
- errors.add :remote, e
85
+ errors.add :remote, "Couldn't connect to S3."
89
86
  end
90
87
  end
91
88
 
@@ -3,16 +3,11 @@ require 'kramdown'
3
3
  module Admin
4
4
  class Post < ::Post
5
5
 
6
- class Callbacks
7
- def after_create(record)
8
- Admin::Image.create(post: record)
9
- end
10
- end
11
-
12
6
  has_one :header, class_name: Admin::Image
7
+ has_many :titles, -> { order "titles.created_at DESC" }, class_name: Admin::Title
8
+
13
9
  before_save :compile!, prepend: true
14
10
  before_save :excerptize!
15
- after_create Callbacks.new
16
11
 
17
12
  def publish!(params = {})
18
13
  self.assign_attributes(params)
@@ -0,0 +1,4 @@
1
+ module Admin
2
+ class Tag < ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Admin
2
+ class Title < ::Title
3
+ end
4
+ end
@@ -2,6 +2,7 @@ require 'nokogiri'
2
2
 
3
3
  class Post < ActiveRecord::Base
4
4
  has_one :header, class_name: Image
5
+ has_many :titles, -> { order "titles.created_at DESC" }
5
6
 
6
7
  store_accessor :properties, :labels
7
8
 
@@ -15,13 +16,31 @@ class Post < ActiveRecord::Base
15
16
 
16
17
  scope :published, lambda { status("published") }
17
18
  scope :drafted, lambda { status("drafted") }
18
- scope :slug, lambda { |slug| where("posts.slug is ?", slug) }
19
19
  scope :without, lambda { |post| where("posts.id != ?", post.id) }
20
20
 
21
- validates :title, presence: true, uniqueness: true
22
- validates :slug, presence: true, uniqueness: true
21
+ validates :titles, length: {minimum: 1}
23
22
 
24
- before_validation :create_slug_if_nil
23
+ before_save :update_tags
24
+
25
+ attr_writer :tags
26
+
27
+ def title=(new_title)
28
+ if self.published?
29
+ self.titles.new(name: new_title)
30
+ else
31
+ title = self.titles.first || self.titles.new
32
+ title.post = self
33
+ title.name = new_title
34
+ end
35
+ end
36
+
37
+ def title
38
+ (self.titles.first || self.titles.new).name
39
+ end
40
+
41
+ def slug
42
+ self.titles.first.slug
43
+ end
25
44
 
26
45
  def status=(new_status)
27
46
  if new_status.eql? "publish"
@@ -59,32 +78,21 @@ class Post < ActiveRecord::Base
59
78
  (self.compiled_excerpt || "").html_safe
60
79
  end
61
80
 
62
-
63
- def title=(new_title)
64
- super
65
- if self.draft?
66
- self.slug = nil
67
- end
68
- end
69
-
70
81
  def header?
71
82
  !self.header.nil? && !self.header.url.blank?
72
83
  end
73
84
 
74
- def labels
75
- ids = super || ''
76
- Label.where(id: ids.split(',')).to_a
77
- end
78
-
79
- def labels=(labels)
80
- super(labels.map(&:id).join(','))
85
+ def tags
86
+ @tags ||= Tag.where("tags.id in (?)", super || [])
81
87
  end
82
88
 
83
89
  protected
84
90
 
85
- def create_slug_if_nil
86
- return unless self.slug.blank?
87
- self.slug = self.title.parameterize
91
+ def update_tags
92
+ ids = tags.map(&:id)
93
+ unless ids == read_attribute(:tags)
94
+ write_attribute(:tags, ids)
95
+ end
88
96
  end
89
97
 
90
98
  end
@@ -0,0 +1,5 @@
1
+ class Tag < ActiveRecord::Base
2
+ def ==(other)
3
+ self.class.table_name == other.class.table_name && self.id == other.id
4
+ end
5
+ end
@@ -0,0 +1,62 @@
1
+ class Title < ActiveRecord::Base
2
+ class Validator < ActiveModel::Validator
3
+ def validate(record)
4
+ validate_length! record
5
+ validate_uniqueness! record
6
+ validate_draft! record
7
+ end
8
+
9
+ def validate_length!(record)
10
+ if record.name.blank?
11
+ msg = "Your title can't be blank."
12
+ record.errors['name'] << msg
13
+ record.post.errors['title'] << msg
14
+ elsif record.name.length < 1
15
+ msg = "Your title needs to be at least 1 character long."
16
+ record.errors['name'] << msg
17
+ record.post.errors['title'] << msg
18
+ end
19
+ end
20
+
21
+ def validate_uniqueness!(record)
22
+ title = Title.where('titles.slug = ? OR titles.name = ?', record.slug, record.name).first
23
+ unless title.nil?
24
+ msg = "You already have a post with this title: #{title.name}"
25
+ record.errors['uniqueness'] << msg
26
+ record.post.errors['title'] << msg
27
+ return
28
+ end
29
+ end
30
+
31
+ def validate_draft!(record)
32
+ if record.post.published? && !record.new_record?
33
+ msg = "You cannot modify an existing title when a post is published"
34
+ record.errors['draft'] << msg
35
+ record.post.errors['title'] << msg
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ include ActiveModel::Validations
42
+ validates_with Title::Validator
43
+
44
+ before_validation :generate_slug
45
+
46
+ belongs_to :post, required: true
47
+
48
+ scope :slug, lambda { |slug|
49
+ where('titles.slug = ?', slug)
50
+ }
51
+
52
+ def name=(new_name)
53
+ super new_name.strip
54
+ end
55
+
56
+ protected
57
+
58
+ def generate_slug
59
+ self.slug = self.name.parameterize.downcase
60
+ end
61
+
62
+ end
@@ -0,0 +1 @@
1
+ json.array! @image.errors.values