one_touch 1.0.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/.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:
|