pluto-models 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b32361390e52c8909c4c2ff6a49ffa07df29b7d8
4
- data.tar.gz: 0335b356a6de669eed82424fc08a080d2f158ffd
3
+ metadata.gz: 2adea6bc1a5429eea27ded5314e603e339ff2595
4
+ data.tar.gz: 50f68d6d907924a99745c74a5ba71545cafcf746
5
5
  SHA512:
6
- metadata.gz: cfa986ebd8dc839d7259724c7fa739535fa0adb3923bf30e88ad4c47679c3917ac2b62a4521613c266e1260cd76f0136360bce20cd540f7ac9d9f0b63fdf5c22
7
- data.tar.gz: 87868c583b0bfa102457197e4893c22108d809ec9ac257d677f314d0366bd5e4e0c12ea155f13c3d3ce91cd9ac5d88b748152dc3dac19b4ddf9f6faf4694d0f2
6
+ metadata.gz: 81b50daf9bfc77c04abedece3600bca9e9355f46f3e2df182ef6b22676fbdeed6529b6511142707cc3dc666ff82384ce86b71f24b04a421282f6f74357229844
7
+ data.tar.gz: cd4cd39e1a282a1ca242d9b8a743672bf37db241890485a520355310b8cdc5904c8e147ceae0d277b3163d8b74524468463fc921e50f8be63f5b5a2e4090ff5c
@@ -12,6 +12,8 @@ lib/pluto/models/subscription.rb
12
12
  lib/pluto/models/utils.rb
13
13
  lib/pluto/schema.rb
14
14
  lib/pluto/version.rb
15
+ test/data/ruby.ini
15
16
  test/helper.rb
16
17
  test/test_filter.rb
17
18
  test/test_helpers.rb
19
+ test/test_site.rb
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # pluto-models gem - planet schema 'n' models for easy (re)use
2
2
 
3
- * home :: [github.com/feedreader/pluto-models](https://github.com/feedreader/pluto-models)
4
- * bugs :: [github.com/feedreader/pluto-models/issues](https://github.com/feedreader/pluto-models/issues)
3
+ * home :: [github.com/feedreader/pluto.models](https://github.com/feedreader/pluto.models)
4
+ * bugs :: [github.com/feedreader/pluto.models/issues](https://github.com/feedreader/pluto.models/issues)
5
5
  * gem :: [rubygems.org/gems/pluto-models](https://rubygems.org/gems/pluto-models)
6
6
  * rdoc :: [rubydoc.info/gems/pluto-models](http://rubydoc.info/gems/pluto-models)
7
7
  * forum :: [groups.google.com/group/feedreader](http://groups.google.com/group/feedreader)
@@ -85,7 +85,8 @@ end
85
85
  ## Real World Usage
86
86
 
87
87
  - [`pluto`](https://github.com/feedreader/pluto) - planet generator command line tool using the pluto-models gem
88
- - [`pluto.live`](https://github.com/feedreader/pluto.live) - sample planet site; sinatra web app/starter template in ruby using the pluto-models gem
88
+ - [`pluto.live.starter`](https://github.com/feedreader/pluto.live.starter) - sample planet site; sinatra web app starter template in ruby using the pluto-models gem
89
+ - [`pluto.live`](https://github.com/feedreader/pluto.live) - sample planet site; rails web app in ruby using the pluto-models gem
89
90
 
90
91
 
91
92
 
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ Hoe.spec 'pluto-models' do
8
8
  self.summary = "pluto-models - planet schema 'n' models for easy (re)use"
9
9
  self.description = summary
10
10
 
11
- self.urls = ['https://github.com/feedreader/pluto-models']
11
+ self.urls = ['https://github.com/feedreader/pluto.models']
12
12
 
13
13
  self.author = 'Gerald Bauer'
14
14
  self.email = 'feedreader@googlegroups.com'
@@ -20,8 +20,9 @@ Hoe.spec 'pluto-models' do
20
20
  self.extra_deps = [
21
21
  ['props', '>= 1.1.2'],
22
22
  ['logutils', '>= 0.6.1'],
23
- ['feedutils', '>= 0.4.0'],
24
- ['textutils', '>= 0.10.0'],
23
+ ['feedparser', '>= 1.0.0'],
24
+ ['feedfilter', '>= 1.1.1'],
25
+ ['textutils', '>= 1.0.1'],
25
26
  ['activerecord'],
26
27
  ['logutils-activerecord', '>= 0.2.0'],
27
28
  ['props-activerecord', '>= 0.1.0'],
@@ -19,8 +19,10 @@ require 'active_record'
19
19
 
20
20
  require 'props' # manage settings/env
21
21
  require 'logutils'
22
- require 'feedutils'
23
22
  require 'textutils'
23
+ require 'feedparser'
24
+ require 'feedfilter'
25
+
24
26
 
25
27
  ## add more activerecords addons/utils
26
28
  require 'activerecord/utils' # add macros e.g. read_attr_w_fallbacks etc.
@@ -108,45 +110,5 @@ end # module Pluto
108
110
 
109
111
 
110
112
 
111
- ######
112
- # todo - move to ext/array.rb or similar
113
-
114
- class Array
115
-
116
- ## todo: check if there's already a builtin method for this
117
- #
118
- # note:
119
- # in rails ary.in_groups(3) results in
120
- # top-to-bottom, left-to-right.
121
- # and not left-to-right first and than top-to-bottom.
122
- #
123
- # rename to in_groups_vertical(3) ???
124
-
125
- def in_columns( cols ) # alias for convenience for chunks - needed? why? why not?
126
- chunks( cols )
127
- end
128
-
129
- def chunks( number_of_chunks )
130
- ## NB: use chunks - columns might be in use by ActiveRecord!
131
- ###
132
- # e.g.
133
- # [1,2,3,4,5,6,7,8,9,10].columns(3)
134
- # becomes:
135
- # [[1,4,7,10],
136
- # [2,5,8],
137
- # [3,6,9]]
138
-
139
- ## check/todo: make a copy of the array first??
140
- # for now reference to original items get added to columns
141
- chunks = (1..number_of_chunks).collect { [] }
142
- each_with_index do |item,index|
143
- chunks[ index % number_of_chunks ] << item
144
- end
145
- chunks
146
- end
147
-
148
- end
149
-
150
-
151
113
  # say hello
152
114
  puts Pluto.banner if $DEBUG || (defined?($RUBYLIBS_DEBUG) && $RUBYLIBS_DEBUG)
@@ -21,9 +21,9 @@ class Feed < ActiveRecord::Base
21
21
  # note: order by first non-null datetime field
22
22
  # coalesce - supported by sqlite (yes), postgres (yes)
23
23
 
24
- # note: if not published, touched or built use hardcoded 1971-01-01 for now
25
- ## order( "coalesce(published,touched,built,'1971-01-01') desc" )
26
- order( "coalesce(feeds.last_published,'1971-01-01') desc" )
24
+ # note: if not updated or published use hardcoded 1971-01-01 for now
25
+ ## order( "coalesce(updated,published,'1971-01-01') desc" )
26
+ order( "coalesce(feeds.items_last_updated,'1971-01-01') desc" )
27
27
  end
28
28
 
29
29
  ##################################
@@ -34,6 +34,7 @@ class Feed < ActiveRecord::Base
34
34
  def name() title; end # alias for title
35
35
  def description() summary; end # alias for summary
36
36
  def desc() summary; end # alias(2) for summary
37
+ def subtitle() summary; end # alias(3) for summary
37
38
  def link() url; end # alias for url
38
39
  def feed() feed_url; end # alias for feed_url
39
40
 
@@ -46,28 +47,28 @@ class Feed < ActiveRecord::Base
46
47
 
47
48
  def url?() read_attribute(:url).present?; end
48
49
  def title?() read_attribute(:title).present?; end
49
- def title2?() read_attribute(:title2).present?; end
50
50
  def feed_url?() read_attribute(:feed_url).present?; end
51
51
 
52
52
  def url() read_attribute_w_fallbacks( :url, :auto_url ); end
53
53
  def title() read_attribute_w_fallbacks( :title, :auto_title ); end
54
- def title2() read_attribute_w_fallbacks( :title2, :auto_title2 ); end
55
54
  def feed_url() read_attribute_w_fallbacks( :feed_url, :auto_feed_url ); end
56
55
 
56
+ def summary?() read_attribute(:summary).present?; end
57
57
 
58
+
59
+ def updated?() read_attribute(:updated).present?; end
58
60
  def published?() read_attribute(:published).present?; end
59
- def touched?() read_attribute(:touched).present?; end
60
61
 
62
+ def updated
63
+ ## todo/fix: use a new name - do NOT squeeze convenience lookup into existing
64
+ # db backed attribute
65
+ read_attribute_w_fallbacks( :updated, :published )
66
+ end
61
67
 
62
68
  def published
63
69
  ## todo/fix: use a new name - do NOT squeeze convenience lookup into existing
64
70
  # db backed attribute
65
-
66
- read_attribute_w_fallbacks(
67
- :published,
68
- :touched, # try touched (aka updated (ATOM))
69
- :built # try build (aka lastBuildDate (RSS))
70
- )
71
+ read_attribute_w_fallbacks( :published, :updated )
71
72
  end
72
73
 
73
74
 
@@ -75,44 +76,23 @@ class Feed < ActiveRecord::Base
75
76
  def debug?() @debug || false; end
76
77
 
77
78
 
78
- def match_terms?( terms, text ) ### make helper method private - why? why not??
79
- return false if text.blank? ## allow/guard against nil and empty string
79
+ def deep_update_from_struct!( data )
80
80
 
81
- terms.each do |term|
82
- if /#{term}/i =~ text ## Note: lets ignore case (use i regex option)
83
- return true
84
- end
81
+ ######
82
+ ## check for filters (includes/excludes) if present
83
+ ## for now just check for includes
84
+ ##
85
+ if includes.present?
86
+ includesFilter = FeedFilter::IncludeFilters.new( includes )
87
+ else
88
+ includesFilter = nil
85
89
  end
86
90
 
87
- false # no term match found
88
- end
89
-
90
-
91
-
92
- def save_from_struct!( data )
93
-
94
- update_from_struct!( data )
95
-
96
91
  data.items.each do |item|
97
-
98
- ######
99
- ## check for filters (includes/excludes) if present
100
- ## for now just check for includes
101
- ##
102
- if includes.present?
103
- ## split terms (allow comma,pipe) - do NOT use space; allows e.g. terms such as github pages
104
- terms = includes.split( /\s*[,|]\s*/ )
105
- ## remove leading and trailing white spaces - check - still required when using \s* ??
106
- terms = terms.map { |term| term.strip }
107
- match = match_terms?( terms, item.title ) ||
108
- match_terms?( terms, item.summary) ||
109
- match_terms?( terms, item.content)
110
-
111
- if match == false
112
- puts "** SKIPPING | #{item.title}"
113
- puts " no include terms match: #{terms.join('|')}"
114
- next ## skip to next item
115
- end
92
+ if includesFilter && includesFilter.match_item?( item ) == false
93
+ puts "** SKIPPING | #{item.title}"
94
+ puts " no include terms match: #{includes}"
95
+ next ## skip to next item
116
96
  end
117
97
 
118
98
  item_rec = Item.find_by_guid( item.guid )
@@ -123,17 +103,37 @@ class Feed < ActiveRecord::Base
123
103
  ## todo: check if any attribs changed
124
104
  puts "UPDATE | #{item.title}"
125
105
  end
126
-
106
+
127
107
  item_rec.debug = debug? ? true : false # pass along debug flag
128
- item_rec.update_from_struct!( self, item )
108
+
109
+ item_rec.feed_id = id # feed_rec.id - add feed_id fk_ref
110
+ item_rec.fetched = fetched # feed_rec.fetched
111
+
112
+ item_rec.update_from_struct!( item )
129
113
 
130
114
  end # each item
131
- end
115
+
116
+
117
+ # update cached value last published for item
118
+ ## todo/check: force reload of items - why? why not??
119
+ last_item_rec = items.latest.limit(1).first # note limit(1) will return relation/arrar - use first to get first element or nil from ary
120
+ if last_item_rec.present?
121
+ if last_item_rec.updated?
122
+ self.items_last_updated = last_item_rec.updated
123
+ ## save! ## note: will get save w/ update_from_struct! - why? why not??
124
+ else # try published
125
+ self.items_last_updated = last_item_rec.published
126
+ ## save! ## note: will get save w/ update_from_struct! - why? why not??
127
+ end
128
+ end
129
+
130
+ update_from_struct!( data )
131
+ end # method deep_update_from_struct!
132
132
 
133
133
 
134
134
  def update_from_struct!( data )
135
135
 
136
- ## todo: move to FeedUtils::Feed ??? why? why not??
136
+ ## todo: move to FeedParser::Feed ??? why? why not??
137
137
  if data.generator
138
138
  generator_full = ''
139
139
  generator_full << data.generator
@@ -145,9 +145,9 @@ class Feed < ActiveRecord::Base
145
145
 
146
146
  ##
147
147
  # todo:
148
- ## strip all tags from title2
148
+ ## strip all tags from summary (subtitle)
149
149
  ## limit to 255 chars
150
- ## e.g. title2 such as this exist
150
+ ## e.g. summary (subtitle) such as this exist
151
151
  ## This is a low-traffic announce-only list for people interested
152
152
  ## in hearing news about Polymer (<a href="http://polymer-project.org">http://polymer-project.org</a>).
153
153
  ## The higher-traffic mailing list for all kinds of discussion is
@@ -156,16 +156,14 @@ class Feed < ActiveRecord::Base
156
156
 
157
157
  feed_attribs = {
158
158
  format: data.format,
159
+ updated: data.updated,
159
160
  published: data.published,
160
- touched: data.updated,
161
- built: data.built,
162
161
  summary: data.summary,
162
+ generator: generator_full,
163
163
  ### todo/fix: add/use
164
164
  # auto_title: ???,
165
165
  # auto_url: ???,
166
166
  # auto_feed_url: ???,
167
- auto_title2: data.title2 ? strip_tags(data.title2)[0...255] : data.title2, # limit to 255 chars; strip tags
168
- generator: generator_full
169
167
  }
170
168
 
171
169
  if debug?
@@ -11,6 +11,7 @@ class Item < ActiveRecord::Base
11
11
  ## todo/fix:
12
12
  ## use a module ref or something; do NOT include all methods - why? why not?
13
13
  include TextUtils::HypertextHelper ## e.g. lets us use strip_tags( ht )
14
+ include FeedFilter::AdsFilter ## e.g. lets us use strip_ads( ht )
14
15
 
15
16
 
16
17
  ##################################
@@ -25,41 +26,45 @@ class Item < ActiveRecord::Base
25
26
  # note: order by first non-null datetime field
26
27
  # coalesce - supported by sqlite (yes), postgres (yes)
27
28
 
28
- # note: if not published,touched or built_at use hardcoded 1971-01-01 for now
29
- order( "coalesce(items.published,items.touched,'1971-01-01') desc" )
29
+ # note: if not updated,published use hardcoded 1971-01-01 for now
30
+ order( "coalesce(items.updated,items.published,'1971-01-01') desc" )
30
31
  end
31
32
 
32
- def published?() read_attribute(:published).present?; end
33
+ def updated?() read_attribute(:updated).present?; end
34
+ def published?() read_attribute(:published).present?; end # note: published is basically an alias for created
33
35
 
34
- def published
36
+ def updated
35
37
  ## todo/fix: use a new name - do NOT squeeze convenience lookup into existing
36
38
  # db backed attribute
37
-
38
- read_attribute_w_fallbacks(
39
- :published,
40
- :touched # try touched (aka updated RSS/ATOM)
41
- )
39
+ read_attribute_w_fallbacks( :updated, :published )
42
40
  end
43
41
 
42
+ def published
43
+ ## todo/fix: use a new name - do NOT squeeze convenience lookup into existing
44
+ # db backed attribute
45
+ read_attribute_w_fallbacks( :published, :updated )
46
+ end
44
47
 
45
48
 
46
49
  def debug=(value) @debug = value; end
47
50
  def debug?() @debug || false; end
48
51
 
49
- def update_from_struct!( feed_rec, data )
52
+
53
+ def update_from_struct!( data )
50
54
  ## check: new item/record? not saved? add guid
51
55
  # otherwise do not add guid - why? why not?
52
56
 
57
+ ## note: for now also strip ads in summary
58
+ ## fix/todo: summary (in the future) is supposed to be only plain vanilla text
59
+
53
60
  item_attribs = {
54
61
  guid: data.guid, # todo: only add for new records???
55
62
  title: data.title ? strip_tags(data.title)[0...255] : data.title, ## limit to 255 chars; strip tags
56
63
  url: data.url,
57
- summary: data.summary,
58
- content: data.content,
64
+ summary: data.summary.blank? ? data.summary : strip_ads( data.summary ).strip,
65
+ content: data.content.blank? ? data.content : strip_ads( data.content ).strip,
66
+ updated: data.updated,
59
67
  published: data.published,
60
- touched: data.updated,
61
- feed_id: feed_rec.id, # add feed_id fk_ref
62
- fetched: feed_rec.fetched
63
68
  }
64
69
 
65
70
  if debug?
@@ -13,7 +13,6 @@ class Site < ActiveRecord::Base
13
13
  ##################################
14
14
  # attribute reader aliases
15
15
  def name() title; end # alias for title
16
- def fetched_at() fetched; end # - legacy attrib reader -- remove!!!
17
16
 
18
17
  def owner_name() author; end # alias for author
19
18
  def owner() author; end # alias(2) for author
@@ -22,8 +21,179 @@ class Site < ActiveRecord::Base
22
21
  def owner_email() email; end # alias for email
23
22
  def author_email() email; end # alias(2) for email
24
23
 
24
+
25
+
26
+ def self.deep_create_or_update_from_hash!( name, config, opts={} )
27
+
28
+ ## note: allow (optional) config of site key too
29
+ site_key = config['key'] || config['slug']
30
+ if site_key.nil?
31
+ ## if no key configured; use (file)name; remove -_ chars
32
+ ## e.g. jekyll-meta becomes jekyllmeta etc.
33
+ site_key = name.downcase.gsub( /[\-_]/, '' )
34
+ end
35
+
36
+ site_rec = Site.find_by_key( site_key )
37
+ if site_rec.nil?
38
+ site_rec = Site.new
39
+ site_rec.key = site_key
40
+ end
41
+
42
+ site_rec.deep_update_from_hash!( config, opts )
43
+ site_rec
44
+ end
45
+
46
+
47
+ def deep_update_from_hash!( config, opts={} )
48
+
49
+ site_attribs = {
50
+ title: config['title'] || config['name'], # support either title or name
51
+ url: config['source'] || config['url'], # support source or url for source url for auto-update (optional)
52
+ author: config['author'] || config['owner'],
53
+ email: config['email'],
54
+ updated: Time.now, ## track last_update via pluto (w/ update_subscription_for fn)
55
+ }
56
+
57
+ ## note: allow (optional) config of site key too
58
+ site_key = config['key'] || config['slug']
59
+ site_attribs[:key] = site_key if site_key
60
+
61
+
62
+ logger = LogUtils::Logger.root
63
+ logger.debug "site_attribs: #{site_attribs.inspect}"
64
+
65
+ if new_record?
66
+ ## use object_id: site.id and object_type: Site
67
+ ## change - model/table/schema!!!
68
+ Activity.create!( text: "new site >#{key}< - #{title}" )
69
+ end
70
+
71
+ update_attributes!( site_attribs )
72
+
73
+
74
+ # -- log update activity
75
+ ## Activity.create!( text: "update subscriptions for site >#{key}<" )
76
+
77
+ #### todo/fix:
78
+ ## double check - how to handle delete
79
+ ## feeds might get referenced by other sites
80
+ ## cannot just delete feeds; only save to delete join table (subscriptions)
81
+ ## check if feed "lingers" on with no reference (to site)???
82
+
83
+ # clean out subscriptions and add again
84
+ logger.debug "before site.subscriptions.delete_all - count: #{subscriptions.count}"
85
+ # note: use destroy_all NOT delete_all (delete_all tries by default only nullify)
86
+ subscriptions.destroy_all
87
+ logger.debug "after site.subscriptions.delete_all - count: #{subscriptions.count}"
88
+
89
+
90
+ config.each do |k, v|
91
+
92
+ ## todo: downcase key - why ??? why not???
93
+
94
+ # skip "top-level" feed keys e.g. title, etc. or planet planet sections (e.g. planet,defaults)
95
+ next if ['key','slug',
96
+ 'title','name','name2','title2','subtitle',
97
+ 'source', 'url',
98
+ 'include','includes','exclude','excludes',
99
+ 'feeds',
100
+ 'author', 'owner', 'email',
101
+ 'planet','defaults'].include?( k )
102
+
103
+ ### todo/check:
104
+ ## check value - must be hash
105
+ # check if url or feed_url present
106
+ # that is, check for required props/key-value pairs
107
+
108
+ feed_key = k.to_s.dup
109
+ feed_hash = v
110
+
111
+ # todo/fix: use title from feed?
112
+ # e.g. fill up auto_title, auto_url, etc.
113
+
114
+ feed_attribs = {
115
+ feed_url: feed_hash[ 'feed' ] || feed_hash[ 'feed_url' ] || feed_hash[ 'xml_url' ],
116
+ url: feed_hash[ 'link' ] || feed_hash[ 'url' ] || feed_hash[ 'html_url' ],
117
+ title: feed_hash[ 'title' ] || feed_hash[ 'name' ],
118
+ ## note: title2 no longer supported; use summary or subtitle?
119
+ ### title2: feed_hash[ 'title2' ] || feed_hash[ 'name2' ] || feed_hash[ 'subtitle'],
120
+ includes: feed_hash[ 'includes' ] || feed_hash[ 'include' ],
121
+ excludes: feed_hash[ 'excludes' ] || feed_hash[ 'exclude' ],
122
+ author: feed_hash[ 'author' ] || feed_hash[ 'owner' ],
123
+ email: feed_hash[ 'email' ],
124
+ avatar: feed_hash[ 'avatar' ] || feed_hash[ 'face'],
125
+ location: feed_hash[ 'location' ],
126
+ github: feed_hash[ 'github' ],
127
+ twitter: feed_hash[ 'twitter' ],
128
+ rubygems: feed_hash[ 'rubygems' ],
129
+ meetup: feed_hash[ 'meetup' ], ### -- remove from schema - virtual attrib ?? - why? why not??
130
+ }
131
+
132
+ feed_attribs[:encoding] = feed_hash['encoding']||feed_hash['charset'] if feed_hash['encoding']||feed_hash['charset']
133
+
134
+ #####
135
+ ##
136
+ # auto-fill; convenience helpers
137
+
138
+ if feed_hash['meetup']
139
+ ## link/url = http://www.meetup.com/vienna-rb
140
+ ## feed/feed_url = http://www.meetup.com/vienna-rb/events/rss/vienna.rb/
141
+
142
+ feed_attribs[:url] = "http://www.meetup.com/#{feed_hash['meetup']}" if feed_attribs[:url].nil?
143
+ feed_attribs[:feed_url] = "http://www.meetup.com/#{feed_hash['meetup']}/events/rss/#{feed_hash['meetup']}/" if feed_attribs[:feed_url].nil?
144
+ end
145
+
146
+ if feed_hash['googlegroups']
147
+ ## link/url = https://groups.google.com/group/beerdb or
148
+ ## https://groups.google.com/forum/#!forum/beerdb
149
+ ## feed/feed_url = https://groups.google.com/forum/feed/beerdb/topics/atom.xml?num=15
150
+
151
+ feed_attribs[:url] = "https://groups.google.com/group/#{feed_hash['googlegroups']}" if feed_attribs[:url].nil?
152
+ feed_attribs[:feed_url] = "https://groups.google.com/forum/feed//#{feed_hash['googlegroups']}/topics/atom.xml?num=15" if feed_attribs[:feed_url].nil?
153
+ end
154
+
155
+ if feed_hash['github'] && feed_hash['github'].index('/') ## e.g. jekyll/jekyll
156
+ ## link/url = https://github.com/jekyll/jekyll
157
+ ## feed/feed_url = https://github.com/jekyll/jekyll/commits/master.atom
158
+
159
+ feed_attribs[:url] = "https://github.com/#{feed_hash['github']}" if feed_attribs[:url].nil?
160
+ feed_attribs[:feed_url] = "https://github.com/#{feed_hash['github']}/commits/master.atom" if feed_attribs[:feed_url].nil?
161
+ end
162
+
163
+ if feed_hash['rubygems'] && feed_attribs[:url].nil? && feed_attribs[:feed_url].nil?
164
+ ## link/url = http://rubygems.org/gems/jekyll
165
+ ## feed/feed_url = http://rubygems.org/gems/jekyll/versions.atom
166
+
167
+ feed_attribs[:url] = "http://rubygems.org/gems/#{feed_hash['rubygems']}" if feed_attribs[:url].nil?
168
+ feed_attribs[:feed_url] = "http://rubygems.org/gems/#{feed_hash['rubygems']}/versions.atom" if feed_attribs[:feed_url].nil?
169
+ end
170
+
171
+
172
+ puts "Updating feed subscription >#{feed_key}< - >#{feed_attribs[:feed_url]}<..."
173
+
174
+ feed_rec = Feed.find_by_key( feed_key )
175
+ if feed_rec.nil?
176
+ feed_rec = Feed.new
177
+ feed_attribs[:key] = feed_key
178
+
179
+ ## use object_id: feed.id and object_type: Feed
180
+ ## change - model/table/schema!!!
181
+ ## todo: add parent_action_id - why? why not?
182
+ Activity.create!( text: "new feed >#{feed_key}< - #{feed_attribs[:title]}" )
183
+ end
184
+
185
+ feed_rec.update_attributes!( feed_attribs )
186
+
187
+ # add subscription record
188
+ # note: subscriptions get cleaned out on update first (see above)
189
+ subscriptions.create!( feed_id: feed_rec.id )
190
+ end
191
+
192
+ end # method deep_update_from_hash!
193
+
25
194
  end # class Site
26
195
 
27
196
 
28
197
  end # module Model
29
198
  end # module Pluto
199
+
@@ -10,16 +10,16 @@ class ItemCursor
10
10
  end
11
11
 
12
12
  def each
13
- last_published = Time.local( 1971, 1, 1 )
13
+ last_updated = Time.local( 1971, 1, 1 )
14
14
  last_feed_id = -1 ## todo: use feed_key instead of id?? why? why not??
15
-
15
+
16
16
  @items.each do |item|
17
17
 
18
- item_published = item.published # cache published value ref
18
+ item_updated = item.updated # cache updated value ref
19
19
 
20
- if last_published.year == item_published.year &&
21
- last_published.month == item_published.month &&
22
- last_published.day == item_published.day
20
+ if last_updated.year == item_updated.year &&
21
+ last_updated.month == item_updated.month &&
22
+ last_updated.day == item_updated.day
23
23
  new_date = false
24
24
  else
25
25
  new_date = true
@@ -37,7 +37,7 @@ class ItemCursor
37
37
 
38
38
  yield( item, new_date, new_feed )
39
39
 
40
- last_published = item.published
40
+ last_updated = item.updated
41
41
  last_feed_id = item.feed.id
42
42
  end
43
43
  end # method each
@@ -35,6 +35,7 @@ class CreateDb < ActiveRecord::Migration
35
35
 
36
36
  # note: do NOT store body content (that is, text) and md5 digest
37
37
  # use git! and github! commit will be http_etag!!
38
+ t.string :md5 # md5 hash of body
38
39
 
39
40
 
40
41
  #############
@@ -53,37 +54,35 @@ class CreateDb < ActiveRecord::Migration
53
54
  create_table :feeds do |t|
54
55
  t.string :key, null: false
55
56
  t.string :encoding, null: false, default: 'utf8' # charset encoding; default to utf8
56
- t.string :format # e.g. atom (1.0), rss 2.0, rss 0.7 etc.
57
-
58
- t.string :title # user supplied title
59
- t.string :auto_title # "fallback" - auto(fill) title from feed
60
-
61
- t.string :title2 # user supplied title2
62
- t.string :auto_title2 # "fallback" - auto(fill) title2 from feed e.g. subtitle (atom)
63
-
64
- t.string :url # user supplied site url
65
- t.string :auto_url # "fallback" - auto(fill) url from feed
57
+ t.string :format # e.g. atom (1.0), rss 2.0, etc.
66
58
 
59
+ t.string :title # user supplied title
60
+ t.string :url # user supplied site url
67
61
  t.string :feed_url # user supplied feed url
62
+
63
+ t.string :auto_title # "fallback" - auto(fill) title from feed
64
+ t.string :auto_url # "fallback" - auto(fill) url from feed
68
65
  t.string :auto_feed_url # "fallback" - auto discovery feed url from (site) url
69
66
 
70
- t.text :summary # e.g. description (rss)
67
+ t.text :summary # e.g. description (rss), subtitle (atom)
68
+ ## todo: add auto_summary - why? why not?
71
69
 
72
70
  t.string :generator # feed generator (e.g. wordpress, etc.) from feed
73
-
74
- t.datetime :published # from feed published(atom)+ pubDate(rss)
75
- t.datetime :built # from feed lastBuiltDate(rss)
76
- t.datetime :touched # from feed updated(atom)
71
+
72
+ t.datetime :updated # from feed updated(atom) + lastBuildDate(rss)
73
+ t.datetime :published # from feed published(atom) + pubDate(rss) - note: published basically an alias for created
77
74
 
78
75
 
79
76
  ### extras (move to array for custom fields or similar??)
80
- t.string :author # author_name, owner_name
81
- t.string :email # author_email, owner_email
82
- t.string :avatar # gravator or hackergotchi handle (optional)
77
+ t.string :author # author_name, owner_name
78
+ t.string :email # author_email, owner_email
79
+ t.string :avatar # gravator or hackergotchi handle (optional)
80
+ t.string :location # e.g. Vienna > Austria, Bamberg > Germany etc. (optional)
83
81
 
84
- t.string :github # github handle (optional)
85
- t.string :twitter # twitter handle (optional)
86
- t.string :meetup # meetup handle (optional)
82
+ t.string :github # github handle (optional)
83
+ t.string :rubygems # rubygems handle (optional)
84
+ t.string :twitter # twitter handle (optional)
85
+ t.string :meetup # meetup handle (optional)
87
86
 
88
87
 
89
88
  ### add class/kind field e.g.
@@ -100,7 +99,7 @@ class CreateDb < ActiveRecord::Migration
100
99
  # todo: add generic filter list e.g. t.string :filters (comma,pipe or space separated method names?)
101
100
 
102
101
  # -- our own (meta) fields
103
- t.datetime :last_published # cache last (latest) published for items - e.g. latest date from publisehd item
102
+ t.datetime :items_last_updated # cache last (latest) updated for items - e.g. latest date from updated item
104
103
  t.datetime :fetched # last fetched date via pluto
105
104
 
106
105
  t.integer :http_code # last http status code e.g. 200,404,etc.
@@ -123,15 +122,15 @@ class CreateDb < ActiveRecord::Migration
123
122
 
124
123
  ## note: title may contain more than 255 chars!!
125
124
  ## e.g. Rails Girls blog has massive titles in feed
126
- ## cut-off/limit to 255
125
+ ## cut-off/limit to 255 - why?? why not??
127
126
  ## also strip tags in titles - why? why not?? - see feed.title2/auto_title2
128
127
 
129
- t.string :title # todo: add some :null => false ??
128
+ t.text :title # todo: add some :null => false ??
130
129
  t.text :summary # e.g. description (rss), summary (atom)
131
130
  t.text :content
132
131
 
133
- t.datetime :published # from feed (published) + pubDate(rss)
134
- t.datetime :touched # from feed updated (atom)
132
+ t.datetime :updated # from feed updated (atom) + pubDate(rss)
133
+ t.datetime :published # from feed published (atom) -- note: published is basically an alias for created
135
134
 
136
135
  ## todo: add :last_updated_at ?? (NOTE: updated_at already take by auto-timestamps)
137
136
  t.references :feed, null: false
@@ -3,8 +3,8 @@
3
3
  module Pluto
4
4
 
5
5
  MAJOR = 1
6
- MINOR = 3
7
- PATCH = 2
6
+ MINOR = 4
7
+ PATCH = 0
8
8
  VERSION = [MAJOR,MINOR,PATCH].join('.')
9
9
 
10
10
  def self.version
@@ -0,0 +1,18 @@
1
+ title = Planet Ruby
2
+ source = https://github.com/feedreader/pluto.models/raw/master/test/data/ruby.ini
3
+
4
+ [rubylang]
5
+ title = Ruby Lang News
6
+ link = http://www.ruby-lang.org/en/news
7
+ feed = http://www.ruby-lang.org/en/feeds/news.rss
8
+
9
+ [rubyonrails]
10
+ title = Ruby on Rails News
11
+ link = http://weblog.rubyonrails.org
12
+ feed = http://weblog.rubyonrails.org/feed/atom.xml
13
+
14
+ [viennarb]
15
+ title = Vienna.rb News
16
+ link = http://vienna-rb.at
17
+ feed = http://vienna-rb.at/atom.xml
18
+
@@ -23,34 +23,37 @@ class TestFilter < MiniTest::Test
23
23
  title: 'Test'
24
24
  )
25
25
 
26
- feed_data = FeedUtils::Feed.new
26
+ feed_data = FeedParser::Feed.new
27
27
  feed_data.title = 'Test'
28
28
  feed_data.items = []
29
29
 
30
- item_data = FeedUtils::Item.new
30
+ item_data = FeedParser::Item.new
31
31
  item_data.title = 'Test #1'
32
32
  item_data.summary = 'Test'
33
33
  item_data.content = 'Test'
34
+ item_data.updated = Date.today
34
35
 
35
36
  feed_data.items << item_data
36
37
 
37
- item_data = FeedUtils::Item.new
38
+ item_data = FeedParser::Item.new
38
39
  item_data.title = 'Test #2'
39
40
  item_data.summary = "Test\nTest\nTest1"
40
41
  item_data.content = 'Test'
42
+ item_data.updated = Date.today
41
43
 
42
44
  feed_data.items << item_data
43
45
 
44
46
 
45
- item_data = FeedUtils::Item.new
47
+ item_data = FeedParser::Item.new
46
48
  item_data.title = 'Test #3'
47
49
  item_data.summary = "Test\nTest\nTest"
48
50
  item_data.content = 'Test\nTest\nGitHub Pages'
51
+ item_data.updated = Date.today
49
52
 
50
53
  feed_data.items << item_data
51
54
 
52
- feed1.save_from_struct!( feed_data ) ## check w/ includes
53
- ## feed2.save_from_struct!( feed_data ) ## check w/o includes
55
+ feed1.deep_update_from_struct!( feed_data ) ## check w/ includes
56
+ feed2.deep_update_from_struct!( feed_data ) ## check w/o includes
54
57
 
55
58
  assert true ## if we get here it should workd
56
59
  end
@@ -10,7 +10,13 @@ require 'helper'
10
10
 
11
11
  class TestHelper < MiniTest::Test
12
12
 
13
- ## add some tests; to be done
13
+ def setup
14
+ Log.delete_all
15
+ Site.delete_all
16
+ Feed.delete_all
17
+ Subscription.delete_all
18
+ Item.delete_all
19
+ end
14
20
 
15
21
  def test_banner
16
22
  puts Pluto.banner
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+
3
+ ###
4
+ # to run use
5
+ # ruby -I ./lib -I ./test test/test_site.rb
6
+ # or better
7
+ # rake test
8
+
9
+ require 'helper'
10
+
11
+ class TestSite < MiniTest::Test
12
+
13
+ def setup
14
+ Log.delete_all
15
+ Site.delete_all
16
+ Feed.delete_all
17
+ Subscription.delete_all
18
+ Item.delete_all
19
+ end
20
+
21
+
22
+ def test_site_create
23
+ site_text = File.read( "#{Pluto.root}/test/data/ruby.ini")
24
+ site_config = INI.load( site_text )
25
+ ## pp site_config
26
+
27
+ assert_equal 0, Site.count
28
+ assert_equal 0, Feed.count
29
+
30
+ Site.deep_create_or_update_from_hash!( 'ruby', site_config )
31
+
32
+ assert_equal 1, Site.count
33
+ assert_equal 3, Feed.count
34
+
35
+ ruby = Site.find_by_key!( 'ruby' )
36
+ assert_equal 'Planet Ruby', ruby.title
37
+ assert_equal 3, ruby.subscriptions.count
38
+ assert_equal 3, ruby.feeds.count
39
+
40
+ rubylang = Feed.find_by_key!( 'rubylang' )
41
+ assert_equal 'Ruby Lang News', rubylang.title
42
+ assert_equal 'http://www.ruby-lang.org/en/news', rubylang.url
43
+ assert_equal 'http://www.ruby-lang.org/en/feeds/news.rss', rubylang.feed_url
44
+ end
45
+
46
+ def test_site_update
47
+ site_text = File.read( "#{Pluto.root}/test/data/ruby.ini")
48
+ site_config = INI.load( site_text )
49
+ ## pp site_config
50
+
51
+ assert_equal 0, Site.count
52
+ assert_equal 0, Feed.count
53
+
54
+ ## note: call twice (first time for create, second time for update)
55
+ Site.deep_create_or_update_from_hash!( 'ruby', site_config )
56
+ Site.deep_create_or_update_from_hash!( 'ruby', site_config )
57
+
58
+ assert_equal 1, Site.count
59
+ assert_equal 3, Feed.count
60
+
61
+ ruby = Site.find_by_key!( 'ruby' )
62
+ assert_equal 'Planet Ruby', ruby.title
63
+ assert_equal 3, ruby.subscriptions.count
64
+ assert_equal 3, ruby.feeds.count
65
+
66
+ rubylang = Feed.find_by_key!( 'rubylang' )
67
+ assert_equal 'Ruby Lang News', rubylang.title
68
+ assert_equal 'http://www.ruby-lang.org/en/news', rubylang.url
69
+ assert_equal 'http://www.ruby-lang.org/en/feeds/news.rss', rubylang.feed_url
70
+ end
71
+
72
+
73
+ end # class TestSite
74
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pluto-models
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-26 00:00:00.000000000 Z
11
+ date: 2015-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: props
@@ -39,33 +39,47 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.6.1
41
41
  - !ruby/object:Gem::Dependency
42
- name: feedutils
42
+ name: feedparser
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 0.4.0
47
+ version: 1.0.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 0.4.0
54
+ version: 1.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: feedfilter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.1.1
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: textutils
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - ">="
60
74
  - !ruby/object:Gem::Version
61
- version: 0.10.0
75
+ version: 1.0.1
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - ">="
67
81
  - !ruby/object:Gem::Version
68
- version: 0.10.0
82
+ version: 1.0.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: activerecord
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -188,10 +202,12 @@ files:
188
202
  - lib/pluto/models/utils.rb
189
203
  - lib/pluto/schema.rb
190
204
  - lib/pluto/version.rb
205
+ - test/data/ruby.ini
191
206
  - test/helper.rb
192
207
  - test/test_filter.rb
193
208
  - test/test_helpers.rb
194
- homepage: https://github.com/feedreader/pluto-models
209
+ - test/test_site.rb
210
+ homepage: https://github.com/feedreader/pluto.models
195
211
  licenses:
196
212
  - Public Domain
197
213
  metadata: {}
@@ -219,4 +235,5 @@ specification_version: 4
219
235
  summary: pluto-models - planet schema 'n' models for easy (re)use
220
236
  test_files:
221
237
  - test/test_filter.rb
238
+ - test/test_site.rb
222
239
  - test/test_helpers.rb