feeder 0.3.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +119 -2
  3. data/app/controllers/feeder/application_controller.rb +1 -1
  4. data/app/controllers/feeder/items_controller.rb +7 -0
  5. data/app/models/feeder/feedable_observer.rb +43 -7
  6. data/app/models/feeder/item.rb +2 -6
  7. data/app/views/feeder/items/_item.html.erb +1 -1
  8. data/app/views/feeder/items/_item.json.jbuilder +1 -0
  9. data/app/views/feeder/{feeds → items}/index.html.erb +1 -1
  10. data/app/views/feeder/items/index.json.jbuilder +1 -0
  11. data/config/routes.rb +1 -1
  12. data/db/migrate/20140321085409_add_sticky_to_feeder_items.rb +5 -0
  13. data/db/migrate/20140401131911_prohibit_null_on_feeder_item_stickies.rb +13 -0
  14. data/db/migrate/20140526110451_add_blocked_to_feeder_items.rb +5 -0
  15. data/db/migrate/20140526110501_add_reported_to_feeder_items.rb +5 -0
  16. data/lib/feeder.rb +18 -2
  17. data/lib/feeder/active_record.rb +9 -0
  18. data/lib/feeder/concerns.rb +6 -0
  19. data/lib/feeder/concerns/controllers.rb +5 -0
  20. data/lib/feeder/concerns/controllers/items_controller.rb +22 -0
  21. data/lib/feeder/concerns/feedable.rb +27 -0
  22. data/lib/feeder/concerns/helpers.rb +5 -0
  23. data/lib/feeder/concerns/helpers/filter.rb +20 -0
  24. data/lib/feeder/concerns/models.rb +5 -0
  25. data/lib/feeder/concerns/models/item.rb +31 -0
  26. data/lib/feeder/configuration.rb +19 -5
  27. data/lib/feeder/engine.rb +3 -2
  28. data/lib/feeder/version.rb +1 -1
  29. data/spec/controllers/feeder/items_controller_spec.rb +36 -0
  30. data/spec/dummy/app/models/message.rb +2 -0
  31. data/spec/dummy/config/initializers/feeder.rb +1 -1
  32. data/spec/dummy/db/development.sqlite3 +0 -0
  33. data/spec/dummy/db/schema.rb +4 -1
  34. data/spec/dummy/db/test.sqlite3 +0 -0
  35. data/spec/dummy/log/development.log +359 -0
  36. data/spec/dummy/log/test.log +53050 -0
  37. data/spec/factories/feeder/items.rb +10 -0
  38. data/spec/lib/feeder/concerns/feedable_spec.rb +33 -0
  39. data/spec/lib/feeder/configuration_spec.rb +6 -6
  40. data/spec/lib/feeder_spec.rb +14 -2
  41. data/spec/models/feeder/feedable_observer_spec.rb +95 -0
  42. data/spec/models/feeder/item_spec.rb +38 -0
  43. data/spec/spec_helper.rb +4 -0
  44. data/spec/support/shared_examples/filterable.rb +48 -0
  45. metadata +97 -39
  46. data/app/controllers/feeder/feeds_controller.rb +0 -9
  47. data/app/views/layouts/feeder/application.html.erb +0 -14
  48. data/spec/controllers/feeder/feeds_controller_spec.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 22e1b2d7d889186ead64cdeda3bda95f03db5608
4
- data.tar.gz: f4fcc534375a8010d0175406332ce0fbdf9fa34d
3
+ metadata.gz: 04e9d95c32a3be20252ca3365e9e3e9aa6d61599
4
+ data.tar.gz: d6765c13e947c4a5d30b6f30125b9afcbfc79106
5
5
  SHA512:
6
- metadata.gz: 3b008831dc5207f16c7bf28bc3b6b5103de2c74d4bbd385a823ff5b44bfa8b93641b6bc298dff967eca4fae94f1ff59bf51799fb2f7f05a4ae79a59108519763
7
- data.tar.gz: 42b34bff84ff71b0451fd4871c6aa15370d1d6a8c3068bcbec789e8c872dfb57f9766c98e646198404a13b952c85586ce9ead7c0b5c33249b1fb3db1a2c626fb
6
+ metadata.gz: 7bc92c030d928c6642485ca8dd2ecb2c74866bd5a630c3589bdd2d9aa50141cecf0ca0c71f98fef0a9d441a1ffa02d9d81fb45f4c348af385491b21621eee572
7
+ data.tar.gz: 527433e9fb8feae052f6b99e192d10064676bb303a3b69fda212ba1523e58d3f94b0c3f3c31e2251524119f68ef56d5d83c4cb0aaf5e21568b7524a3ae29b3e6
data/README.md CHANGED
@@ -1,4 +1,10 @@
1
- # Mingle
1
+ # Feeder
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/feeder.svg)](https://rubygems.org/gems/feeder)
4
+ [![Build Status](https://img.shields.io/travis/hyperoslo/feeder.svg)](https://travis-ci.org/hyperoslo/feeder)
5
+ [![Dependency Status](https://img.shields.io/gemnasium/hyperoslo/feeder.svg)](https://gemnasium.com/hyperoslo/feeder)
6
+ [![Code Climate](https://img.shields.io/codeclimate/github/hyperoslo/feeder.svg)](https://codeclimate.com/github/hyperoslo/feeder)
7
+ [![Coverage Status](https://img.shields.io/coveralls/hyperoslo/feeder.svg)](https://coveralls.io/r/hyperoslo/feeder)
2
8
 
3
9
  Feeder gives you a mountable engine that provides a route to a feed page in your
4
10
  Ruby on Rails application.
@@ -20,11 +26,122 @@ Or install it yourself as:
20
26
  Install the migrations:
21
27
 
22
28
  rake feeder:install:migrations
23
-
29
+
24
30
  Run the migrations:
25
31
 
26
32
  rake db:migrate
27
33
 
34
+ ## Usage
35
+
36
+ To make Feeder available, mount it to a route by adding the following somewhere
37
+ in your _config/routes.rb_:
38
+
39
+ ```ruby
40
+ mount Feeder::Engine => "/feed"
41
+ ```
42
+
43
+ You will now be able to display a feed on _/feed_ in your application. In order
44
+ for Feeder to display anything in your feed, however, you will need to make
45
+ views per item type in the feed. Feeder looks up these views in
46
+ _app/views/feeder/types_ by default, and then checks for a partial with the same
47
+ name as your item type. As an example, if you have a `Message` model that you
48
+ wish to list out on your feed, you would make a file called *_message.html.erb*
49
+ in _app/views/feeder/types_.
50
+
51
+ Feeder also comes with an observer for automatically generating wrapper items
52
+ for your feedables (e.g. messages). In order to use it, you only need to register
53
+ `Feeder::FeedableObserver` into your app, which can be done in
54
+ _config/application.rb_ like this:
55
+
56
+ ```ruby
57
+ config.active_record.observers = [ 'Feeder::FeedableObserver' ]
58
+ ```
59
+
60
+ Then, all you need to do is tell Feeder what to
61
+ observe, which is done through an initializer, like this:
62
+
63
+ ```ruby
64
+ Feeder.configure do |config|
65
+ config.observe Message
66
+ end
67
+ ```
68
+
69
+ ... and declare that your `Message` model is feedable:
70
+
71
+ ```ruby
72
+ class Message < ActiveRecord::Base
73
+ feedable
74
+ end
75
+ ```
76
+
77
+ If you don't want to publish every message in the feed, you can supply a condition
78
+ to `observe`:
79
+
80
+ ```ruby
81
+ Feeder.configure do |config|
82
+ config.observe Message, if: -> message { message.show_in_feed? }
83
+ end
84
+ ```
85
+
86
+ Pretty neat.
87
+
88
+ ### Filtering
89
+
90
+ Want to filter out what feedables to display in your feed? We've got you covered
91
+ through the all-powerful `filter` scope! Give it a hash of feedables and the
92
+ IDs that you want to fetch, and Feeder makes sure to only return feed items with
93
+ the specified feedables. You may also pass in the symbol `:all` instead of a
94
+ list of IDs, which would fetch each of them. For example: say you have the
95
+ following feedables:
96
+
97
+ - `ShortMessage`
98
+ - `Tweet`
99
+ - `NewsArticle`
100
+
101
+ To get `Feeder::Item`s with news articles having IDs `[1, 2, 3, 4, 5]`, tweets
102
+ `[2, 4, 6, 7]` and all short message, you could do like this:
103
+
104
+ ```ruby
105
+ Feeder::Item.filter(
106
+ NewsArticle => [1, 2, 3, 4, 5],
107
+ Tweet => [2, 4, 6, 7],
108
+ ShortMessage => :all,
109
+ )
110
+ ```
111
+
112
+ **NOTE:** The `filter` scope is _exclusive_, meaning that anything you _do not_
113
+ pass in to it will also not be brought back. With the above feedables, if you
114
+ only want short messages `[1, 3, 4]`, but all of the tweets and news articles,
115
+ you would have to specify them as well, like this:
116
+
117
+ ```ruby
118
+ Feeder::Item.filter(
119
+ ShortMessage => [1, 3, 4],
120
+ Tweet => :all,
121
+ NewsArticle => :all
122
+ )
123
+ ```
124
+
125
+ The following would only return feed items with short messages:
126
+
127
+ ```ruby
128
+ Feeder::Item.filter(ShortMessage => [1, 3, 4])
129
+ ```
130
+
131
+ ### Configuration
132
+
133
+ ```ruby
134
+ Feeder.configure do |config|
135
+ # A list of scopes that will be applied to the feed items in the controller.
136
+ config.scopes << proc { limit 5 }
137
+ end
138
+ ```
139
+
140
+ ### Stickies
141
+
142
+ You can "sticky" messages in your feed so they're pinned at the top regardless of when
143
+ they were created. Just set the `sticky` attribute and Feeder will take care of the rest.
144
+
28
145
  ## Contributing
29
146
 
30
147
  1. Fork it
@@ -1,4 +1,4 @@
1
1
  module Feeder
2
- class ApplicationController < ActionController::Base
2
+ class ApplicationController < ::ApplicationController
3
3
  end
4
4
  end
@@ -0,0 +1,7 @@
1
+ require_dependency "feeder/application_controller"
2
+
3
+ module Feeder
4
+ class ItemsController < ApplicationController
5
+ include Feeder::Concerns::Controllers::ItemsController
6
+ end
7
+ end
@@ -1,13 +1,49 @@
1
1
  module Feeder
2
- class FeedableObserver < ActiveRecord::Observer
3
- observe Feeder.config.observables
2
+ class FeedableObserver < ::ActiveRecord::Observer
3
+ observe Feeder.config.observables.keys
4
4
 
5
5
  def after_create(feedable)
6
- Feeder::Item.create!(
7
- feedable: feedable,
8
- created_at: feedable.created_at,
9
- published_at: Time.zone.now
10
- )
6
+ options = options_for feedable
7
+
8
+ if condition = options[:if]
9
+ if condition.respond_to? :call
10
+ return unless condition.call feedable
11
+ else
12
+ return unless feedable.send condition
13
+ end
14
+ end
15
+
16
+ feedable.create_feeder_item! do |item|
17
+ item.feedable = feedable
18
+ item.created_at = feedable.created_at
19
+ item.published_at = Time.zone.now
20
+
21
+ if feedable.respond_to? :sticky
22
+ item.sticky = feedable.sticky
23
+ end
24
+ end
25
+ end
26
+
27
+ def after_save(feedable)
28
+ item = feedable.feeder_item
29
+
30
+ if item
31
+ if feedable.respond_to? :sticky
32
+ item.sticky = feedable.sticky
33
+ end
34
+
35
+ item.save!
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def options_for(feedable)
42
+ (observables[feedable.class] || observables[feedable.class.to_s]) or raise StandardError, "#{feedable} is not observed"
43
+ end
44
+
45
+ def observables
46
+ Feeder.config.observables
11
47
  end
12
48
  end
13
49
  end
@@ -1,9 +1,5 @@
1
1
  module Feeder
2
- class Item < ActiveRecord::Base
3
- belongs_to :feedable, polymorphic: true
4
-
5
- def type
6
- feedable_type.underscore
7
- end
2
+ class Item < ::ActiveRecord::Base
3
+ include Feeder::Concerns::Models::Item
8
4
  end
9
5
  end
@@ -1,3 +1,3 @@
1
- <div class="feed_item <%= item.type %>">
1
+ <div class="feed-item <%= item.type.parameterize %>">
2
2
  <%= render "feeder/types/#{item.type}", feedable: item.feedable %>
3
3
  </div>
@@ -0,0 +1 @@
1
+ json.partial! "feeder/types/#{item.type}", feedable: item.feedable
@@ -1,3 +1,3 @@
1
- <div class="feed_items">
1
+ <div class="feed-items">
2
2
  <%= render @items %>
3
3
  </div>
@@ -0,0 +1 @@
1
+ json.array! @items, partial: "feeder/items/item", as: :item
@@ -1,3 +1,3 @@
1
1
  Feeder::Engine.routes.draw do
2
- root to: 'feeds#index', via: :get
2
+ resources :items, path: '/(.:format)', only: :index
3
3
  end
@@ -0,0 +1,5 @@
1
+ class AddStickyToFeederItems < ActiveRecord::Migration
2
+ def change
3
+ add_column :feeder_items, :sticky, :boolean, default: false
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ class ProhibitNullOnFeederItemStickies < ActiveRecord::Migration
2
+ class Feeder::Item < ActiveRecord::Base; end
3
+
4
+ def up
5
+ Feeder::Item.where(sticky: nil).update_all sticky: false
6
+
7
+ change_column :feeder_items, :sticky, :boolean, null: false
8
+ end
9
+
10
+ def down
11
+ change_column :feeder_items, :sticky, :boolean, null: true
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class AddBlockedToFeederItems < ActiveRecord::Migration
2
+ def change
3
+ add_column :feeder_items, :blocked, :boolean, null: false, default: false
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddReportedToFeederItems < ActiveRecord::Migration
2
+ def change
3
+ add_column :feeder_items, :reported, :boolean, null: false, default: false
4
+ end
5
+ end
@@ -1,5 +1,8 @@
1
1
  require "feeder/engine"
2
2
  require "feeder/configuration"
3
+ require "feeder/concerns"
4
+ require "feeder/active_record"
5
+ require "kaminari"
3
6
 
4
7
  module Feeder
5
8
 
@@ -9,9 +12,22 @@ module Feeder
9
12
  end
10
13
 
11
14
  def configure
12
- raise ArgumentError, "No block provided" unless block_given?
15
+ yield config if block_given?
16
+ end
17
+
18
+ # Set temporary configuration options for the duration of the given block.
19
+ #
20
+ # options - A Hash describing temporary configuration options.
21
+ def temporarily options = {}
22
+ original = @configuration.dup
23
+
24
+ options.each do |key, value|
25
+ @configuration.send "#{key}=", value
26
+ end
13
27
 
14
- yield config
28
+ yield
29
+ ensure
30
+ @configuration = original
15
31
  end
16
32
  end
17
33
 
@@ -0,0 +1,9 @@
1
+ module Feeder::ActiveRecord
2
+ module Extensions
3
+ def feedable
4
+ include Feeder::Concerns::Feedable
5
+ end
6
+ end
7
+ end
8
+
9
+ ::ActiveRecord::Base.send :extend, Feeder::ActiveRecord::Extensions if defined?(ActiveRecord)
@@ -0,0 +1,6 @@
1
+ module Feeder::Concerns
2
+ require 'feeder/concerns/controllers'
3
+ require 'feeder/concerns/helpers'
4
+ require 'feeder/concerns/feedable'
5
+ require 'feeder/concerns/models'
6
+ end
@@ -0,0 +1,5 @@
1
+ module Feeder
2
+ module Concerns::Controllers
3
+ require 'feeder/concerns/controllers/items_controller'
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ module Feeder
2
+ module Concerns::Controllers::ItemsController
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ respond_to :html, :json
7
+
8
+ def index
9
+ @items = Item.order(sticky: :desc)
10
+
11
+ Feeder.config.scopes.each do |scope|
12
+ @items = @items.instance_eval &scope
13
+ end
14
+
15
+ @items = @items.page(params[:page] || 1)
16
+ @items = @items.per(params[:limit] || 25)
17
+
18
+ respond_with @items
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ module Feeder::Concerns::Feedable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ attr_accessor :sticky
6
+
7
+ has_one :feeder_item, as: :feedable, class_name: 'Feeder::Item', dependent: :destroy
8
+
9
+ def sticky
10
+ if feeder_item
11
+ feeder_item.sticky
12
+ else
13
+ !!@sticky
14
+ end
15
+ end
16
+
17
+ def sticky= value
18
+ @sticky = value
19
+
20
+ if feeder_item
21
+ feeder_item.sticky = value
22
+ end
23
+ end
24
+
25
+ delegate :block, :unblock, :blocked?, :report, :unreport, :reported?, to: :feeder_item
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module Feeder
2
+ module Concerns::Helpers
3
+ require 'feeder/concerns/helpers/filter'
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ module Feeder
2
+ module Concerns::Helpers::Filter
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ scope :filter, ->(options) {
7
+ args = []
8
+ wheres = options.each.map do |feedable, ids|
9
+ ids = feedable.pluck :id if ids == :all
10
+
11
+ args << feedable << ids
12
+
13
+ "(feedable_type = ? AND feedable_id IN (?))"
14
+ end.join " OR "
15
+
16
+ where(wheres, *(args))
17
+ }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module Feeder
2
+ module Concerns::Models
3
+ require 'feeder/concerns/models/item'
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ module Feeder
2
+ module Concerns::Models::Item
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Feeder::Concerns::Helpers::Filter
7
+
8
+ belongs_to :feedable, polymorphic: true
9
+
10
+ def type
11
+ feedable_type.underscore
12
+ end
13
+
14
+ def report
15
+ self.update reported: true
16
+ end
17
+
18
+ def block
19
+ self.update blocked: true
20
+ end
21
+
22
+ def unreport
23
+ self.update reported: false
24
+ end
25
+
26
+ def unblock
27
+ self.update blocked: false
28
+ end
29
+ end
30
+ end
31
+ end