socialization 0.5.0.beta3 → 0.5.0.beta4

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.
@@ -12,6 +12,8 @@ module Socialization
12
12
  extend ActiveSupport::Concern
13
13
 
14
14
  included do
15
+ after_destroy { Socialization.follow_model.remove_followables(self) }
16
+
15
17
  # Specifies if self can follow {Followable} objects.
16
18
  #
17
19
  # @return [Boolean]
@@ -12,6 +12,8 @@ module Socialization
12
12
  extend ActiveSupport::Concern
13
13
 
14
14
  included do
15
+ after_destroy { Socialization.like_model.remove_likeables(self) }
16
+
15
17
  # Specifies if self can like {Likeable} objects.
16
18
  #
17
19
  # @return [Boolean]
@@ -12,6 +12,8 @@ module Socialization
12
12
  extend ActiveSupport::Concern
13
13
 
14
14
  included do
15
+ after_destroy { Socialization.mention_model.remove_mentionables(self) }
16
+
15
17
  # Specifies if self can mention {Mentionable} objects.
16
18
  #
17
19
  # @return [Boolean]
@@ -98,6 +98,18 @@ module Socialization
98
98
  end
99
99
  end
100
100
 
101
+ # Remove all the followers for followable
102
+ def remove_followers(followable)
103
+ self.where(:followable_type => followable.class.name.classify).
104
+ where(:followable_id => followable.id).destroy_all
105
+ end
106
+
107
+ # Remove all the followables for follower
108
+ def remove_followables(follower)
109
+ self.where(:follower_type => follower.class.name.classify).
110
+ where(:follower_id => follower.id).destroy_all
111
+ end
112
+
101
113
  private
102
114
  def follow_for(follower, followable)
103
115
  followed_by(follower).following(followable)
@@ -98,6 +98,18 @@ module Socialization
98
98
  end
99
99
  end
100
100
 
101
+ # Remove all the likers for likeable
102
+ def remove_likers(likeable)
103
+ self.where(:likeable_type => likeable.class.name.classify).
104
+ where(:likeable_id => likeable.id).destroy_all
105
+ end
106
+
107
+ # Remove all the likeables for liker
108
+ def remove_likeables(liker)
109
+ self.where(:liker_type => liker.class.name.classify).
110
+ where(:liker_id => liker.id).destroy_all
111
+ end
112
+
101
113
  private
102
114
  def like_for(liker, likeable)
103
115
  liked_by(liker).liking( likeable)
@@ -98,6 +98,18 @@ module Socialization
98
98
  end
99
99
  end
100
100
 
101
+ # Remove all the mentioners for mentionable
102
+ def remove_mentioners(mentionable)
103
+ self.where(:mentionable_type => mentionable.class.name.classify).
104
+ where(:mentionable_id => mentionable.id).destroy_all
105
+ end
106
+
107
+ # Remove all the mentionables for mentioner
108
+ def remove_mentionables(mentioner)
109
+ self.where(:mentioner_type => mentioner.class.name.classify).
110
+ where(:mentioner_id => mentioner.id).destroy_all
111
+ end
112
+
101
113
  private
102
114
  def mention_for(mentioner, mentionable)
103
115
  mentioned_by(mentioner).mentioning(mentionable)
@@ -1,6 +1,123 @@
1
1
  module Socialization
2
2
  module RedisStores
3
3
  class Base
4
- end
5
- end
6
- end
4
+
5
+ class << self
6
+ protected
7
+ def actors(victim, klass, options = {})
8
+ if options[:pluck]
9
+ Socialization.redis.smembers(generate_forward_key(victim)).inject([]) do |result, element|
10
+ result << element.match(/\:(\d+)$/)[1] if element.match(/^#{klass}\:/)
11
+ end
12
+ else
13
+ actors_relation(victim, klass, options).all
14
+ end
15
+ end
16
+
17
+ def actors_relation(victim, klass, options = {})
18
+ ids = actors(victim, klass, :pluck => :id)
19
+ klass.where("#{klass.table_name}.id IN (?)", ids)
20
+ end
21
+
22
+ def victims_relation(actor, klass, options = {})
23
+ ids = victims(actor, klass, :pluck => :id)
24
+ klass.where("#{klass.table_name}.id IN (?)", ids)
25
+ end
26
+
27
+ def victims(actor, klass, options = {})
28
+ if options[:pluck]
29
+ Socialization.redis.smembers(generate_backward_key(actor)).inject([]) do |result, element|
30
+ result << element.match(/\:(\d+)$/)[1] if element.match(/^#{klass}\:/)
31
+ end
32
+ else
33
+ victims_relation(actor, klass, options).all
34
+ end
35
+ end
36
+
37
+ def relation!(actor, victim, options = {})
38
+ unless options[:skip_check] || relation?(actor, victim)
39
+ Socialization.redis.sadd generate_forward_key(victim), generate_redis_value(actor)
40
+ Socialization.redis.sadd generate_backward_key(actor), generate_redis_value(victim)
41
+ call_after_create_hooks(actor, victim)
42
+ true
43
+ else
44
+ false
45
+ end
46
+ end
47
+
48
+ def unrelation!(actor, victim, options = {})
49
+ if options[:skip_check] || relation?(actor, victim)
50
+ Socialization.redis.srem generate_forward_key(victim), generate_redis_value(actor)
51
+ Socialization.redis.srem generate_backward_key(actor), generate_redis_value(victim)
52
+ call_after_destroy_hooks(actor, victim)
53
+ true
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ def relation?(actor, victim)
60
+ Socialization.redis.sismember generate_forward_key(victim), generate_redis_value(actor)
61
+ end
62
+
63
+ def remove_actor_relations(victim)
64
+ forward_key = generate_forward_key(victim)
65
+ actors = Socialization.redis.smembers forward_key
66
+ Socialization.redis.del forward_key
67
+ actors.each do |actor|
68
+ Socialization.redis.srem generate_backward_key(actor), generate_redis_value(victim)
69
+ end
70
+ true
71
+ end
72
+
73
+ def remove_victim_relations(actor)
74
+ backward_key = generate_backward_key(actor)
75
+ victims = Socialization.redis.smembers backward_key
76
+ Socialization.redis.del backward_key
77
+ victims.each do |victim|
78
+ Socialization.redis.srem generate_forward_key(victim), generate_redis_value(actor)
79
+ end
80
+ true
81
+ end
82
+
83
+
84
+ private
85
+ def key_type_to_type_names(klass)
86
+ if klass.name.match(/Follow$/)
87
+ ['follower', 'followable']
88
+ elsif klass.name.match(/Like$/)
89
+ ['liker', 'likeable']
90
+ elsif klass.name.match(/Mention$/)
91
+ ['mentioner', 'mentionable']
92
+ else
93
+ raise ArgumentError.new("Can't find matching type for #{klass}.")
94
+ end
95
+ end
96
+
97
+ def generate_forward_key(victim)
98
+ keys = key_type_to_type_names(self)
99
+ if victim.is_a?(String)
100
+ "#{keys[0].pluralize.capitalize}:#{victim}"
101
+ else
102
+ "#{keys[0].pluralize.capitalize}:#{victim.class}:#{victim.id}"
103
+ end
104
+ end
105
+
106
+ def generate_backward_key(actor)
107
+ keys = key_type_to_type_names(self)
108
+ if actor.is_a?(String)
109
+ "#{keys[1].pluralize.capitalize}:#{actor}"
110
+ else
111
+ "#{keys[1].pluralize.capitalize}:#{actor.class}:#{actor.id}"
112
+ end
113
+ end
114
+
115
+ def generate_redis_value(obj)
116
+ "#{obj.class.name}:#{obj.id}"
117
+ end
118
+
119
+ end # class << self
120
+
121
+ end # Base
122
+ end # RedisStores
123
+ end # Socialization
@@ -1,5 +1,3 @@
1
- # require File.expand_path(File.dirname(__FILE__)) + '/base'
2
-
3
1
  module Socialization
4
2
  module RedisStores
5
3
  class Follow < Socialization::RedisStores::Base
@@ -8,91 +6,16 @@ module Socialization
8
6
  extend Socialization::RedisStores::Mixins::Base
9
7
 
10
8
  class << self
11
- def follow!(follower, followable)
12
- unless follows?(follower, followable)
13
- Socialization.redis.sadd generate_followers_key(follower, followable), follower.id
14
- Socialization.redis.sadd generate_followables_key(follower, followable), followable.id
15
-
16
- call_after_create_hooks(follower, followable)
17
- true
18
- else
19
- false
20
- end
21
- end
22
-
23
- def unfollow!(follower, followable)
24
- if follows?(follower, followable)
25
- Socialization.redis.srem generate_followers_key(follower, followable), follower.id
26
- Socialization.redis.srem generate_followables_key(follower, followable), followable.id
27
-
28
- call_after_destroy_hooks(follower, followable)
29
- true
30
- else
31
- false
32
- end
33
- end
34
-
35
- def follows?(follower, followable)
36
- Socialization.redis.sismember generate_followers_key(follower, followable), follower.id
37
- end
38
-
39
- # Returns an ActiveRecord::Relation of all the followers of a certain type that are following followable
40
- def followers_relation(followable, klass, opts = {})
41
- ids = followers(followable, klass, :pluck => :id)
42
- klass.where('id IN (?)', ids)
43
- end
44
-
45
- # Returns all the followers of a certain type that are following followable
46
- def followers(followable, klass, opts = {})
47
- if opts[:pluck]
48
- Socialization.redis.smembers(generate_followers_key(klass, followable)).map { |id|
49
- id.to_i if id.is_integer?
50
- }
51
- else
52
- followers_relation(followable, klass, opts).all
53
- end
54
- end
55
-
56
- # Returns an ActiveRecord::Relation of all the followables of a certain type that are followed by follower
57
- def followables_relation(follower, klass, opts = {})
58
- ids = followables(follower, klass, :pluck => :id)
59
- klass.where('id IN (?)', ids)
60
- end
61
-
62
- # Returns all the followables of a certain type that are followed by follower
63
- def followables(follower, klass, opts = {})
64
- if opts[:pluck]
65
- Socialization.redis.smembers(generate_followables_key(follower, klass)).map { |id|
66
- id.to_i if id.is_integer?
67
- }
68
- else
69
- followables_relation(follower, klass, opts).all
70
- end
71
- end
72
-
73
- private
74
- def generate_followers_key(follower, followable)
75
- raise ArgumentError.new("`followable` needs to be an acts_as_followable objecs, not a class.") if followable.class == Class
76
- follower_class = if follower.class == Class
77
- follower
78
- else
79
- follower.class
80
- end
81
-
82
- "Followers:#{followable.class}:#{followable.id}:#{follower_class}"
83
- end
84
-
85
- def generate_followables_key(follower, followable)
86
- raise ArgumentError.new("`follower` needs to be an acts_as_follower object, not a class.") if follower.class == Class
87
- followable_class = if followable.class == Class
88
- followable
89
- else
90
- followable.class
91
- end
92
-
93
- "Followables:#{follower.class}:#{follower.id}:#{followable_class}"
94
- end
95
- end # class << self
9
+ alias_method :follow!, :relation!; public :follow!
10
+ alias_method :unfollow!, :unrelation!; public :unfollow!
11
+ alias_method :follows?, :relation?; public :follows?
12
+ alias_method :followers_relation, :actors_relation; public :followers_relation
13
+ alias_method :followers, :actors; public :followers
14
+ alias_method :followables_relation, :victims_relation; public :followables_relation
15
+ alias_method :followables, :victims; public :followables
16
+ alias_method :remove_followers, :remove_actor_relations; public :remove_followers
17
+ alias_method :remove_followables, :remove_victim_relations; public :remove_followables
18
+ end
96
19
 
97
20
  end
98
21
  end
@@ -1,5 +1,3 @@
1
- # require File.expand_path(File.dirname(__FILE__)) + '/base'
2
-
3
1
  module Socialization
4
2
  module RedisStores
5
3
  class Like < Socialization::RedisStores::Base
@@ -8,122 +6,16 @@ module Socialization
8
6
  extend Socialization::RedisStores::Mixins::Base
9
7
 
10
8
  class << self
11
- def like!(liker, likeable)
12
- unless likes?(liker, likeable)
13
- Socialization.redis.sadd generate_likers_key(liker, likeable), liker.id
14
- Socialization.redis.sadd generate_likeables_key(liker, likeable), likeable.id
15
-
16
- call_after_create_hooks(liker, likeable)
17
- liker.touch if [:all, :liker].include?(touch) && liker.respond_to?(:touch)
18
- likeable.touch if [:all, :likeable].include?(touch) && likeable.respond_to?(:touch)
19
- true
20
- else
21
- false
22
- end
23
- end
24
-
25
- def unlike!(liker, likeable)
26
- if likes?(liker, likeable)
27
- Socialization.redis.srem generate_likers_key(liker, likeable), liker.id
28
- Socialization.redis.srem generate_likeables_key(liker, likeable), likeable.id
29
-
30
- call_after_destroy_hooks(liker, likeable)
31
- liker.touch if [:all, :liker].include?(touch) && liker.respond_to?(:touch)
32
- likeable.touch if [:all, :likeable].include?(touch) && likeable.respond_to?(:touch)
33
- true
34
- else
35
- false
36
- end
37
- end
38
-
39
- def likes?(liker, likeable)
40
- Socialization.redis.sismember generate_likers_key(liker, likeable), liker.id
41
- end
42
-
43
- # Returns an ActiveRecord::Relation of all the likers of a certain type that are likeing likeable
44
- def likers_relation(likeable, klass, opts = {})
45
- ids = likers(likeable, klass, :pluck => :id)
46
- klass.where('id IN (?)', ids)
47
- end
48
-
49
- # Returns all the likers of a certain type that are likeing likeable
50
- def likers(likeable, klass, opts = {})
51
- if opts[:pluck]
52
- Socialization.redis.smembers(generate_likers_key(klass, likeable)).map { |id|
53
- id.to_i if id.is_integer?
54
- }
55
- else
56
- likers_relation(likeable, klass, opts).all
57
- end
58
- end
59
-
60
- # Returns an ActiveRecord::Relation of all the likeables of a certain type that are liked by liker
61
- def likeables_relation(liker, klass, opts = {})
62
- ids = likeables(liker, klass, :pluck => :id)
63
- klass.where('id IN (?)', ids)
64
- end
65
-
66
- # Returns all the likeables of a certain type that are liked by liker
67
- def likeables(liker, klass, opts = {})
68
- if opts[:pluck]
69
- Socialization.redis.smembers(generate_likeables_key(liker, klass)).map { |id|
70
- id.to_i if id.is_integer?
71
- }
72
- else
73
- likeables_relation(liker, klass, opts).all
74
- end
75
- end
76
-
77
- def touch(what = nil)
78
- if what.nil?
79
- @touch || false
80
- else
81
- raise ArgumentError unless [:all, :liker, :likeable, false, nil].include?(what)
82
- @touch = what
83
- end
84
- end
85
-
86
- def after_like(method)
87
- raise ArgumentError unless method.is_a?(Symbol) || method.nil?
88
- @after_create_hook = method
89
- end
90
-
91
- def after_unlike(method)
92
- raise ArgumentError unless method.is_a?(Symbol) || method.nil?
93
- @after_destroy_hook = method
94
- end
95
-
96
- private
97
- def call_after_create_hooks(liker, likeable)
98
- self.send(@after_create_hook, liker, likeable) if @after_create_hook
99
- end
100
-
101
- def call_after_destroy_hooks(liker, likeable)
102
- self.send(@after_destroy_hook, liker, likeable) if @after_destroy_hook
103
- end
104
-
105
- def generate_likers_key(liker, likeable)
106
- raise ArgumentError.new("`likeable` needs to be an acts_as_likeable objecs, not a class.") if likeable.class == Class
107
- liker_class = if liker.class == Class
108
- liker
109
- else
110
- liker.class
111
- end
112
-
113
- "Likers:#{likeable.class}:#{likeable.id}:#{liker_class}"
114
- end
115
-
116
- def generate_likeables_key(liker, likeable)
117
- raise ArgumentError.new("`liker` needs to be an acts_as_liker object, not a class.") if liker.class == Class
118
- likeable_class = if likeable.class == Class
119
- likeable
120
- else
121
- likeable.class
122
- end
123
-
124
- "Likeables:#{liker.class}:#{liker.id}:#{likeable_class}"
125
- end
126
- end # class << self
9
+ alias_method :like!, :relation!; public :like!
10
+ alias_method :unlike!, :unrelation!; public :unlike!
11
+ alias_method :likes?, :relation?; public :likes?
12
+ alias_method :likers_relation, :actors_relation; public :likers_relation
13
+ alias_method :likers, :actors; public :likers
14
+ alias_method :likeables_relation, :victims_relation; public :likeables_relation
15
+ alias_method :likeables, :victims; public :likeables
16
+ alias_method :remove_likers, :remove_actor_relations; public :remove_likers
17
+ alias_method :remove_likeables, :remove_victim_relations; public :remove_likeables
18
+ end
127
19
 
128
20
  end
129
21
  end
@@ -1,5 +1,3 @@
1
- # require File.expand_path(File.dirname(__FILE__)) + '/base'
2
-
3
1
  module Socialization
4
2
  module RedisStores
5
3
  class Mention < Socialization::RedisStores::Base
@@ -8,91 +6,16 @@ module Socialization
8
6
  extend Socialization::RedisStores::Mixins::Base
9
7
 
10
8
  class << self
11
- def mention!(mentioner, mentionable)
12
- unless mentions?(mentioner, mentionable)
13
- Socialization.redis.sadd generate_mentioners_key(mentioner, mentionable), mentioner.id
14
- Socialization.redis.sadd generate_mentionables_key(mentioner, mentionable), mentionable.id
15
-
16
- call_after_create_hooks(mentioner, mentionable)
17
- true
18
- else
19
- false
20
- end
21
- end
22
-
23
- def unmention!(mentioner, mentionable)
24
- if mentions?(mentioner, mentionable)
25
- Socialization.redis.srem generate_mentioners_key(mentioner, mentionable), mentioner.id
26
- Socialization.redis.srem generate_mentionables_key(mentioner, mentionable), mentionable.id
27
-
28
- call_after_destroy_hooks(mentioner, mentionable)
29
- true
30
- else
31
- false
32
- end
33
- end
34
-
35
- def mentions?(mentioner, mentionable)
36
- Socialization.redis.sismember generate_mentioners_key(mentioner, mentionable), mentioner.id
37
- end
38
-
39
- # Returns an ActiveRecord::Relation of all the mentioners of a certain type that are mentioning mentionable
40
- def mentioners_relation(mentionable, klass, opts = {})
41
- ids = mentioners(mentionable, klass, :pluck => :id)
42
- klass.where('id IN (?)', ids)
43
- end
44
-
45
- # Returns all the mentioners of a certain type that are mentioning mentionable
46
- def mentioners(mentionable, klass, opts = {})
47
- if opts[:pluck]
48
- Socialization.redis.smembers(generate_mentioners_key(klass, mentionable)).map { |id|
49
- id.to_i if id.is_integer?
50
- }
51
- else
52
- mentioners_relation(mentionable, klass, opts).all
53
- end
54
- end
55
-
56
- # Returns an ActiveRecord::Relation of all the mentionables of a certain type that are mentioned by mentioner
57
- def mentionables_relation(mentioner, klass, opts = {})
58
- ids = mentionables(mentioner, klass, :pluck => :id)
59
- klass.where('id IN (?)', ids)
60
- end
61
-
62
- # Returns all the mentionables of a certain type that are mentioned by mentioner
63
- def mentionables(mentioner, klass, opts = {})
64
- if opts[:pluck]
65
- Socialization.redis.smembers(generate_mentionables_key(mentioner, klass)).map { |id|
66
- id.to_i if id.is_integer?
67
- }
68
- else
69
- mentionables_relation(mentioner, klass, opts).all
70
- end
71
- end
72
-
73
- private
74
- def generate_mentioners_key(mentioner, mentionable)
75
- raise ArgumentError.new("`mentionable` needs to be an acts_as_mentionable objecs, not a class.") if mentionable.class == Class
76
- mentioner_class = if mentioner.class == Class
77
- mentioner
78
- else
79
- mentioner.class
80
- end
81
-
82
- "Mentioners:#{mentionable.class}:#{mentionable.id}:#{mentioner_class}"
83
- end
84
-
85
- def generate_mentionables_key(mentioner, mentionable)
86
- raise ArgumentError.new("`mentioner` needs to be an acts_as_mentioner object, not a class.") if mentioner.class == Class
87
- mentionable_class = if mentionable.class == Class
88
- mentionable
89
- else
90
- mentionable.class
91
- end
92
-
93
- "Mentionables:#{mentioner.class}:#{mentioner.id}:#{mentionable_class}"
94
- end
95
- end # class << self
9
+ alias_method :mention!, :relation!; public :mention!
10
+ alias_method :unmention!, :unrelation!; public :unmention!
11
+ alias_method :mentions?, :relation?; public :mentions?
12
+ alias_method :mentioners_relation, :actors_relation; public :mentioners_relation
13
+ alias_method :mentioners, :actors; public :mentioners
14
+ alias_method :mentionables_relation, :victims_relation; public :mentionables_relation
15
+ alias_method :mentionables, :victims; public :mentionables
16
+ alias_method :remove_mentioners, :remove_actor_relations; public :remove_mentioners
17
+ alias_method :remove_mentionables, :remove_victim_relations; public :remove_mentionables
18
+ end
96
19
 
97
20
  end
98
21
  end
@@ -1,3 +1,3 @@
1
1
  module Socialization
2
- VERSION = "0.5.0.beta3"
2
+ VERSION = "0.5.0.beta4"
3
3
  end
@@ -12,6 +12,8 @@ module Socialization
12
12
  extend ActiveSupport::Concern
13
13
 
14
14
  included do
15
+ after_destroy { Socialization.follow_model.remove_followers(self) }
16
+
15
17
  # Specifies if self can be followed.
16
18
  #
17
19
  # @return [Boolean]
@@ -12,6 +12,8 @@ module Socialization
12
12
  extend ActiveSupport::Concern
13
13
 
14
14
  included do
15
+ after_destroy { Socialization.like_model.remove_likers(self) }
16
+
15
17
  # Specifies if self can be liked.
16
18
  #
17
19
  # @return [Boolean]
@@ -12,6 +12,8 @@ module Socialization
12
12
  extend ActiveSupport::Concern
13
13
 
14
14
  included do
15
+ after_destroy { Socialization.mention_model.remove_mentioners(self) }
16
+
15
17
  # Specifies if self can be mentioned.
16
18
  #
17
19
  # @return [Boolean]
@@ -98,5 +98,17 @@ class FollowerTest < Test::Unit::TestCase
98
98
  end
99
99
  end
100
100
 
101
+ context "deleting a follower" do
102
+ setup do
103
+ @follower = ImAFollower.create
104
+ @follower.follow!(@followable)
105
+ end
106
+
107
+ should "remove follow relationships" do
108
+ Socialization.follow_model.expects(:remove_followables).with(@follower)
109
+ @follower.destroy
110
+ end
111
+ end
112
+
101
113
  end
102
114
  end
@@ -98,5 +98,9 @@ class LikerTest < Test::Unit::TestCase
98
98
  end
99
99
  end
100
100
 
101
+ should "remove like relationships" do
102
+ Socialization.like_model.expects(:remove_likeables).with(@liker)
103
+ @liker.destroy
104
+ end
101
105
  end
102
106
  end
@@ -97,5 +97,10 @@ class MentionerTest < Test::Unit::TestCase
97
97
  @mentioner.mentionees_relation(@mentionable.class, { :foo => :bar })
98
98
  end
99
99
  end
100
+
101
+ should "remove mention relationships" do
102
+ Socialization.mention_model.expects(:remove_mentionables).with(@mentioner)
103
+ @mentioner.destroy
104
+ end
100
105
  end
101
106
  end
@@ -108,6 +108,23 @@ class ActiveRecordFollowStoreTest < Test::Unit::TestCase
108
108
  end
109
109
  end
110
110
 
111
+ context "#remove_followers" do
112
+ should "delete all followers relationships for a followable" do
113
+ @follower.follow!(@followable)
114
+ assert_equal 1, @followable.followers(@follower.class).count
115
+ @klass.remove_followers(@followable)
116
+ assert_equal 0, @followable.followers(@follower.class).count
117
+ end
118
+ end
119
+
120
+ context "#remove_followables" do
121
+ should "delete all followables relationships for a follower" do
122
+ @follower.follow!(@followable)
123
+ assert_equal 1, @follower.followables(@followable.class).count
124
+ @klass.remove_followables(@follower)
125
+ assert_equal 0, @follower.followables(@followable.class).count
126
+ end
127
+ end
111
128
  end
112
129
 
113
130
  # Helpers