feedable 0.0.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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