one_touch 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +14 -0
- data/Rakefile +6 -0
- data/lib/one_touch/engine.rb +4 -0
- data/lib/one_touch/models/active_record_extension.rb +142 -0
- data/lib/one_touch/models/acts_as_favor.rb +153 -0
- data/lib/one_touch/models/array.rb +12 -0
- data/lib/one_touch/models/as_favor_host.rb +68 -0
- data/lib/one_touch/models/as_favorable.rb +58 -0
- data/lib/one_touch/models/host_relation.rb +31 -0
- data/lib/one_touch/models/relations.rb +62 -0
- data/lib/one_touch/railtie.rb +14 -0
- data/lib/one_touch/version.rb +3 -0
- data/lib/one_touch.rb +3 -0
- data/log/development.log +0 -0
- data/one_touch.gemspec +24 -0
- data/readme.md +122 -0
- data/spec/fake_app.rb +76 -0
- data/spec/models/acts_as_favor_spec.rb +100 -0
- data/spec/models/array_spec.rb +10 -0
- data/spec/models/as_favor_host_spec.rb +21 -0
- data/spec/models/as_favorable_spec.rb +109 -0
- data/spec/models/load_test_data.rb +17 -0
- data/spec/spec_helper.rb +36 -0
- metadata +81 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in one_touch.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
# gem 'cancan', '~> 1.6' # when add controller code in gem
|
7
|
+
gem 'rake'
|
8
|
+
gem 'rspec'
|
9
|
+
gem 'rspec-rails'
|
10
|
+
gem 'ruby-debug19'
|
11
|
+
gem 'rails', '3.0.10'
|
12
|
+
gem 'meta_where', '~> 1.0.0'
|
13
|
+
# gem 'squeel' # add another version to support it and Rails3.1
|
14
|
+
gem 'sqlite3', '~> 1.3.0', :group => :test
|
data/Rakefile
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),'acts_as_favor')
|
2
|
+
require File.join(File.dirname(__FILE__),'as_favor_host')
|
3
|
+
require File.join(File.dirname(__FILE__),'as_favorable')
|
4
|
+
require File.join(File.dirname(__FILE__),'relations')
|
5
|
+
require File.join(File.dirname(__FILE__),'host_relation')
|
6
|
+
|
7
|
+
module OneTouch
|
8
|
+
module ActiveRecordExtension
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# Default is define two relations:
|
18
|
+
# belongs_to :host, :class_name => "User"
|
19
|
+
# belongs_to :favorable, :polymorphic => true
|
20
|
+
# Set acts_as_favor :host_class_name => nil to override default host relation by
|
21
|
+
# belongs_to :host, :polymorphic => true
|
22
|
+
# User can define his own specific relations, but make sure two relations name must be host and favorable
|
23
|
+
# and use acts_as_favor :no_default_relations => false after user defined relations
|
24
|
+
# Ex: belongs_to :host, :class_name => "AnyOne", :primary_key => :any_key
|
25
|
+
# belongs_to :tag
|
26
|
+
# acts_as_favor :no_default_relations => true
|
27
|
+
def acts_as_favor(opt={})
|
28
|
+
|
29
|
+
opt.reverse_merge!({:include_host_relation_module => true, :host_class_name => "User", :no_default_relations => false})
|
30
|
+
class_attribute :host_relation_type, :favorable_relation_type, :instance_writer => false
|
31
|
+
|
32
|
+
method_name_of_host_klasses = "host_klasses_for_#{name.underscore}"
|
33
|
+
|
34
|
+
include ActsAsFavor
|
35
|
+
|
36
|
+
unless opt[:no_default_relations]
|
37
|
+
if opt[:host_class_name].nil?
|
38
|
+
belongs_to :host, :polymorphic => true
|
39
|
+
host_relation_type = :polymorphic
|
40
|
+
if respond_to? method_name_of_host_klasses
|
41
|
+
send(method_name_of_host_klasses).each do |k|
|
42
|
+
k.to_s.constantize.rewrite_join_favors if k.to_s.constantize.respond_to? :rewrite_join_favors
|
43
|
+
end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
belongs_to :host, :class_name => opt[:host_class_name]
|
47
|
+
host_relation_type = :single
|
48
|
+
end
|
49
|
+
if opt[:favorable_class_name].nil?
|
50
|
+
belongs_to :favorable, :polymorphic => true
|
51
|
+
favorable_relation_type = :polymorphic
|
52
|
+
else
|
53
|
+
belongs_to :favorable, :class_name => opt[:favorable_class_name]
|
54
|
+
favorable_relation_type = :single
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if opt[:include_host_relation_module]
|
59
|
+
class_attribute :include_host_relation_module, :instance_writer => false
|
60
|
+
self.include_host_relation_module = true
|
61
|
+
add_host_relation_map(favorable_klasses, :be_favors, :host)
|
62
|
+
end
|
63
|
+
|
64
|
+
if respond_to? method_name_of_host_klasses
|
65
|
+
send(method_name_of_host_klasses).each do |k|
|
66
|
+
k.to_s.constantize.after_load_acts_as_favor if k.to_s.constantize.respond_to? :after_load_acts_as_favor
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Used in host class
|
72
|
+
# Ex:
|
73
|
+
# as_favor_host :klass => "OtherClass"
|
74
|
+
#
|
75
|
+
def as_favor_host(opt={})
|
76
|
+
opt = { :klass => "Favor", :no_default_relations => false }.merge opt
|
77
|
+
|
78
|
+
assign_cattr_in_favor_class(:host_klasses,opt)
|
79
|
+
# The Name of has_many is stick to favors, to make it changable would cause some query problem
|
80
|
+
# :as => :host can not be configured too, cause in act_as_favor, the relation name was sticked
|
81
|
+
unless opt[:no_default_relations]
|
82
|
+
has_many :favors, :class_name => opt[:klass], :as => :host
|
83
|
+
end
|
84
|
+
include AsFavorHost
|
85
|
+
end
|
86
|
+
|
87
|
+
# Used in favorable class
|
88
|
+
def as_favorable(opt={})
|
89
|
+
opt = { :klass => "Favor", :no_default_relations => false }.merge opt
|
90
|
+
|
91
|
+
assign_cattr_in_favor_class(:favorable_klasses,opt)
|
92
|
+
unless opt[:no_default_relations]
|
93
|
+
has_many :be_favors, :class_name => opt[:klass], :as => :favorable
|
94
|
+
end
|
95
|
+
include AsFavorable
|
96
|
+
include HostRelation if favor_class.respond_to? :include_host_relation_module and favor_class.include_host_relation_module = true
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def add_host_relation_map(favorable_klasses, *two_connection_name)
|
102
|
+
class_attribute :favorable_to_media, :media_to_host, :instance_writer => false
|
103
|
+
unless respond_to? :favorable_klasses
|
104
|
+
class_attribute :favorable_klasses
|
105
|
+
self.favorable_klasses = favorable_klasses
|
106
|
+
end
|
107
|
+
self.favorable_to_media = two_connection_name.first
|
108
|
+
self.media_to_host = two_connection_name[1]
|
109
|
+
|
110
|
+
include Relations
|
111
|
+
self.target_to_media = :be_favors
|
112
|
+
self.media_to_host = :host
|
113
|
+
|
114
|
+
favorable_klasses.each do |k|
|
115
|
+
klass = k.to_s.constantize
|
116
|
+
klass.send :include, HostRelation # host_relation model is added to favorable klass
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Programmer Doc
|
121
|
+
# To maintain the connection betwwen host, favorable and favor class,
|
122
|
+
# I defined class_attribute in there super class AR::Base
|
123
|
+
# I don't know if there is any good solution to not disturb AR::Base
|
124
|
+
def assign_cattr_in_favor_class(cattr_name,opt)
|
125
|
+
class_attribute :favor_klass, :favor_class, :instance_writer => false
|
126
|
+
self.favor_klass = opt[:klass]
|
127
|
+
self.favor_class = favor_klass.constantize
|
128
|
+
|
129
|
+
# favor_class.acts_as_favor :just_define_host_klasses => true unless favor_class.respond_to? :host_klasses
|
130
|
+
# favor.class.acts_as_favor
|
131
|
+
cattr_name = "#{cattr_name}_for_#{opt[:klass].underscore}".to_sym
|
132
|
+
unless ::ActiveRecord::Base.respond_to? cattr_name
|
133
|
+
::ActiveRecord::Base.class_attribute cattr_name, :instance_writer => false
|
134
|
+
::ActiveRecord::Base.send "#{cattr_name}=", []
|
135
|
+
end
|
136
|
+
(::ActiveRecord::Base.send cattr_name) << self.name.to_sym
|
137
|
+
end
|
138
|
+
|
139
|
+
end # End ClassMethods
|
140
|
+
|
141
|
+
end # ActiveRecordExtension
|
142
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module OneTouch
|
3
|
+
module ActsAsFavor
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
|
8
|
+
class_attribute :cons_context, :cons_context_match, :context_relation, :instance_writer => false
|
9
|
+
self.cons_context = self::CONTEXT
|
10
|
+
self.cons_context_match = self::CONTEXT_MATCH
|
11
|
+
self.context_relation = self::CONTEXT_RELATION if defined? CONTEXT_RELATION
|
12
|
+
|
13
|
+
# Seems there are many hook work to do, oppose hook, belongs hook ....
|
14
|
+
# All needed hook should be just about relations between contexts
|
15
|
+
# Other hooks should be write by user
|
16
|
+
after_save :context_relation_hook if context_relation.presence
|
17
|
+
|
18
|
+
validates_inclusion_of :context,:in => cons_context, :message => "%{value} not included in CONTEXT"
|
19
|
+
|
20
|
+
validate :context_should_match_favorable_type
|
21
|
+
validate :host_should_differ_favorable
|
22
|
+
|
23
|
+
scope :context_as, lambda { |context| where( { :context => context } ) if context.present? }
|
24
|
+
|
25
|
+
# Generate build or destroy methods like:
|
26
|
+
# Favor.add_focus, Favor.del_focus
|
27
|
+
cons_context.each do |context|
|
28
|
+
generate_del_methods context
|
29
|
+
generate_add_methods context
|
30
|
+
end
|
31
|
+
|
32
|
+
# generate scope method like :
|
33
|
+
# scope :context_as_follow , where(:context => follow)
|
34
|
+
cons_context.each do |cont|
|
35
|
+
method_name = "context_as_#{cont}".to_sym
|
36
|
+
scope method_name, where(:context => cont)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Here add some dynamic scope query methods
|
40
|
+
# the method name is context + favorable_type(transfer it to a valid method string)
|
41
|
+
# for example,follow_investors retrive all record which context is follow and favorable type is investor
|
42
|
+
# sample output :
|
43
|
+
# scope :focus_tags , where(:favorable_type => "Tag",:context => "focus")
|
44
|
+
# This function seems not useful in real life
|
45
|
+
#
|
46
|
+
# cons_context_match.each do |context,favorable|
|
47
|
+
# favorable.each do |klass|
|
48
|
+
# method_name = "#{context}_#{klass.to_s.tableize.tr('/','_')}".to_sym
|
49
|
+
# scope method_name, where(:favorable_type => klass.to_s,:context => context.to_s)
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
|
53
|
+
# just used in test
|
54
|
+
# But in production, I think it should be run once at init
|
55
|
+
# write how to use it in readme.md
|
56
|
+
def self.context_match_keys_should_belongs_to_context
|
57
|
+
cons_context_match.keys.all? { |key| cons_context.include? key.to_s }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
module ClassMethods
|
63
|
+
|
64
|
+
# Generate dynamic class methods like add_focus, add_follow
|
65
|
+
# Favor.add_focus has at least two parameters as add_focus(operator,resource)
|
66
|
+
# the operator is someone(maybe something) who has taken an action(context) on a resource
|
67
|
+
# the resource is target
|
68
|
+
# return an object with error when create favor failed
|
69
|
+
#
|
70
|
+
# Even method could receive a block to assign more attributes, but it is not recommended
|
71
|
+
# Favor is designed to handle simple data structure, mostly for simple relation data
|
72
|
+
# just the user click one button or touch one link, then a favor was created
|
73
|
+
def generate_add_methods(name)
|
74
|
+
method_name = "add_#{name}".to_sym
|
75
|
+
define_singleton_method method_name do |operator,resource,other_field_option={}|
|
76
|
+
build_hash = other_field_option.merge(context: name)
|
77
|
+
favor = operator.favors.build build_hash
|
78
|
+
favor.favorable = resource
|
79
|
+
yield favor if block_given?
|
80
|
+
favor.save ? true : favor # check rails create method to make this ok
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Generate dynamic class methods like del_focus, del_follow
|
85
|
+
# Most like add_focus, but raise errors when delete favor failed
|
86
|
+
# TODO: add exception object when raise errors or use the same raise of destroy
|
87
|
+
def generate_del_methods(name)
|
88
|
+
method_name = "del_#{name}".to_sym
|
89
|
+
define_singleton_method method_name do |ope,res,other_field_option={}|
|
90
|
+
query_hash = other_field_option.merge(context: name, favorable: res)
|
91
|
+
favor = ope.favors.where(query_hash)
|
92
|
+
if favor.present?
|
93
|
+
favor.first.destroy
|
94
|
+
else
|
95
|
+
false
|
96
|
+
# raise "delete favor failed,can not find favor object",caller
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# seems just used for relations module
|
102
|
+
def host_klasses
|
103
|
+
attr = "host_klasses_for_#{name.underscore}".to_sym
|
104
|
+
respond_to?(attr) ? send(attr) : []
|
105
|
+
end
|
106
|
+
|
107
|
+
def favorable_klasses
|
108
|
+
attr = "favorable_klasses_for_#{name.underscore}".to_sym
|
109
|
+
respond_to?(attr) ? send(attr) : []
|
110
|
+
end
|
111
|
+
|
112
|
+
def favorable_to_media
|
113
|
+
:be_favors
|
114
|
+
end
|
115
|
+
|
116
|
+
def media_to_host
|
117
|
+
:host
|
118
|
+
end
|
119
|
+
end # end ClassMethods
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def host_should_differ_favorable
|
124
|
+
if host_id == favorable_id and host_type == favorable_type
|
125
|
+
errors.add(:favorable_id," can not the same as the host_id when types of host and favorable are the same ")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def context_should_match_favorable_type
|
130
|
+
unless cons_context_match[context.to_sym].present? and
|
131
|
+
cons_context_match[context.to_sym].include? favorable_type.to_sym
|
132
|
+
errors.add(:context," and favorable type not included in context match")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# how to reslove two or more hook
|
137
|
+
def context_relation_hook
|
138
|
+
context_relation.each do |key,contexts|
|
139
|
+
if contexts.include? context.to_sym
|
140
|
+
other_context = contexts.select { |con| con != context.to_sym }.first
|
141
|
+
send("relation_#{key}_hook",other_context) if self.class.private_method_defined? "relation_#{key}_hook"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def relation_oppose_hook(other_context)
|
147
|
+
oppose_context = other_context
|
148
|
+
oppose_favor= Favor.where(:host => host, :context => oppose_context, :favorable => favorable)
|
149
|
+
oppose_favor.first.destroy if oppose_favor.presence
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module OneTouch
|
2
|
+
module AsFavorHost
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
|
7
|
+
class_attribute :default_context_in_host, :instance_writer => false
|
8
|
+
|
9
|
+
if favor_class.respond_to? :host_relation_type and favor_class.host_relation_type == :polymorphic
|
10
|
+
scope :join_favors, joins(:favors.type(self))
|
11
|
+
else
|
12
|
+
scope :join_favors, joins(:favors)
|
13
|
+
end
|
14
|
+
|
15
|
+
scope :favorable_is, lambda { |sth| where(:favors => { :favorable => sth } ) }
|
16
|
+
|
17
|
+
# One module may not only host but also favorable, ex: User model
|
18
|
+
# when User as favorable, defined a method called context_as, so here define another method to distinct the former
|
19
|
+
scope :host_context_as, lambda { |context| where(:favors => { :context => context } ) }
|
20
|
+
|
21
|
+
if favor_class.respond_to? :host_relation_type # means favor class has load acts_as_favor
|
22
|
+
build_context_klass_methods
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
|
28
|
+
# if favor_class.host_relation_type == :polymorphic, then this will be automated executed
|
29
|
+
def rewrite_join_favors
|
30
|
+
scope :join_favors, joins(:favors.type(self))
|
31
|
+
end
|
32
|
+
|
33
|
+
def favor_to(favorable,context=default_context_in_host)
|
34
|
+
join_favors.host_context_as(context).
|
35
|
+
favorable_is(favorable)
|
36
|
+
end
|
37
|
+
|
38
|
+
def after_load_acts_as_favor
|
39
|
+
build_context_klass_methods
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_context_klass_methods
|
43
|
+
favor_class.cons_context_match.each do |context, klasses|
|
44
|
+
klasses.each do |klass|
|
45
|
+
method_name = "#{context}_#{klass.to_s.underscore.pluralize}".to_sym
|
46
|
+
define_method method_name do
|
47
|
+
klass.to_s.constantize.favored_by(self,context)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end # End ClassMethods
|
54
|
+
|
55
|
+
# find the favor to target with specific context
|
56
|
+
# return ARelation object
|
57
|
+
def find_favor(target,context)
|
58
|
+
favors.where(context: context,favorable: target)
|
59
|
+
end
|
60
|
+
|
61
|
+
# check favor exist or not
|
62
|
+
# params is context and target
|
63
|
+
def favor?(target,context)
|
64
|
+
find_favor(target,context).present? ? true : false
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module OneTouch
|
2
|
+
module AsFavorable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :default_context_in_favorable, :instance_writer => false
|
7
|
+
|
8
|
+
scope :join_be_favors, joins(:be_favors.type(self))
|
9
|
+
|
10
|
+
scope :host_is, lambda { |someone| where(:be_favors => { :host => someone } ) }
|
11
|
+
scope :context_as, lambda { |context| where(:be_favors => { :context => context } ) if context.present? }
|
12
|
+
scope :group_by_favorable, group("#{favor_klass.downcase.pluralize}.favorable_id")
|
13
|
+
# scope :includes_favors_and_hosts, includes(:be_favors => :host)
|
14
|
+
|
15
|
+
end # included
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
def allstars(context=default_context_in_favorable)
|
20
|
+
join_be_favors.context_as(context).group_by_favorable.
|
21
|
+
select("count(*) as counter, #{self.table_name}.*").order("counter desc")
|
22
|
+
end
|
23
|
+
|
24
|
+
def favored_by(user,context = default_context_in_favorable)
|
25
|
+
join_be_favors.context_as(context).
|
26
|
+
host_is(user)
|
27
|
+
end
|
28
|
+
|
29
|
+
def unfavored_by(user,context = default_context_in_favorable)
|
30
|
+
favored_by_ids = favored_by(user,context).map(&:id).presence || [0]
|
31
|
+
where(:id.not_in => favored_by_ids)
|
32
|
+
end
|
33
|
+
|
34
|
+
end # ClassMethods
|
35
|
+
|
36
|
+
def favors_count(cont=default_context_in_favorable)
|
37
|
+
be_favors.context_as(cont).count
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return host list
|
41
|
+
# same method to Host.favor_to, but different aspect
|
42
|
+
def favorers(host = default_host, context = default_context_in_favorable)
|
43
|
+
host.favor_to(self, context)
|
44
|
+
end
|
45
|
+
|
46
|
+
def favored_by?(ahost, context= default_context_in_favorable)
|
47
|
+
be_favors.where(:host => ahost, :context => context).present?
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Should be Overrided in favorable class
|
53
|
+
def default_host
|
54
|
+
::User
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module OneTouch
|
2
|
+
module HostRelation
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# results like
|
11
|
+
# { [:focus, @tagA] => [ User1, User2, User3] , [:dislike, @tagB] => [ User2, User5 ] ... }
|
12
|
+
# or { [ nil, @tagA] => [ Article1, Post2, User3] , [:nil, @tagB] => [ User2, User5 ] ... }
|
13
|
+
# or { [[:Tag, 1], :focus] => [ [:User, 1], [:User, 2] ... ] } or
|
14
|
+
# simply is { [:Tag, :focus] => [ [:User, 1], [:User, 2] ... ] } or
|
15
|
+
# just as { 1 => [ [:User, 1], [:User, 2] ... ] }, 1 is the relation point
|
16
|
+
# { TagA => [ :focus, User1, User2, User3] , TagB => [ :like, User2, User5 ] }
|
17
|
+
def host_relations(to_media, media_to_host)
|
18
|
+
includes(to_media.to_sym => media_to_host.to_sym).inject({}) do |memo,favorable|
|
19
|
+
favorable.send(to_media).each do |favor|
|
20
|
+
favor.respond_to?(:context) ? (context = favor.context) : (context = nil)
|
21
|
+
combine_key = [context, favorable]
|
22
|
+
|
23
|
+
host = favor.send media_to_host
|
24
|
+
memo.keys.include?(combine_key) ? memo[combine_key] << host : memo[combine_key] = [host]
|
25
|
+
end
|
26
|
+
memo
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end # End ClassMethods
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module OneTouch
|
2
|
+
module Relations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
# should add be_favors and host as a class attributes
|
7
|
+
class_attribute :favorable_klasses unless respond_to? :favorable_klasses
|
8
|
+
class_attribute :target_to_media, :media_to_host, :instance_writer => false
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# build all favorable_to_host_relation
|
14
|
+
# results should be like Tag.host_relations
|
15
|
+
def build_relations
|
16
|
+
favorable_klasses.inject({}) do |memo,klass|
|
17
|
+
memo.merge(klass.to_s.constantize.host_relations(target_to_media, media_to_host))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# match relation and relation_rules, if relation did not match any rule, return 0 point
|
22
|
+
# This methods can be override by user
|
23
|
+
def matching_mark(relation_key)
|
24
|
+
@rule ||= relation_rules
|
25
|
+
transfer_key = relation_key.map { |ele| ele.is_a?(ActiveRecord::Base) ? ele.class.name.to_sym : ele.to_sym }
|
26
|
+
@rule[transfer_key].presence || 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# Transfer ([User1, User2, User3], mark 5) to { User1 => { User2 => 5, User3 => 5 } ...}
|
30
|
+
# and combine the origin relation
|
31
|
+
# the code is a little ugly
|
32
|
+
def array_hosts_to_hash(origin_relation,hosts,mark)
|
33
|
+
hosts.inject(origin_relation) do |memo,host|
|
34
|
+
each_relations= hosts.each_relations_on(host,mark)
|
35
|
+
# the old_relation and new_relation both are Hash
|
36
|
+
memo.merge!(host => each_relations) do |key,old_r,new_r|
|
37
|
+
old_r.merge!(new_r) do |key,old_mark,new_mark|
|
38
|
+
old_mark + new_mark
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Transfer favorable_to_host_relation to host_to_host_relations
|
45
|
+
# TODO: use fiber to improve performance
|
46
|
+
# TODO: No test for it
|
47
|
+
def host_relations
|
48
|
+
h_relation = {}
|
49
|
+
build_relations.each do |key, hosts|
|
50
|
+
h_relation = array_hosts_to_hash h_relation, hosts, matching_mark(key)
|
51
|
+
end
|
52
|
+
h_relation
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: add this function
|
56
|
+
def favorable_relations
|
57
|
+
end
|
58
|
+
|
59
|
+
end # end ClassMethods
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require File.join(File.dirname(__FILE__), 'models/array') # maybe move it outside models folder
|
3
|
+
|
4
|
+
module OneTouch
|
5
|
+
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
initializer 'one_touch' do |app|
|
8
|
+
ActiveSupport.on_load(:active_record) do
|
9
|
+
require File.join(File.dirname(__FILE__), 'models/active_record_extension')
|
10
|
+
::ActiveRecord::Base.send :include, OneTouch::ActiveRecordExtension
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end # end Railtie
|
14
|
+
end
|
data/lib/one_touch.rb
ADDED
data/log/development.log
ADDED
File without changes
|
data/one_touch.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "one_touch/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "one_touch"
|
7
|
+
s.version = OneTouch::VERSION
|
8
|
+
s.authors = ["raykin"]
|
9
|
+
s.email = ["raykincoldxiao@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{ One Touch is used for recording relation between resources }
|
12
|
+
s.description = %q{ One Touch makes one click operation simple }
|
13
|
+
|
14
|
+
s.rubyforge_project = "one_touch"
|
15
|
+
|
16
|
+
s.add_runtime_dependency 'rails', '~> 3.0'
|
17
|
+
#s.add_development_dependency 'rails', '~> 3.0'
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
|
24
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
### Intro
|
2
|
+
In webapp, many data is generated by just one click like focus someone, dislike some post.This gem make it easy.
|
3
|
+
|
4
|
+
These one click data most are relation data and managed in one single model in this gem. So we can analyze the relation data easily.
|
5
|
+
|
6
|
+
## Dependency
|
7
|
+
version ~> 1 support Rails3.0 and need meta_where
|
8
|
+
version ~> 2 will support Rails3.1 and need squeel
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
gem install one_touch
|
12
|
+
|
13
|
+
Suppose we have User, Post and Tag model.We also has a Favor model which record the relations between users or between user and post.Favor model could be anyother model.
|
14
|
+
|
15
|
+
Then we define:
|
16
|
+
```ruby
|
17
|
+
class User; as_favor_host; as_favorable; end;
|
18
|
+
|
19
|
+
class Post; as_favorable; end;
|
20
|
+
|
21
|
+
class Tag; as_favorable; end;
|
22
|
+
|
23
|
+
# Make sure Favor has defined constants CONTEXT and CONTEXT_MATCH before acts_as_favor
|
24
|
+
# The Favor column depend on the relations you defined
|
25
|
+
# In this case, it would be id, user_id, favorable_id, favorable_type, context
|
26
|
+
# Don't forget context column
|
27
|
+
class Favor
|
28
|
+
CONTEXT = Set.new(%w[focus favorite follow agree oppose buy subscribe])
|
29
|
+
CONTEXT_MATCH = { :focus => [:Tag, :Post], :follow => [:User] }
|
30
|
+
|
31
|
+
acts_as_favor
|
32
|
+
end
|
33
|
+
# The real relations between them are:
|
34
|
+
# User has_many :favors, :class_name => "Favor", :as => :host
|
35
|
+
# Post has_many :be_favors, :class_name => "Favor", :as => :favorable
|
36
|
+
# Favor belongs_to :host, :class_name => "User"; belongs_to :favorable, :polymorphic => true
|
37
|
+
# This is default setting
|
38
|
+
```ruby
|
39
|
+
You can define the relation by yourself.
|
40
|
+
```ruby
|
41
|
+
class Favor
|
42
|
+
belongs_to :host, :polymorphic => true
|
43
|
+
belongs_to :favorable, :polymorphic => true
|
44
|
+
acts_as_favor :no_default_relations => true
|
45
|
+
end
|
46
|
+
# make sure the relation name are host and favorable, they can not be changed
|
47
|
+
```ruby
|
48
|
+
Or you want Favor model to be another name, Bridge.
|
49
|
+
```ruby
|
50
|
+
class User
|
51
|
+
as_favor_host :klass => "Bridge"
|
52
|
+
end
|
53
|
+
```ruby
|
54
|
+
In short, there are four relation names can not be configured, favors, be_favors, host and favorable.
|
55
|
+
And if you change the Favor model name, make sure it consists in all applications.
|
56
|
+
|
57
|
+
## Features
|
58
|
+
Favor.add_context and Favor.del_context can add or del favors. Ex:
|
59
|
+
```ruby
|
60
|
+
# @host focus on @favorable
|
61
|
+
Favor.add_focus(@host, @favorable)
|
62
|
+
#=> success return true, fail return error @favor object
|
63
|
+
#=> could also
|
64
|
+
|
65
|
+
# @host remove focus on @favorable
|
66
|
+
Favor.del_focus(@host, @favorable)
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
|
70
|
+
### Query Methods
|
71
|
+
The most useful methods would be: favor_to, favored_by, allstars. They are all class methods.
|
72
|
+
```ruby
|
73
|
+
User.favor_to(@user1, :follow) #=> return guys(an ActiveRelation object) who follows @user1, so you can define fans
|
74
|
+
def fans
|
75
|
+
User.favor_to(self, :follow)
|
76
|
+
end
|
77
|
+
@user1.fans # => the same results
|
78
|
+
|
79
|
+
Tag.favored_by(@user1, :focus) #=> return tag that @use1 focused
|
80
|
+
|
81
|
+
Tag.allstars(:focus) #=> return tags with a count attribute which returns how many focus on it
|
82
|
+
|
83
|
+
# Dynamic methods context_favorable_pluralize_klass, Ex:
|
84
|
+
@user1.focus_tags
|
85
|
+
# => return the tags which was focused by @user1
|
86
|
+
@user1.favor?(@tag1, :focus) #=> return true if @user1 focus on @tag1
|
87
|
+
```ruby
|
88
|
+
Actually the favor_to and favored_by are fundmental methods which many other methods based on
|
89
|
+
|
90
|
+
#### Analyze Relation Data
|
91
|
+
|
92
|
+
Use Favor.host_relations to get the relations of Users.
|
93
|
+
But first you should define the relation mark rule.
|
94
|
+
```ruby
|
95
|
+
class Favor
|
96
|
+
def self.relation_rules
|
97
|
+
{ [:agree, :Answer] => 3, [:follow, :User] => 2, [:focus, :Tag] => 1 }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
```ruby
|
101
|
+
|
102
|
+
The results like:
|
103
|
+
```ruby
|
104
|
+
Favor.host_relations
|
105
|
+
# It a Hash, the first element is
|
106
|
+
# => @user1 => { @user2 => 100, @user3 => 50, .... }
|
107
|
+
```ruby
|
108
|
+
This means @user1 is more close to @user2 than @user3.Maybe the answers that @user1 and @user2 both agree are more than the answers that @user1 and @user2 both agree.Actually it depends on your mark rule, but it really shows there must be more common betweens @user1 and @user2.
|
109
|
+
##### Becare running host_relations is really time consumer, so run it in backend job and keep the results.
|
110
|
+
|
111
|
+
### Why call it one touch
|
112
|
+
Because We create or delete data in one click. No Form Needed.
|
113
|
+
|
114
|
+
### TODO
|
115
|
+
Change the dependency meta_where to squeel and make it support Rails3.1
|
116
|
+
Add cache to favored_by, favor_to
|
117
|
+
Add a generator to make install faster.
|
118
|
+
Add another gem to supply view and controller template
|
119
|
+
|
120
|
+
### Abstract Requirement
|
121
|
+
Connect any two resource in one model.
|
122
|
+
I really want to implement this gem as more abstract requirement.That means four relation names can be configured, But's not easy.
|
data/spec/fake_app.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'action_controller/railtie'
|
3
|
+
require 'action_view/railtie'
|
4
|
+
|
5
|
+
# database
|
6
|
+
ActiveRecord::Base.configurations = {'test' => {:adapter => 'sqlite3', :database => ':memory:'}}
|
7
|
+
ActiveRecord::Base.establish_connection('test')
|
8
|
+
|
9
|
+
# config
|
10
|
+
app = Class.new(Rails::Application)
|
11
|
+
#app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
|
12
|
+
#app.config.session_store :cookie_store, :key => "_myapp_session"
|
13
|
+
app.config.active_support.deprecation = :log
|
14
|
+
app.initialize!
|
15
|
+
#
|
16
|
+
## routes
|
17
|
+
#app.routes.draw do
|
18
|
+
# resources :users
|
19
|
+
#end
|
20
|
+
|
21
|
+
class Favor < ActiveRecord::Base
|
22
|
+
|
23
|
+
# CONTEXT is an array of string and do not change the string to symbol
|
24
|
+
# CONTEXT_MATCH is all symbols, do not change it
|
25
|
+
CONTEXT = Set.new(%w[dig focus favorite follow dislike])
|
26
|
+
CONTEXT_MATCH = { :focus => [:Tag],:favorite => [:Tag],
|
27
|
+
:follow => [:User,:Investor], :dislike => [:User] }
|
28
|
+
CONTEXT_RELATION = { :oppose => [:follow, :dislike], :contain => [:favorite, :focus] }
|
29
|
+
|
30
|
+
# acts_as_favor # should run it after table has create
|
31
|
+
|
32
|
+
# belongs_to :host, :polymorphic => true
|
33
|
+
# belongs_to :favorable, :polymorphic => true
|
34
|
+
|
35
|
+
def self.relation_rules
|
36
|
+
{ [:follow, :User] => 2, [:dislike, :User] => 2, [:focus, :Tag] => 1 }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
class TestingFoo < ActiveRecord::Base
|
42
|
+
CONTEXT = Set.new(%w[follow dislike])
|
43
|
+
CONTEXT_MATCH = { :dislike => [:User] }
|
44
|
+
CONTEXT_RELATION = { :oppose => [:follow, :dislike] }
|
45
|
+
end
|
46
|
+
|
47
|
+
class Ask < ActiveRecord::Base
|
48
|
+
end
|
49
|
+
class User < ActiveRecord::Base
|
50
|
+
end
|
51
|
+
class Tag < ActiveRecord::Base
|
52
|
+
end
|
53
|
+
class Tagging < ActiveRecord::Base
|
54
|
+
end
|
55
|
+
|
56
|
+
#migrations
|
57
|
+
class CreateAllTables < ActiveRecord::Migration
|
58
|
+
def self.up
|
59
|
+
create_table(:favors) do |t|
|
60
|
+
t.references :host,:polymorphic => true # if user just need t.string user_id
|
61
|
+
t.references :favorable,:polymorphic => true
|
62
|
+
t.string :context
|
63
|
+
end
|
64
|
+
create_table(:users) {|t| t.string :email;t.string :name}
|
65
|
+
create_table(:tags) {|t| t.string :name}
|
66
|
+
#create_table(:readerships) {|t| t.integer :user_id; t.integer :book_id }
|
67
|
+
#create_table(:authorships) {|t| t.integer :user_id; t.integer :book_id }
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.down
|
71
|
+
drop_table :favors
|
72
|
+
drop_table :users
|
73
|
+
drop_table :tags
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OneTouch::ActsAsFavor do
|
4
|
+
|
5
|
+
include OneTouch::LoadTestData
|
6
|
+
|
7
|
+
context ActiveRecord::Base do
|
8
|
+
[:acts_as_favor,:as_favor_host,:as_favorable].each do |method_name|
|
9
|
+
it "should respond to #{method_name}" do
|
10
|
+
ActiveRecord::Base.should respond_to method_name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "favorable class should has two elements: user and tag" do
|
16
|
+
Favor.favorable_klasses.count.should eq(2)
|
17
|
+
Favor.favorable_klasses.should include(:User,:Tag)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should check context against entity type when the context is invalid" do
|
21
|
+
afavor.update_attributes context: "error_context"
|
22
|
+
afavor.valid?.should be_false
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should check context against entity type when the context is valid" do
|
26
|
+
afavor.update_attributes context: "follow"
|
27
|
+
afavor.valid?.should be_false
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should save ok when context is favorite and essay is target resource" do
|
31
|
+
afavor.update_attributes context: "favorite"
|
32
|
+
afavor.valid?.should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
[:add_focus, :del_focus, :context_as_follow].each do |method_name|
|
36
|
+
it "should has an class method #{method_name}" do
|
37
|
+
Favor.should respond_to method_name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should add a focus to a tag " do
|
42
|
+
fly_favor = Favor.add_focus foo,fly_tag
|
43
|
+
fly_favor.should be_true
|
44
|
+
foo.favors.where(context: 'focus',favorable_id: fly_tag.id,favorable_type: 'Tag').first.should be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it " foo can not follow him self ,return an instance of Favor" do
|
48
|
+
Favor.add_follow(foo,foo).class.should eq(Favor)
|
49
|
+
# lambda {Favor.add_follow(foo,foo)}.should raise_error(ActiveRecord::RecordInvalid)
|
50
|
+
end
|
51
|
+
|
52
|
+
it " keys of context_match should belongs to context " do
|
53
|
+
Favor.context_match_keys_should_belongs_to_context.should be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
context TestingFoo do
|
57
|
+
it "should not contain method host_relations" do
|
58
|
+
TestingFoo.should_not respond_to 'host_relations'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "all stars" do
|
63
|
+
|
64
|
+
before :each do
|
65
|
+
Favor.add_follow(tom,foo)
|
66
|
+
Favor.add_follow(bar,foo)
|
67
|
+
Favor.add_follow(ray,foo)
|
68
|
+
Favor.add_follow(ray,bar)
|
69
|
+
Favor.add_follow(tom,bar)
|
70
|
+
Favor.add_follow(foo,ray)
|
71
|
+
Favor.add_follow(foo,tom)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "keys of #build_relations should contains ['follow',foo]" do
|
75
|
+
Favor.build_relations.keys.should include(['follow',foo])
|
76
|
+
end
|
77
|
+
|
78
|
+
it "relation ['follow',foo] get 2 mark " do
|
79
|
+
first_build_relation = Favor.build_relations.select { |k,v| k.last == foo && k.first == 'follow' }
|
80
|
+
first_build_relation.keys.should include(['follow',foo])
|
81
|
+
Favor.matching_mark(first_build_relation.keys.first).should eq(2)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "host relations should be a complex nested hash" do
|
85
|
+
h_relations = Favor.host_relations
|
86
|
+
h_relations.is_a? Hash
|
87
|
+
h_relations.values.first.is_a? Hash
|
88
|
+
foo_relations = h_relations.select {|k,v| k == foo}
|
89
|
+
foo_relations.values.should eq([{}])
|
90
|
+
bar_relations = h_relations.select {|k,v| k == bar}
|
91
|
+
bar_relations.values.should eq([{ tom => 2, ray => 2}])
|
92
|
+
bar_to_tom = bar_relations.values.select {|k,v| k== tom}
|
93
|
+
tom_relations = h_relations.select {|k,v| k == tom}
|
94
|
+
tom_to_bar = tom_relations.values.select {|k,v| k== bar}
|
95
|
+
bar_to_tom.should eq(tom_to_bar)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe OneTouch::AsFavorHost do
|
4
|
+
|
5
|
+
include OneTouch::LoadTestData
|
6
|
+
context "foo" do
|
7
|
+
|
8
|
+
[:focus_tags,:favor_klass].each do |method_name|
|
9
|
+
it "should response #{method_name}, and ActiveRecord::Base not response to it" do
|
10
|
+
foo.should respond_to method_name
|
11
|
+
ActiveRecord::Base.should_not respond_to method_name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should find a favor on afavor" do
|
16
|
+
foo.favor?(tag,"favorite").should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OneTouch::AsFavorable do
|
4
|
+
|
5
|
+
include OneTouch::LoadTestData
|
6
|
+
|
7
|
+
context Tag do
|
8
|
+
it " should contain classmethod allstars" do
|
9
|
+
Tag.should respond_to(:allstars)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "when context is nil, return all" do
|
13
|
+
all_tags = Tag.all
|
14
|
+
Tag.context_as(nil).should eq(all_tags)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "first tag favors count should be 1" do
|
18
|
+
Tag.first.favors_count.should eq(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
context "first tag" do
|
24
|
+
it " favored_by? foo is true " do
|
25
|
+
Tag.first.favored_by?(foo, :favorite).should be_true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "User" do
|
30
|
+
|
31
|
+
before :each do
|
32
|
+
Favor.add_follow(tom,foo)
|
33
|
+
Favor.add_follow(bar,foo)
|
34
|
+
Favor.add_follow(ray,foo)
|
35
|
+
Favor.add_follow(ray,bar)
|
36
|
+
Favor.add_follow(tom,bar)
|
37
|
+
Favor.add_follow(foo,ray)
|
38
|
+
Favor.add_follow(foo,tom)
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:foo_follows) { User.favored_by(foo, "follow") }
|
42
|
+
|
43
|
+
context Favor do
|
44
|
+
it "build relations should return a hash of relation between favorable and host" do
|
45
|
+
Favor.build_relations.instance_of?(Hash).should be_true
|
46
|
+
Favor.build_relations.first.first.should eq(["favorite", Tag.first]) # means first key
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
it "host relations should has key [foo,'follow'] " do
|
52
|
+
User.host_relations(:be_favors, :host).keys.should include(['follow', foo])
|
53
|
+
end
|
54
|
+
|
55
|
+
it "host relations of foo follow should be tom, bar, ray" do
|
56
|
+
User.host_relations(:be_favors, :host)[['follow',foo]].should eq([tom,bar,ray])
|
57
|
+
end
|
58
|
+
|
59
|
+
it "allstars of be followed should has four guy,first one is foo,second is bar" do
|
60
|
+
allstars = User.allstars(:follow)
|
61
|
+
# allstars.count.should == 4 # it failed cause allstars.to_sql has a count(*)
|
62
|
+
allstars.all.count.should == 4
|
63
|
+
allstars.first.should eq(foo)
|
64
|
+
allstars.second.should eq(bar)
|
65
|
+
allstars.first.counter.should eq(3)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "favored by foo should be ray and tom" do
|
69
|
+
foo_follows.first.should eq(ray)
|
70
|
+
foo_follows.second.should eq(tom)
|
71
|
+
end
|
72
|
+
|
73
|
+
# favor_to is defined in as_favor_host.rb
|
74
|
+
it "foo's fans should contains three guys that are tom,bar,ray" do
|
75
|
+
fans= User.favor_to(foo, "follow")
|
76
|
+
fans.all.count.should eq(3)
|
77
|
+
fans.should include(tom,ray,bar)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "favorers of foo should be three guys " do
|
81
|
+
favorers = foo.favorers(User,:follow)
|
82
|
+
favorers.count.should eq(3)
|
83
|
+
favorers.should include(tom,ray,bar)
|
84
|
+
end
|
85
|
+
|
86
|
+
# follow_users is defined in as_favor_host.rb
|
87
|
+
it "foo's follow_users should contains two guys as ray,tom" do
|
88
|
+
follows= foo.follow_users
|
89
|
+
follows.count.should eq(2)
|
90
|
+
follows.should include(ray,tom)
|
91
|
+
end
|
92
|
+
|
93
|
+
it " when foo dislike ray, foo's followings did not include ray " do
|
94
|
+
Favor.add_dislike(foo,ray)
|
95
|
+
foo_follows = User.favored_by(foo, "follow")
|
96
|
+
foo_follows.count.should eq(1)
|
97
|
+
foo_follows.should include(tom)
|
98
|
+
no_foo_follows = User.unfavored_by(foo, "follow")
|
99
|
+
no_foo_follows.count.should eq(User.count - 1)
|
100
|
+
end
|
101
|
+
|
102
|
+
it " unfollow singleone is all peoples " do
|
103
|
+
single_one = User.create(email: "singleone@test.com")
|
104
|
+
User.unfavored_by(single_one, 'follow').count.should eq(User.count)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module OneTouch
|
2
|
+
module LoadTestData
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
let!(:foo) { User.create(email: "foo@test.com",name: "iamfoo") }
|
7
|
+
let!(:bar) { User.create(email: "bar@test.com") }
|
8
|
+
let!(:ray) { User.create(email: "ray@test.com") }
|
9
|
+
let!(:tom) { User.create(email: "tom@test.com") }
|
10
|
+
let!(:tag) { Tag.create :name => "money" }
|
11
|
+
let!(:fly_tag) { Tag.create :name => "fly" }
|
12
|
+
let!(:afavor) { Favor.create :host_id => foo.id, :host_type => "User", :favorable_id => tag.id,:favorable_type => "Tag" , :context => "favorite" }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rails'
|
4
|
+
require 'one_touch'
|
5
|
+
require File.join(File.dirname(__FILE__), 'fake_app')
|
6
|
+
require 'meta_where'
|
7
|
+
#require 'squeel'
|
8
|
+
require 'rspec/rails'
|
9
|
+
require 'models/load_test_data'
|
10
|
+
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.use_transactional_fixtures = true
|
14
|
+
|
15
|
+
config.before :all do
|
16
|
+
#CreateAllTables.down if ActiveRecord::Base.connection.table_exists? 'favors'
|
17
|
+
CreateAllTables.up unless ActiveRecord::Base.connection.table_exists? 'favors'
|
18
|
+
|
19
|
+
# Squeel.configure do |config|
|
20
|
+
# config.load_core_extensions :hash, :symbol
|
21
|
+
# end
|
22
|
+
|
23
|
+
Tag.as_favorable
|
24
|
+
|
25
|
+
User.as_favor_host
|
26
|
+
|
27
|
+
User.as_favorable
|
28
|
+
Favor.acts_as_favor :host_class_name => nil
|
29
|
+
# TestingFoo.acts_as_favor
|
30
|
+
# Tag.as_favorable :be_tagging_favors, :klass => "Tagging"
|
31
|
+
# Ask.as_favor_host :tagging_favors, :klass => "Tagging"
|
32
|
+
# Tagging.acts_as_favor :host_class_name => nil, :favorable_klass_name => :Tag, :include_favor_relations_module => true
|
33
|
+
# relation should be in another module
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: one_touch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- raykin
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-11 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: &25065280 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *25065280
|
25
|
+
description: ! ' One Touch makes one click operation simple '
|
26
|
+
email:
|
27
|
+
- raykincoldxiao@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- Rakefile
|
35
|
+
- lib/one_touch.rb
|
36
|
+
- lib/one_touch/engine.rb
|
37
|
+
- lib/one_touch/models/active_record_extension.rb
|
38
|
+
- lib/one_touch/models/acts_as_favor.rb
|
39
|
+
- lib/one_touch/models/array.rb
|
40
|
+
- lib/one_touch/models/as_favor_host.rb
|
41
|
+
- lib/one_touch/models/as_favorable.rb
|
42
|
+
- lib/one_touch/models/host_relation.rb
|
43
|
+
- lib/one_touch/models/relations.rb
|
44
|
+
- lib/one_touch/railtie.rb
|
45
|
+
- lib/one_touch/version.rb
|
46
|
+
- log/development.log
|
47
|
+
- one_touch.gemspec
|
48
|
+
- readme.md
|
49
|
+
- spec/fake_app.rb
|
50
|
+
- spec/models/acts_as_favor_spec.rb
|
51
|
+
- spec/models/array_spec.rb
|
52
|
+
- spec/models/as_favor_host_spec.rb
|
53
|
+
- spec/models/as_favorable_spec.rb
|
54
|
+
- spec/models/load_test_data.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
homepage: ''
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project: one_touch
|
76
|
+
rubygems_version: 1.8.11
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: One Touch is used for recording relation between resources
|
80
|
+
test_files: []
|
81
|
+
has_rdoc:
|