feedable 0.0.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +56 -3
- data/lib/feedable.rb +75 -0
- data/lib/feedable/active_record.rb +16 -0
- data/lib/feedable/version.rb +1 -1
- data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/test/dummy/app/controllers/alligators_controller.rb +83 -0
- data/test/dummy/app/controllers/posts_controller.rb +83 -0
- data/test/dummy/app/controllers/roosters_controller.rb +83 -0
- data/test/dummy/app/controllers/trumpets_controller.rb +83 -0
- data/test/dummy/app/helpers/alligators_helper.rb +2 -0
- data/test/dummy/app/helpers/posts_helper.rb +2 -0
- data/test/dummy/app/helpers/roosters_helper.rb +2 -0
- data/test/dummy/app/helpers/trumpets_helper.rb +2 -0
- data/test/dummy/app/models/alligator.rb +4 -0
- data/test/dummy/app/models/post.rb +4 -0
- data/test/dummy/app/models/rooster.rb +4 -0
- data/test/dummy/app/models/trumpet.rb +3 -0
- data/test/dummy/app/views/alligators/_form.html.erb +21 -0
- data/test/dummy/app/views/alligators/edit.html.erb +6 -0
- data/test/dummy/app/views/alligators/index.html.erb +23 -0
- data/test/dummy/app/views/alligators/new.html.erb +5 -0
- data/test/dummy/app/views/alligators/show.html.erb +10 -0
- data/test/dummy/app/views/posts/_form.html.erb +25 -0
- data/test/dummy/app/views/posts/edit.html.erb +6 -0
- data/test/dummy/app/views/posts/index.html.erb +25 -0
- data/test/dummy/app/views/posts/new.html.erb +5 -0
- data/test/dummy/app/views/posts/show.html.erb +15 -0
- data/test/dummy/app/views/roosters/_form.html.erb +25 -0
- data/test/dummy/app/views/roosters/edit.html.erb +6 -0
- data/test/dummy/app/views/roosters/index.html.erb +25 -0
- data/test/dummy/app/views/roosters/new.html.erb +5 -0
- data/test/dummy/app/views/roosters/show.html.erb +15 -0
- data/test/dummy/app/views/trumpets/_form.html.erb +25 -0
- data/test/dummy/app/views/trumpets/edit.html.erb +6 -0
- data/test/dummy/app/views/trumpets/index.html.erb +25 -0
- data/test/dummy/app/views/trumpets/new.html.erb +5 -0
- data/test/dummy/app/views/trumpets/show.html.erb +15 -0
- data/test/dummy/config/routes.rb +8 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20120509025908_create_posts.rb +10 -0
- data/test/dummy/db/migrate/20120509030117_create_alligators.rb +9 -0
- data/test/dummy/db/migrate/20120509030249_create_roosters.rb +10 -0
- data/test/dummy/db/migrate/20120509030326_create_trumpets.rb +10 -0
- data/test/dummy/db/schema.rb +43 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +63 -0
- data/test/dummy/log/test.log +4666 -0
- data/test/feedable_test.rb +50 -3
- data/test/fixtures/alligators.yml +12 -0
- data/test/fixtures/posts.yml +15 -0
- data/test/fixtures/roosters.yml +15 -0
- data/test/fixtures/trumpets.yml +15 -0
- data/test/test_helper.rb +8 -2
- 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.
|
19
|
+
Feedable should be installed as a gem in your app.
|
20
20
|
|
21
|
-
|
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
|
-
###
|
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!
|
data/lib/feedable.rb
CHANGED
@@ -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
|
data/lib/feedable/version.rb
CHANGED
@@ -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
|