Class: Mist::Post

Inherits:
GitModel show all
Includes:
Permalink
Defined in:
app/models/mist/post.rb

Constant Summary collapse

TAG_DELIM =
/\s*,\s*/

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Permalink

#permalink

Methods inherited from GitModel

#==, #attributes, #class_name, #commit, #commit_message, #default_attributes!, #destroy, #initialize, #inspect, #new_record?, #path, #path_was, #persisted?, #save, #save!, #save_record_file, #update_attributes

Methods included from GitModel::ClassMethods

#[], #add_attribute_default, #add_attribute_getter, #add_attribute_setter, #all, #attribute, #count, #create!, #exist?, extended, #find, #inherited, #last, #load, #meta_file_path, #record_path, #save_meta_data, #table_name, #timestamps

Constructor Details

This class inherits a constructor from Mist::GitModel

Class Method Details

.all_by_publication_dateObject



53
54
55
56
# File 'app/models/mist/post.rb', line 53

def self.all_by_publication_date
  # invert <=> so that result is descending order
  load_existing_with_attribute :published_at, self[:published_at].sort { |a, b| -(a[1] <=> b[1]) }
end

.increase_popularity(post) ⇒ Object



37
38
39
40
41
# File 'app/models/mist/post.rb', line 37

def self.increase_popularity(post)
  self[:popular_posts][post.id] = popularity_for(post.id) + 1
   :popular_posts
  post.popularity = self[:popular_posts][post.id]
end

.load_existing_with_attribute(attribute_name, array) ⇒ Object



28
29
30
# File 'app/models/mist/post.rb', line 28

def self.load_existing_with_attribute(attribute_name, array)
  array.collect { |(post_id, attribute_value)| find post_id, attribute_name => attribute_value }.reject { |i| i.nil? }
end

.matching_tags(tags) ⇒ Object



58
59
60
61
62
# File 'app/models/mist/post.rb', line 58

def self.matching_tags(tags)
  return [] if tags.blank?
  matches = self[:tags].inject({}) { |h,(k,v)| ((t = v.split(TAG_DELIM)) & tags).size > 0 ? h[k] = t : nil; h }
  load_existing_with_attribute :tags, matches.sort { |a, b| -((a[1] & tags).size <=> (b[1] & tags).size) }
end


32
33
34
35
# File 'app/models/mist/post.rb', line 32

def self.most_popular(count)
  # invert <=> so that result is descending order
  load_existing_with_attribute :popularity, self[:popular_posts].sort { |a, b| -(a[1].to_i <=> b[1].to_i) }
end

.popularity_for(post_id) ⇒ Object



43
44
45
# File 'app/models/mist/post.rb', line 43

def self.popularity_for(post_id)
  self[:popular_posts][post_id] || 0
end

.recently_published(count) ⇒ Object



47
48
49
50
51
# File 'app/models/mist/post.rb', line 47

def self.recently_published(count)
  all_by_publication_date.tap do |result|
    result.pop while result.length > count
  end
end

Instance Method Details

#code_examplesObject



187
188
189
# File 'app/models/mist/post.rb', line 187

def code_examples
  Mist::CodeExampleParser.new(content).examples
end

#content=(c) ⇒ Object



128
129
130
# File 'app/models/mist/post.rb', line 128

def content=(c)
  attributes[:content] = c.gsub(/\r/, "")
end

#content_as_htmlObject



155
156
157
# File 'app/models/mist/post.rb', line 155

def content_as_html
  GitHub::Markup.render("#{title}.markdown", content_with_embedded_gists).html_safe
end

#content_as_html_previewObject



159
160
161
162
163
164
# File 'app/models/mist/post.rb', line 159

def content_as_html_preview
  # just take to the first blank line -- that's probably the first paragraph
  # TODO make this smarter by including more than 1 paragraph if it's short, or by omitting headers
  first_paragraph = /\A(.+?)(\n\n|\n    |\z)/m.match(content.gsub(/\r/, ''))
  GitHub::Markup.render("#{title}.markdown", first_paragraph[1]).html_safe
end

#content_with_embedded_gistsObject



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'app/models/mist/post.rb', line 166

def content_with_embedded_gists
  # by using gist_id directly we can avoid hitting the Gist API every time the
  # post is rendered.
  
  return content.dup if gist_id.blank?
  
  template = '<script src="https://gist.github.com/__ID__.js?file=__FILENAME__"></script>'
  template['__ID__'] = gist_id.to_s
  
  content.dup.tap do |result|
    # process last example first, so that changes to result don't taint offsets
    code_examples.reverse.each do |example|
      result[example.offset] = template.sub(/__FILENAME__/, example.filename) + "\n"
    end
  end
end

#destroy_gistObject



237
238
239
# File 'app/models/mist/post.rb', line 237

def destroy_gist
  gist.destroy if gist && gist.persisted?
end

#draft?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'app/models/mist/post.rb', line 112

def draft?
  !published?
end

#generated_gist_descriptionObject



132
133
134
# File 'app/models/mist/post.rb', line 132

def generated_gist_description
  'Code examples for "%s" - %s' % [title, url]
end

#gistObject



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/models/mist/post.rb', line 136

def gist
  @gist ||= begin
    if gist_id.blank?
      if has_code_examples?
        ActiveGist.new(:description => generated_gist_description, :public => true)
      else
        nil
      end
    else
      begin
        ActiveGist.find(gist_id)
      rescue RestClient::ResourceNotFound
        self.gist_id = nil
        nil
      end
    end
  end
end

#has_code_examples?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'app/models/mist/post.rb', line 183

def has_code_examples?
  code_examples.length > 0
end

#load_code_examples_from_gistObject



191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/models/mist/post.rb', line 191

def load_code_examples_from_gist
  if gist && gist.persisted?
    self.content = self.content.dup.tap do |result|
      code_examples.reverse.each do |example|
        if gist.files[example.filename]
          lines = gist.files[example.filename][:content].split("\n")
          lines.unshift "    file: #{example.filename}"
          result[example.offset] = lines.join("\n    ") + "\n"
        end
      end
    end
  end
end

#publishObject



116
117
118
# File 'app/models/mist/post.rb', line 116

def publish
  self.published_at = Time.now unless published?
end

#published=(bool) ⇒ Object



124
125
126
# File 'app/models/mist/post.rb', line 124

def published=(bool)
  bool ? publish : unpublish
end

#published?Boolean

Returns:

  • (Boolean)


108
109
110
# File 'app/models/mist/post.rb', line 108

def published?
  !published_at.blank?
end

#set_gist_dataObject

Assigns the file contents of each file in the gist according to what’s found in #content. Does not save the gist. Returns the list of files themselves.



207
208
209
210
211
212
213
214
# File 'app/models/mist/post.rb', line 207

def set_gist_data
  gist.description = generated_gist_description
  gist.files.keys.each { |filename| gist.files[filename] = nil }
  code_examples.each do |example|
    gist.files[example.filename] = { :content => example }
  end
  gist.files
end

#similar_posts(max_count = nil) ⇒ Object



64
65
66
67
68
69
70
71
# File 'app/models/mist/post.rb', line 64

def similar_posts(max_count = nil)
  self.class.matching_tags(tags).tap do |matching|
    matching.delete self # similar does not mean identical :)
    while max_count && matching.length > max_count
      matching.pop
    end
  end
end

#tags=(t) ⇒ Object



73
74
75
76
77
78
79
# File 'app/models/mist/post.rb', line 73

def tags=(t)
  if t.kind_of?(String)
    attributes[:tags] = t.split(TAG_DELIM)
  else
    attributes[:tags] = t
  end
end

#title=(value) ⇒ Object



104
105
106
# File 'app/models/mist/post.rb', line 104

def title=(value)
  attributes[:title] = value
end

#unpublishObject



120
121
122
# File 'app/models/mist/post.rb', line 120

def unpublish
  self.published_at = nil
end

#update_gist_if_necessaryObject



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'app/models/mist/post.rb', line 220

def update_gist_if_necessary
  return unless errors.empty?
  
  if has_code_examples?
    set_gist_data
    
    if gist.changed?
      errors.add(:gist, "could not be saved: #{gist.errors.full_messages.join('; ')}") unless gist.save
    end

    self.gist_id = gist.id
  else
    # no code examples, delete gist
    gist.destroy if gist && gist.persisted?
  end
end

#update_metaObject



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'app/models/mist/post.rb', line 81

def update_meta
  if popularity_changed? || new_record?
    self.class[:popular_posts][id] = popularity
    self.class. :popular_posts
  end
  
  if published_at_changed?
    if published_at.blank?
      self.class[:published_at].delete id
    else
      self.class[:published_at][id] = published_at
    end
    self.class. :published_at
  end
  
  if tags && !tags.empty?
    self.class[:tags][id] = tags.join(', ')
  else
    self.class[:tags].delete id
  end
  self.class. :tags
end

#url(options = {}) ⇒ Object



216
217
218
# File 'app/models/mist/post.rb', line 216

def url(options = {})
  Mist::Engine.routes.url_helpers.post_path(id, options.reverse_merge(:only_path => false).reverse_merge(Rails.application.default_url_options))
end