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.
- 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
|