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_date(include_unpublished = false) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'app/models/mist/post.rb', line 55

def all_by_publication_date(include_unpublished = false)
  publications = self[:published_at].sort do |(ka,va), (kb,vb)|
    if va.nil?
      vb.nil? ? 0 : -1
    else
      vb.nil? ? 1 : -(va <=> vb)
    end
  end

  unless include_unpublished
    publications = publications.select { |(post, publish_date)| !publish_date.blank? }
  end
  
  load_existing_with_attribute :published_at, publications
end

.increase_popularity(post) ⇒ Object



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

def 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



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

def 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



71
72
73
74
75
# File 'app/models/mist/post.rb', line 71

def 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


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

def 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



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

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

.recently_published(count, include_unpublished = false) ⇒ Object



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

def recently_published(count, include_unpublished = false)
  recent = all_by_publication_date(include_unpublished)
  recent.tap do |result|
    result.pop while result.length > count
  end
end

Instance Method Details

#code_examplesObject



197
198
199
# File 'app/models/mist/post.rb', line 197

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

#content=(c) ⇒ Object



138
139
140
# File 'app/models/mist/post.rb', line 138

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

#content_as_htmlObject



165
166
167
# File 'app/models/mist/post.rb', line 165

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

#content_as_html_previewObject



169
170
171
172
173
174
# File 'app/models/mist/post.rb', line 169

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



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/models/mist/post.rb', line 176

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



247
248
249
# File 'app/models/mist/post.rb', line 247

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

#draft?Boolean

Returns:

  • (Boolean)


122
123
124
# File 'app/models/mist/post.rb', line 122

def draft?
  !published?
end

#generated_gist_descriptionObject



142
143
144
# File 'app/models/mist/post.rb', line 142

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

#gistObject



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'app/models/mist/post.rb', line 146

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)


193
194
195
# File 'app/models/mist/post.rb', line 193

def has_code_examples?
  code_examples.length > 0
end

#load_code_examples_from_gistObject



201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'app/models/mist/post.rb', line 201

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



126
127
128
# File 'app/models/mist/post.rb', line 126

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

#published=(bool) ⇒ Object



134
135
136
# File 'app/models/mist/post.rb', line 134

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

#published?Boolean

Returns:

  • (Boolean)


118
119
120
# File 'app/models/mist/post.rb', line 118

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.



217
218
219
220
221
222
223
224
# File 'app/models/mist/post.rb', line 217

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



78
79
80
81
82
83
84
85
# File 'app/models/mist/post.rb', line 78

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



87
88
89
90
91
92
93
# File 'app/models/mist/post.rb', line 87

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

#title=(value) ⇒ Object



114
115
116
# File 'app/models/mist/post.rb', line 114

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

#unpublishObject



130
131
132
# File 'app/models/mist/post.rb', line 130

def unpublish
  self.published_at = nil
end

#update_gist_if_necessaryObject



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'app/models/mist/post.rb', line 230

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



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'app/models/mist/post.rb', line 95

def update_meta
  if popularity_changed? || new_record?
    self.class[:popular_posts][id] = popularity
    self.class. :popular_posts
  end
  
  if published_at_changed? || new_record?
    self.class[:published_at][id] = published_at
    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



226
227
228
# File 'app/models/mist/post.rb', line 226

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