feedable 0.0.1 → 0.9.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.
Files changed (54) hide show
  1. data/README.md +56 -3
  2. data/lib/feedable.rb +75 -0
  3. data/lib/feedable/active_record.rb +16 -0
  4. data/lib/feedable/version.rb +1 -1
  5. data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
  6. data/test/dummy/app/controllers/alligators_controller.rb +83 -0
  7. data/test/dummy/app/controllers/posts_controller.rb +83 -0
  8. data/test/dummy/app/controllers/roosters_controller.rb +83 -0
  9. data/test/dummy/app/controllers/trumpets_controller.rb +83 -0
  10. data/test/dummy/app/helpers/alligators_helper.rb +2 -0
  11. data/test/dummy/app/helpers/posts_helper.rb +2 -0
  12. data/test/dummy/app/helpers/roosters_helper.rb +2 -0
  13. data/test/dummy/app/helpers/trumpets_helper.rb +2 -0
  14. data/test/dummy/app/models/alligator.rb +4 -0
  15. data/test/dummy/app/models/post.rb +4 -0
  16. data/test/dummy/app/models/rooster.rb +4 -0
  17. data/test/dummy/app/models/trumpet.rb +3 -0
  18. data/test/dummy/app/views/alligators/_form.html.erb +21 -0
  19. data/test/dummy/app/views/alligators/edit.html.erb +6 -0
  20. data/test/dummy/app/views/alligators/index.html.erb +23 -0
  21. data/test/dummy/app/views/alligators/new.html.erb +5 -0
  22. data/test/dummy/app/views/alligators/show.html.erb +10 -0
  23. data/test/dummy/app/views/posts/_form.html.erb +25 -0
  24. data/test/dummy/app/views/posts/edit.html.erb +6 -0
  25. data/test/dummy/app/views/posts/index.html.erb +25 -0
  26. data/test/dummy/app/views/posts/new.html.erb +5 -0
  27. data/test/dummy/app/views/posts/show.html.erb +15 -0
  28. data/test/dummy/app/views/roosters/_form.html.erb +25 -0
  29. data/test/dummy/app/views/roosters/edit.html.erb +6 -0
  30. data/test/dummy/app/views/roosters/index.html.erb +25 -0
  31. data/test/dummy/app/views/roosters/new.html.erb +5 -0
  32. data/test/dummy/app/views/roosters/show.html.erb +15 -0
  33. data/test/dummy/app/views/trumpets/_form.html.erb +25 -0
  34. data/test/dummy/app/views/trumpets/edit.html.erb +6 -0
  35. data/test/dummy/app/views/trumpets/index.html.erb +25 -0
  36. data/test/dummy/app/views/trumpets/new.html.erb +5 -0
  37. data/test/dummy/app/views/trumpets/show.html.erb +15 -0
  38. data/test/dummy/config/routes.rb +8 -0
  39. data/test/dummy/db/development.sqlite3 +0 -0
  40. data/test/dummy/db/migrate/20120509025908_create_posts.rb +10 -0
  41. data/test/dummy/db/migrate/20120509030117_create_alligators.rb +9 -0
  42. data/test/dummy/db/migrate/20120509030249_create_roosters.rb +10 -0
  43. data/test/dummy/db/migrate/20120509030326_create_trumpets.rb +10 -0
  44. data/test/dummy/db/schema.rb +43 -0
  45. data/test/dummy/db/test.sqlite3 +0 -0
  46. data/test/dummy/log/development.log +63 -0
  47. data/test/dummy/log/test.log +4666 -0
  48. data/test/feedable_test.rb +50 -3
  49. data/test/fixtures/alligators.yml +12 -0
  50. data/test/fixtures/posts.yml +15 -0
  51. data/test/fixtures/roosters.yml +15 -0
  52. data/test/fixtures/trumpets.yml +15 -0
  53. data/test/test_helper.rb +8 -2
  54. metadata +105 -12
data/README.md CHANGED
@@ -16,9 +16,13 @@ Requires Ruby version **>= 1.9.2** and ActiveRecord version **>= 3.0**
16
16
  Installation
17
17
  ------------
18
18
 
19
- Feedable should be installed as a gem in your app. Currently, it is only available as the latest-build on github. Fixed versions forth-coming.
19
+ Feedable should be installed as a gem in your app.
20
20
 
21
- For now, you have to live on the bleeding-edge:
21
+ Include the gem in your Gemfile:
22
+
23
+ gem "feedable"
24
+
25
+ Or, if you like to live on the bleeding-edge:
22
26
 
23
27
  gem "feedable", :git => "https://github.com/cavis/feedable"
24
28
 
@@ -26,14 +30,63 @@ For now, you have to live on the bleeding-edge:
26
30
  Getting Started
27
31
  ---------------
28
32
 
29
- ### TODO
33
+ ### In your models, indicate a datetime column to sort by:
34
+
35
+ class Alligator < ActiveRecord::Base
36
+ feedable :created_at
37
+ end
38
+
39
+ class Grizzly < ActiveRecord::Base
40
+ feedable :last_fed_at
41
+ end
42
+
43
+
44
+ Usage
45
+ -----
46
+
47
+ ### In your controller, or wherever you want to sort these records:
48
+
49
+ def activity
50
+ @recent_activity = feedable Alligator, Grizzly, :limit => 10
51
+ end
52
+
53
+ This will return an array of `ActiveRecords`, ordered by the specified datetime columns. The most recent activity will
54
+ be first, with a limit of 10. Keep reading... the advanced options will explain more about this.
55
+
56
+
57
+ Advanced Options
58
+ ----------------
59
+
60
+ Ah, you made it. Welcome!
61
+
62
+ When calling `feedable`, the first thing to pass is 1-or-more `ActiveRecord` queries. *Just make sure* you don't pass in something like `Alligator.all`, as this executes sql right away, and doesn't give `feedable` a chance to interject ordering.
63
+
64
+ However, you can pass in some fancy shmancy `ActiveRecord` conditions, includes, selects, and joins.
65
+
66
+ @recent = feedable Alligator.where('name = ?', 'charles').includes(:garden),
67
+ Grizzly.select('name', 'height'),
68
+ Walleye.joins(:guns).where('guns.type = "smart"'),
69
+ :limit => 10
70
+
71
+ Note that if you don't pass a limit, you get the default limit of 100 things returned. Additionally, you can specify an offset to `feedable`:
72
+
73
+ @recent = feedable Alligator, Grizzly, :limit => 10, :offset => 20
74
+
75
+ Finally, even though you usually want activity in a descending datetime order, you can also flip that around:
76
+
77
+ @recent = feedable Alligator, Grizzly, :limit => 10, :order => 'desc'
78
+ @oldest = feedable Alligator, Grizzly, :limit => 10, :order => 'asc'
79
+
30
80
 
31
81
  Issues and Contributing
32
82
  -----------------------
33
83
 
34
84
  Please, let me know about any bugs/feature-requests via the issues tracker. And if you'd like to contribute, send me a note! Thanks.
35
85
 
86
+
36
87
  License
37
88
  -------
38
89
 
39
90
  Feedable is free software, and may be redistributed under the MIT-LICENSE.
91
+
92
+ Thanks for listening!
@@ -1,2 +1,77 @@
1
1
  module Feedable
2
2
  end
3
+
4
+ # main function
5
+ def feedable(*args)
6
+ opts = { :limit => 100, :offset => 0, :order => :desc }
7
+
8
+ # look for options hash (and remove)
9
+ args.delete_if do |x|
10
+ if x.class == Hash
11
+ opts.merge!(x)
12
+ end
13
+ end
14
+
15
+ # abort if empty
16
+ clauses = []
17
+ return clauses unless args.length > 0
18
+
19
+ # generate clauses from args
20
+ args.each do |q|
21
+ tmp = (q.class == Class) ? q.select('*') : q.clone
22
+
23
+ unless tmp.class == ActiveRecord::Relation
24
+ raise "Invalid class #{tmp.klass} - should be an ActiveRecord::Relation"
25
+ end
26
+ unless tmp.attribute_method?(:feedable_col)
27
+ raise "Class #{tmp.klass} isn't feedable!"
28
+ end
29
+
30
+ # unset any order, limit and offset
31
+ tmp.order_values = []
32
+ tmp.limit_value = nil
33
+ tmp.offset_value = nil
34
+
35
+ # add to unions
36
+ tmp.select_values = ["#{tmp.primary_key} as key", "#{tmp.feedable_col} as dtim", "'#{tmp.klass}' as type"]
37
+ clauses.push tmp.to_sql
38
+ end
39
+
40
+ # join together and execute
41
+ unions = clauses.join(' UNION ')
42
+ paging = "order by dtim #{opts[:order]} limit #{opts[:limit]} offset #{opts[:offset]}"
43
+ query = "select * from (#{unions}) " + paging
44
+ all_ids = ActiveRecord::Base.connection.select_all(query)
45
+ all_recs = []
46
+
47
+ # fetch the database objects
48
+ args.each do |q|
49
+ q = q.select('*') if (q.class == Class)
50
+
51
+ # remove unnecessary clauses
52
+ q.where_values = []
53
+ q.order_values = []
54
+ q.limit_value = nil
55
+ q.offset_value = nil
56
+
57
+ # collect ids
58
+ ids = all_ids.select{|x| x['type'] == q.klass.name}.collect{|x| x['key']}
59
+ recs = q.find(ids)
60
+
61
+ # attach to the output
62
+ recs.each do |r|
63
+ idx = all_ids.index {|x| x['key'] == r.id}
64
+ all_recs[idx] = r
65
+ end
66
+ end
67
+
68
+ # return just the records, in order
69
+ return all_recs
70
+ end
71
+
72
+ # activerecord "blameable"
73
+ if defined?(ActiveRecord)
74
+ require 'feedable/active_record'
75
+ else
76
+ raise "feedable only works with active_record"
77
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_record'
2
+
3
+ module Feedable
4
+ module FeedableRecord
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def feedable(col)
9
+ class_attribute :feedable_col
10
+ self.feedable_col = col
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ ActiveRecord::Base.send :include, Feedable::FeedableRecord
@@ -1,3 +1,3 @@
1
1
  module Feedable
2
- VERSION = "0.0.1"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -0,0 +1,56 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ div.field, div.actions {
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ #notice {
24
+ color: green;
25
+ }
26
+
27
+ .field_with_errors {
28
+ padding: 2px;
29
+ background-color: red;
30
+ display: table;
31
+ }
32
+
33
+ #error_explanation {
34
+ width: 450px;
35
+ border: 2px solid red;
36
+ padding: 7px;
37
+ padding-bottom: 0;
38
+ margin-bottom: 20px;
39
+ background-color: #f0f0f0;
40
+ }
41
+
42
+ #error_explanation h2 {
43
+ text-align: left;
44
+ font-weight: bold;
45
+ padding: 5px 5px 5px 15px;
46
+ font-size: 12px;
47
+ margin: -7px;
48
+ margin-bottom: 0px;
49
+ background-color: #c00;
50
+ color: #fff;
51
+ }
52
+
53
+ #error_explanation ul li {
54
+ font-size: 12px;
55
+ list-style: square;
56
+ }
@@ -0,0 +1,83 @@
1
+ class AlligatorsController < ApplicationController
2
+ # GET /alligators
3
+ # GET /alligators.json
4
+ def index
5
+ @alligators = Alligator.all
6
+
7
+ respond_to do |format|
8
+ format.html # index.html.erb
9
+ format.json { render json: @alligators }
10
+ end
11
+ end
12
+
13
+ # GET /alligators/1
14
+ # GET /alligators/1.json
15
+ def show
16
+ @alligator = Alligator.find(params[:id])
17
+
18
+ respond_to do |format|
19
+ format.html # show.html.erb
20
+ format.json { render json: @alligator }
21
+ end
22
+ end
23
+
24
+ # GET /alligators/new
25
+ # GET /alligators/new.json
26
+ def new
27
+ @alligator = Alligator.new
28
+
29
+ respond_to do |format|
30
+ format.html # new.html.erb
31
+ format.json { render json: @alligator }
32
+ end
33
+ end
34
+
35
+ # GET /alligators/1/edit
36
+ def edit
37
+ @alligator = Alligator.find(params[:id])
38
+ end
39
+
40
+ # POST /alligators
41
+ # POST /alligators.json
42
+ def create
43
+ @alligator = Alligator.new(params[:alligator])
44
+
45
+ respond_to do |format|
46
+ if @alligator.save
47
+ format.html { redirect_to @alligator, notice: 'Alligator was successfully created.' }
48
+ format.json { render json: @alligator, status: :created, location: @alligator }
49
+ else
50
+ format.html { render action: "new" }
51
+ format.json { render json: @alligator.errors, status: :unprocessable_entity }
52
+ end
53
+ end
54
+ end
55
+
56
+ # PUT /alligators/1
57
+ # PUT /alligators/1.json
58
+ def update
59
+ @alligator = Alligator.find(params[:id])
60
+
61
+ respond_to do |format|
62
+ if @alligator.update_attributes(params[:alligator])
63
+ format.html { redirect_to @alligator, notice: 'Alligator was successfully updated.' }
64
+ format.json { head :no_content }
65
+ else
66
+ format.html { render action: "edit" }
67
+ format.json { render json: @alligator.errors, status: :unprocessable_entity }
68
+ end
69
+ end
70
+ end
71
+
72
+ # DELETE /alligators/1
73
+ # DELETE /alligators/1.json
74
+ def destroy
75
+ @alligator = Alligator.find(params[:id])
76
+ @alligator.destroy
77
+
78
+ respond_to do |format|
79
+ format.html { redirect_to alligators_url }
80
+ format.json { head :no_content }
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,83 @@
1
+ class PostsController < ApplicationController
2
+ # GET /posts
3
+ # GET /posts.json
4
+ def index
5
+ @posts = Post.all
6
+
7
+ respond_to do |format|
8
+ format.html # index.html.erb
9
+ format.json { render json: @posts }
10
+ end
11
+ end
12
+
13
+ # GET /posts/1
14
+ # GET /posts/1.json
15
+ def show
16
+ @post = Post.find(params[:id])
17
+
18
+ respond_to do |format|
19
+ format.html # show.html.erb
20
+ format.json { render json: @post }
21
+ end
22
+ end
23
+
24
+ # GET /posts/new
25
+ # GET /posts/new.json
26
+ def new
27
+ @post = Post.new
28
+
29
+ respond_to do |format|
30
+ format.html # new.html.erb
31
+ format.json { render json: @post }
32
+ end
33
+ end
34
+
35
+ # GET /posts/1/edit
36
+ def edit
37
+ @post = Post.find(params[:id])
38
+ end
39
+
40
+ # POST /posts
41
+ # POST /posts.json
42
+ def create
43
+ @post = Post.new(params[:post])
44
+
45
+ respond_to do |format|
46
+ if @post.save
47
+ format.html { redirect_to @post, notice: 'Post was successfully created.' }
48
+ format.json { render json: @post, status: :created, location: @post }
49
+ else
50
+ format.html { render action: "new" }
51
+ format.json { render json: @post.errors, status: :unprocessable_entity }
52
+ end
53
+ end
54
+ end
55
+
56
+ # PUT /posts/1
57
+ # PUT /posts/1.json
58
+ def update
59
+ @post = Post.find(params[:id])
60
+
61
+ respond_to do |format|
62
+ if @post.update_attributes(params[:post])
63
+ format.html { redirect_to @post, notice: 'Post was successfully updated.' }
64
+ format.json { head :no_content }
65
+ else
66
+ format.html { render action: "edit" }
67
+ format.json { render json: @post.errors, status: :unprocessable_entity }
68
+ end
69
+ end
70
+ end
71
+
72
+ # DELETE /posts/1
73
+ # DELETE /posts/1.json
74
+ def destroy
75
+ @post = Post.find(params[:id])
76
+ @post.destroy
77
+
78
+ respond_to do |format|
79
+ format.html { redirect_to posts_url }
80
+ format.json { head :no_content }
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,83 @@
1
+ class RoostersController < ApplicationController
2
+ # GET /roosters
3
+ # GET /roosters.json
4
+ def index
5
+ @roosters = Rooster.all
6
+
7
+ respond_to do |format|
8
+ format.html # index.html.erb
9
+ format.json { render json: @roosters }
10
+ end
11
+ end
12
+
13
+ # GET /roosters/1
14
+ # GET /roosters/1.json
15
+ def show
16
+ @rooster = Rooster.find(params[:id])
17
+
18
+ respond_to do |format|
19
+ format.html # show.html.erb
20
+ format.json { render json: @rooster }
21
+ end
22
+ end
23
+
24
+ # GET /roosters/new
25
+ # GET /roosters/new.json
26
+ def new
27
+ @rooster = Rooster.new
28
+
29
+ respond_to do |format|
30
+ format.html # new.html.erb
31
+ format.json { render json: @rooster }
32
+ end
33
+ end
34
+
35
+ # GET /roosters/1/edit
36
+ def edit
37
+ @rooster = Rooster.find(params[:id])
38
+ end
39
+
40
+ # POST /roosters
41
+ # POST /roosters.json
42
+ def create
43
+ @rooster = Rooster.new(params[:rooster])
44
+
45
+ respond_to do |format|
46
+ if @rooster.save
47
+ format.html { redirect_to @rooster, notice: 'Rooster was successfully created.' }
48
+ format.json { render json: @rooster, status: :created, location: @rooster }
49
+ else
50
+ format.html { render action: "new" }
51
+ format.json { render json: @rooster.errors, status: :unprocessable_entity }
52
+ end
53
+ end
54
+ end
55
+
56
+ # PUT /roosters/1
57
+ # PUT /roosters/1.json
58
+ def update
59
+ @rooster = Rooster.find(params[:id])
60
+
61
+ respond_to do |format|
62
+ if @rooster.update_attributes(params[:rooster])
63
+ format.html { redirect_to @rooster, notice: 'Rooster was successfully updated.' }
64
+ format.json { head :no_content }
65
+ else
66
+ format.html { render action: "edit" }
67
+ format.json { render json: @rooster.errors, status: :unprocessable_entity }
68
+ end
69
+ end
70
+ end
71
+
72
+ # DELETE /roosters/1
73
+ # DELETE /roosters/1.json
74
+ def destroy
75
+ @rooster = Rooster.find(params[:id])
76
+ @rooster.destroy
77
+
78
+ respond_to do |format|
79
+ format.html { redirect_to roosters_url }
80
+ format.json { head :no_content }
81
+ end
82
+ end
83
+ end