bucket_maker 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.gitignore +1 -0
- data/README.md +1 -0
- data/app/controllers/bucket_maker_controller.rb +13 -0
- data/config/routes.rb +3 -0
- data/lib/bucket_maker/bucket.rb +33 -4
- data/lib/bucket_maker/configuration.rb +26 -1
- data/lib/bucket_maker/engine.rb +4 -0
- data/lib/bucket_maker/models/bucketable.rb +43 -0
- data/lib/bucket_maker/series.rb +34 -4
- data/lib/bucket_maker/series_maker.rb +64 -7
- data/lib/bucket_maker/version.rb +3 -1
- data/lib/generators/active_record/bucket_maker_generator.rb +26 -0
- data/lib/generators/bucket_maker/bucket_maker_generator.rb +2 -0
- data/lib/generators/bucket_maker/install_generator.rb +4 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzI3ZDVhYjZhNGQwMDc0YTljOTk1MzBmODE1MDYzNmUwOGRkNWQ1Zg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Yjk3N2ExMWNkZjI5MDkzZDIwZGExMDVlZmM0MDE2YTMxOWM0NmJhNQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MTUwMWQ3ODA5OTYyODNkZDYzOWY0ZmYxMGJlNDQzYjg3N2EzYjcyMmUxOGFk
|
10
|
+
OTZhNTU3ZTg2MmY2MWMwNTY1OTQ1MDcyOWFkMmUxNDU1NWQ4MzNiNGE3MzZi
|
11
|
+
NzllOGQ4ODYyMmQ2OTI0OGNlNjFiMTIxMjQzZTczN2U4ZWJmNTE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NTM0Y2QzODkwOTRkNDhlN2FmMGI1ODY0NGMwYjJjZWIzYjY2NjkxOGFmNmRm
|
14
|
+
ZDg3NGE2Nzk0ZGM3Y2FkYjA5MTUwMzU1OGYxYjRmOTQ5Zjg1OTE1MTliNDZj
|
15
|
+
MjVhZWZkMDVhNjYxYmJmNDYwMTMxMmQyMzM1MmRhMWYyMzk0NGE=
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
[![Code Climate](https://codeclimate.com/github/dinks/bucket_maker.png)](https://codeclimate.com/github/dinks/bucket_maker)
|
5
5
|
[![Build Status](https://travis-ci.org/dinks/bucket_maker.png?branch=master)](https://travis-ci.org/dinks/bucket_maker)
|
6
6
|
[![Coverage Status](https://coveralls.io/repos/dinks/bucket_maker/badge.png)](https://coveralls.io/r/dinks/bucket_maker)
|
7
|
+
[![Dependency Status](https://gemnasium.com/dinks/bucket_maker.png)](https://gemnasium.com/dinks/bucket_maker)
|
7
8
|
|
8
9
|
A Gem to categorize Objects into buckets. Typical use case is an A/B test for Users
|
9
10
|
|
@@ -4,27 +4,40 @@ class BucketMakerController < ApplicationController
|
|
4
4
|
before_filter :ensure_valid_series_bucket_group, only: [:show, :switch]
|
5
5
|
before_filter :ensure_valid_series_bucket, only: [:randomize]
|
6
6
|
|
7
|
+
# Show if the group for the current_user is the same as stored
|
8
|
+
#
|
7
9
|
def show
|
8
10
|
render text: @current_user.in_bucket?(@series_name, @bucket_name, @group_name)
|
9
11
|
end
|
10
12
|
|
13
|
+
# Switch the current user to group_name
|
14
|
+
#
|
11
15
|
def switch
|
12
16
|
render text: @current_user.force_to_bucket!(@series_name, @bucket_name, @group_name)
|
13
17
|
end
|
14
18
|
|
19
|
+
# Randomize the current_user for series_name and bucket_name
|
20
|
+
#
|
15
21
|
def randomize
|
16
22
|
render text: @current_user.bucketize_for_series_and_bucket!(@series_name, @bucket_name)
|
17
23
|
end
|
18
24
|
|
19
25
|
private
|
26
|
+
|
27
|
+
# Ensure we have valid attributes
|
28
|
+
#
|
20
29
|
def ensure_valid_series_bucket_group
|
21
30
|
not_found unless BucketMaker.buckets_configuration.has_group_in_bucket_in_series?(@series_name, @bucket_name, @group_name)
|
22
31
|
end
|
23
32
|
|
33
|
+
# Ensure we have valid attributes
|
34
|
+
#
|
24
35
|
def ensure_valid_series_bucket
|
25
36
|
not_found unless BucketMaker.buckets_configuration.has_bucket_in_series?(@series_name, @bucket_name)
|
26
37
|
end
|
27
38
|
|
39
|
+
# Head not found for invalid requests
|
40
|
+
#
|
28
41
|
def not_found
|
29
42
|
head :not_found
|
30
43
|
false
|
data/config/routes.rb
CHANGED
@@ -2,12 +2,15 @@ Rails.application.routes.draw do
|
|
2
2
|
if BucketMaker.configured? && BucketMaker.load_routes?
|
3
3
|
|
4
4
|
# Show if the user is in group
|
5
|
+
#
|
5
6
|
get "/#{BucketMaker.configuration.path_prefix}:series_name/:bucket_name/:group_name", to: 'bucket_maker#show', as: :show_bucket, format: :json
|
6
7
|
|
7
8
|
# Randomize group
|
9
|
+
#
|
8
10
|
post "/#{BucketMaker.configuration.path_prefix}:series_name/:bucket_name", to: 'bucket_maker#randomize', as: :randomize_bucket, format: :json
|
9
11
|
|
10
12
|
# Force Switch group
|
13
|
+
#
|
11
14
|
post "/#{BucketMaker.configuration.path_prefix}:series_name/:bucket_name/:group_name", to: 'bucket_maker#switch', as: :switch_bucket, format: :json
|
12
15
|
|
13
16
|
end
|
data/lib/bucket_maker/bucket.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module BucketMaker
|
2
|
+
# Class which holds all the Bucket information
|
3
|
+
#
|
2
4
|
class Bucket
|
3
5
|
attr_accessor :name,
|
4
6
|
:summary,
|
@@ -11,6 +13,11 @@ module BucketMaker
|
|
11
13
|
BUCKET_USER_AFTER = 'created_after'
|
12
14
|
BUCKET_DISTRIBUTION = 'distributions'
|
13
15
|
|
16
|
+
# Initializer
|
17
|
+
#
|
18
|
+
# @param name [string] Name of the Bucket
|
19
|
+
# @param options [Hash] Options for the Bucket like BUCKET_DESCRIPTION, BUCKET_USER_AFTER, BUCKET_DISTRIBUTION
|
20
|
+
#
|
14
21
|
def initialize(name, options={})
|
15
22
|
@name = name.to_sym
|
16
23
|
@summary = options[BUCKET_DESCRIPTION]
|
@@ -27,20 +34,28 @@ module BucketMaker
|
|
27
34
|
result[dist_name.to_sym] = dist_options
|
28
35
|
result
|
29
36
|
end if options[BUCKET_DISTRIBUTION]
|
30
|
-
|
31
37
|
end
|
32
38
|
|
39
|
+
# Randomize and get the group for this bucket
|
40
|
+
#
|
41
|
+
# @return [String] Group Name after randomization
|
42
|
+
#
|
33
43
|
def random_group
|
34
44
|
# Is set after first randomization
|
35
45
|
unless @distributions_percent
|
46
|
+
# Get the total value of the distributions
|
47
|
+
#
|
36
48
|
@denominator = @distributions.inject(0) do |result, (_, dist_value)|
|
37
49
|
result + dist_value
|
38
50
|
end
|
51
|
+
# Populate the variable with Distribution Percentages
|
52
|
+
#
|
39
53
|
@distributions_percent = @distributions.inject({}) do |result, (dist_name, dist_value)|
|
40
54
|
result[dist_name.to_sym] = (dist_value * 100.0)/@denominator
|
41
55
|
result
|
42
56
|
end
|
43
|
-
|
57
|
+
# Change the Distribution Percentages with Ranges for easy checks
|
58
|
+
#
|
44
59
|
@distributions_percent.inject(0) do |starter, (dist_name, percent_value)|
|
45
60
|
ender = starter + percent_value
|
46
61
|
@distributions_percent[dist_name.to_sym] = (starter .. ender)
|
@@ -48,18 +63,32 @@ module BucketMaker
|
|
48
63
|
end
|
49
64
|
end
|
50
65
|
|
66
|
+
# Randomize within the 0..max_range
|
67
|
+
#
|
51
68
|
randomized = rand(@denominator * 100)
|
69
|
+
# Find where the randomized number falls in the calculated range
|
70
|
+
#
|
52
71
|
@distributions_percent.find do |_, percent_range|
|
53
72
|
percent_range.include?(randomized)
|
54
73
|
end.first
|
55
74
|
end
|
56
75
|
|
76
|
+
# Check if the Bucketable conforms to the pre-conditions
|
77
|
+
#
|
78
|
+
# @param bucketable [Object] an object which responds to :created_at
|
79
|
+
# @return [Boolean] is it bucketable?
|
80
|
+
#
|
57
81
|
def is_bucketable?(bucketable)
|
58
|
-
bucketable.created_at >= @created_after
|
82
|
+
bucketable.created_at >= @created_after rescue false
|
59
83
|
end
|
60
84
|
|
85
|
+
# Check if there is a group in this bucket
|
86
|
+
#
|
87
|
+
# @param group_name [String] Name of the group
|
88
|
+
# @return [Boolean] does the group exist?
|
89
|
+
#
|
61
90
|
def has_group?(group_name)
|
62
|
-
@distributions[group_name.to_sym] != nil
|
91
|
+
@distributions[group_name.to_sym] != nil if group_name
|
63
92
|
end
|
64
93
|
end
|
65
94
|
end
|
@@ -3,6 +3,8 @@ require 'active_support/core_ext'
|
|
3
3
|
require 'bucket_maker/series_maker'
|
4
4
|
|
5
5
|
module BucketMaker
|
6
|
+
# Configuration Holder for the BucketMaker
|
7
|
+
#
|
6
8
|
class Configuration
|
7
9
|
attr_accessor :redis_options,
|
8
10
|
:path_prefix,
|
@@ -13,23 +15,35 @@ module BucketMaker
|
|
13
15
|
attr_reader :buckets_configuration,
|
14
16
|
:connection
|
15
17
|
|
18
|
+
# Initializer
|
19
|
+
# Sets up the default variable values
|
20
|
+
#
|
16
21
|
def initialize
|
22
|
+
# For Redis
|
17
23
|
@redis_options = {
|
18
24
|
host: 'localhost',
|
19
25
|
port: 6379,
|
20
26
|
db: 1
|
21
27
|
}
|
22
|
-
|
23
28
|
@redis_expiration_time = 12.months
|
24
29
|
|
30
|
+
# For paths
|
31
|
+
# If nil, the routes wont be loaded
|
25
32
|
@path_prefix = '2bOrNot2B/'
|
26
33
|
|
34
|
+
# Configuration for the buckets
|
35
|
+
#
|
27
36
|
@buckets_config_file = nil
|
28
37
|
@buckets_configuration = nil
|
29
38
|
|
39
|
+
# Lazy Load is used to group only if in_bucket? is called
|
40
|
+
# if false, then the group is done at creation time of the objec as well
|
41
|
+
#
|
30
42
|
@lazy_load = true
|
31
43
|
end
|
32
44
|
|
45
|
+
# Reconfigure the Configuration
|
46
|
+
#
|
33
47
|
def reconfigure!
|
34
48
|
if @buckets_config_file
|
35
49
|
@buckets_configuration = BucketMaker::SeriesMaker.instance
|
@@ -37,10 +51,17 @@ module BucketMaker
|
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
54
|
+
# Check if the configuration is done
|
55
|
+
#
|
56
|
+
# @return [Boolean]
|
40
57
|
def configured?
|
41
58
|
@buckets_configuration && @buckets_configuration.configured?
|
42
59
|
end
|
43
60
|
|
61
|
+
# Check if its ok to load routes
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
#
|
44
65
|
def load_routes?
|
45
66
|
@path_prefix != nil
|
46
67
|
end
|
@@ -48,6 +69,8 @@ module BucketMaker
|
|
48
69
|
end
|
49
70
|
|
50
71
|
class << self
|
72
|
+
# Forward some of the calls to the configuration object
|
73
|
+
#
|
51
74
|
extend Forwardable
|
52
75
|
|
53
76
|
attr_accessor :configuration
|
@@ -55,6 +78,8 @@ module BucketMaker
|
|
55
78
|
def_delegators :@configuration, :configured?, :reconfigure!, :buckets_configuration, :load_routes?
|
56
79
|
end
|
57
80
|
|
81
|
+
# Configure after yielding to the block
|
82
|
+
#
|
58
83
|
def self.configure
|
59
84
|
if block_given?
|
60
85
|
self.configuration ||= Configuration.new
|
data/lib/bucket_maker/engine.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module BucketMaker
|
2
|
+
# Engine for the BucketMaker
|
3
|
+
#
|
2
4
|
class Engine < Rails::Engine
|
3
5
|
initializer 'Require concerns path' do |app|
|
4
6
|
concerns_path = 'app/controllers/concerns'
|
@@ -7,6 +9,8 @@ module BucketMaker
|
|
7
9
|
app.paths.add(concerns_path)
|
8
10
|
end
|
9
11
|
|
12
|
+
# Always add the concern
|
13
|
+
#
|
10
14
|
require 'concerns/bucket_maker_concern'
|
11
15
|
end
|
12
16
|
end
|
@@ -2,7 +2,18 @@ require 'bucket_maker/series_maker'
|
|
2
2
|
|
3
3
|
module BucketMaker
|
4
4
|
module Models
|
5
|
+
# Module which holds the base methods for Bucketable objects
|
6
|
+
#
|
5
7
|
module Bucketable
|
8
|
+
|
9
|
+
# Used to test if this object has been grouped under a certain group
|
10
|
+
#
|
11
|
+
# @param series_name [String] Name of the Series
|
12
|
+
# @param bucket_name [String] Name of the Bucket
|
13
|
+
# @param group_name [String] Name of the Group
|
14
|
+
# @param force [Boolean] To force the object into the group_name
|
15
|
+
# @return [Boolean] if the given group_name is the same after randomization of groups
|
16
|
+
#
|
6
17
|
def in_bucket?(series_name, bucket_name, group_name, force=false)
|
7
18
|
# Get the singleton Series Maker
|
8
19
|
series_maker = BucketMaker::SeriesMaker.instance
|
@@ -39,15 +50,32 @@ module BucketMaker
|
|
39
50
|
end
|
40
51
|
end
|
41
52
|
|
53
|
+
# Used to test if this object has not been grouped under a certain group
|
54
|
+
#
|
55
|
+
# @param series_name [String] Name of the Series
|
56
|
+
# @param bucket_name [String] Name of the Bucket
|
57
|
+
# @param group_name [String] Name of the Group
|
58
|
+
# @return [Boolean] if the given group_name is the not the same after randomization of groups
|
59
|
+
#
|
42
60
|
def not_in_bucket?(series_name, bucket_name, group_name)
|
43
61
|
!in_bucket?(series_name, bucket_name, group_name)
|
44
62
|
end
|
45
63
|
|
64
|
+
# Force object into the group_name under bucket under series
|
65
|
+
#
|
66
|
+
# @param series_name [String] Name of the Series
|
67
|
+
# @param bucket_name [String] Name of the Bucket
|
68
|
+
# @param group_name [String] Name of the Group
|
69
|
+
# @return [Boolean] Always true because forcing is ok by us
|
70
|
+
#
|
46
71
|
def force_to_bucket!(series_name, bucket_name, group_name)
|
47
72
|
# Forcefully place inside the bucket
|
48
73
|
in_bucket?(series_name, bucket_name, group_name, true)
|
49
74
|
end
|
50
75
|
|
76
|
+
# Iteratively group object into buckets
|
77
|
+
#
|
78
|
+
# @return [Boolean] true if all the operations are successfull
|
51
79
|
def bucketize!
|
52
80
|
# Take each series and bucket
|
53
81
|
BucketMaker::SeriesMaker.instance.for_each_series_with_bucketable do |series_maker, series_name, bucket_name|
|
@@ -61,6 +89,11 @@ module BucketMaker
|
|
61
89
|
end
|
62
90
|
end
|
63
91
|
|
92
|
+
# Group object for series_name and bucket_name
|
93
|
+
#
|
94
|
+
# @param series_name [String] Name of the Series
|
95
|
+
# @param bucket_name [String] Name of the Bucket
|
96
|
+
# @return [Boolean] true if the operation is successfull
|
64
97
|
def bucketize_for_series_and_bucket!(series_name, bucket_name)
|
65
98
|
# Get the singleton Series Maker
|
66
99
|
series_maker = BucketMaker::SeriesMaker.instance
|
@@ -77,10 +110,20 @@ module BucketMaker
|
|
77
110
|
end
|
78
111
|
end
|
79
112
|
|
113
|
+
# Get the value from persistent store for a key
|
114
|
+
#
|
115
|
+
# @param series_key [String] Series Key
|
116
|
+
# @return [String] should return the group_name
|
117
|
+
#
|
80
118
|
def group_for_key(series_key)
|
81
119
|
raise NotImplementedError, "Implement group_for_key"
|
82
120
|
end
|
83
121
|
|
122
|
+
# Set the value to a persistent store for a series and group
|
123
|
+
#
|
124
|
+
# @param series_key [String] Series Key
|
125
|
+
# @param group_name [String] Name of the Group
|
126
|
+
#
|
84
127
|
def set_group_for_key(series_key, group_name)
|
85
128
|
raise NotImplementedError, "Implement set_group_for_key"
|
86
129
|
end
|
data/lib/bucket_maker/series.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module BucketMaker
|
2
|
+
# Class which holds all the Series based information
|
3
|
+
#
|
2
4
|
class Series
|
3
5
|
attr_reader :name,
|
4
6
|
:description,
|
@@ -9,6 +11,11 @@ module BucketMaker
|
|
9
11
|
SERIES_USER_AFTER = 'created_after'
|
10
12
|
SERIES_BUCKETS = 'buckets'
|
11
13
|
|
14
|
+
# Initializer
|
15
|
+
#
|
16
|
+
# @param name [String] Name of the Series
|
17
|
+
# @param options [Hash] Options for the Series like SERIES_DESCRIPTION, SERIES_USER_AFTER, SERIES_BUCKETS
|
18
|
+
#
|
12
19
|
def initialize(name, options={})
|
13
20
|
@name = name.to_sym
|
14
21
|
@description = options[SERIES_DESCRIPTION] || ''
|
@@ -23,26 +30,49 @@ module BucketMaker
|
|
23
30
|
end if options[SERIES_BUCKETS]
|
24
31
|
end
|
25
32
|
|
33
|
+
# Iterator going through each bucket
|
34
|
+
#
|
26
35
|
def each_bucket
|
27
36
|
@buckets.each do |bucket_name, bucket|
|
28
37
|
yield bucket_name, bucket
|
29
38
|
end if block_given?
|
30
39
|
end
|
31
40
|
|
41
|
+
# Get the Bucket object when given the bucket_name
|
42
|
+
#
|
43
|
+
# @param bucket_name [String] Name of the Bucket
|
44
|
+
# @return [BucketMaker::Bucket] Bucket object
|
45
|
+
#
|
32
46
|
def bucket_with_name(bucket_name)
|
33
|
-
@buckets[bucket_name.to_sym]
|
47
|
+
@buckets[bucket_name.to_sym] if bucket_name
|
34
48
|
end
|
35
49
|
|
50
|
+
# Check if the Bucketable object conforms to the conditions
|
51
|
+
#
|
52
|
+
# @param bucketable [Object] an object which responds to :created_at
|
53
|
+
# @return [Boolean] is it bucketable?
|
54
|
+
#
|
36
55
|
def is_bucketable?(bucketable)
|
37
|
-
bucketable.created_at >= @created_after
|
56
|
+
bucketable.created_at >= @created_after rescue false
|
38
57
|
end
|
39
58
|
|
59
|
+
# Check if the Series has the bucket
|
60
|
+
#
|
61
|
+
# @param bucket_name [String] Name of the Bucket
|
62
|
+
# @return [Boolean] does the bucket exist?
|
63
|
+
#
|
40
64
|
def has_bucket?(bucket_name)
|
41
|
-
@buckets[bucket_name.to_sym] != nil
|
65
|
+
@buckets[bucket_name.to_sym] != nil rescue false
|
42
66
|
end
|
43
67
|
|
68
|
+
# Check if the bucket has the group
|
69
|
+
#
|
70
|
+
# @param bucket_name [String] Name of the Bucket
|
71
|
+
# @param group_name [String] Name of the Group
|
72
|
+
# @return [Boolean] does the group exist inside the bucket?
|
73
|
+
#
|
44
74
|
def has_group_in_bucket?(bucket_name, group_name)
|
45
|
-
has_bucket?(bucket_name) && @buckets[bucket_name.to_sym].has_group?(group_name)
|
75
|
+
has_bucket?(bucket_name) && @buckets[bucket_name.to_sym].has_group?(group_name) rescue false
|
46
76
|
end
|
47
77
|
end
|
48
78
|
end
|
@@ -4,6 +4,8 @@ require 'bucket_maker/series'
|
|
4
4
|
require 'bucket_maker/bucket'
|
5
5
|
|
6
6
|
module BucketMaker
|
7
|
+
# Singleton which holds all information regarding the series, buckets and groups
|
8
|
+
#
|
7
9
|
class SeriesMaker
|
8
10
|
include Singleton
|
9
11
|
|
@@ -13,6 +15,10 @@ module BucketMaker
|
|
13
15
|
|
14
16
|
BUCKET_ROOT = 'series'
|
15
17
|
|
18
|
+
# Set the class variables with configuration details
|
19
|
+
#
|
20
|
+
# @param config [String] Path of the config file WRT the rails app
|
21
|
+
#
|
16
22
|
def make!(config)
|
17
23
|
@series = []
|
18
24
|
|
@@ -28,10 +34,17 @@ module BucketMaker
|
|
28
34
|
end
|
29
35
|
end
|
30
36
|
|
37
|
+
# Check if the SeriesMaker is configured
|
38
|
+
#
|
39
|
+
# @return [Boolean] Signifies if the Object is configured
|
40
|
+
#
|
31
41
|
def configured?
|
32
42
|
@configuration != nil
|
33
43
|
end
|
34
44
|
|
45
|
+
# Iterator for each series to be run on a Bucketable instance
|
46
|
+
# Expects a block parameter to be passed
|
47
|
+
#
|
35
48
|
def for_each_series_with_bucketable
|
36
49
|
@series.collect do |series_name, series|
|
37
50
|
series.each_bucket do |bucket_name, bucket|
|
@@ -40,37 +53,81 @@ module BucketMaker
|
|
40
53
|
end.inject(true) {|result, value| result && value } if block_given?
|
41
54
|
end
|
42
55
|
|
56
|
+
# Get the Series object when given the series_name
|
57
|
+
#
|
58
|
+
# @param series_name [String] Series Name to search
|
59
|
+
# @return [BucketMaker::Series]
|
60
|
+
#
|
43
61
|
def series_with_name(series_name)
|
44
|
-
@series[series_name.to_sym]
|
62
|
+
@series[series_name.to_sym] if series_name
|
45
63
|
end
|
46
64
|
|
65
|
+
# Check if the series exist
|
66
|
+
#
|
67
|
+
# @param series_name [String] Series Name to search
|
68
|
+
# @return [Boolean] is it there?
|
69
|
+
#
|
47
70
|
def has_series?(series_name)
|
48
|
-
@series[series_name.to_sym] != nil
|
71
|
+
@series[series_name.to_sym] != nil rescue false
|
49
72
|
end
|
50
73
|
|
74
|
+
# Check if the bucket exist in the series
|
75
|
+
#
|
76
|
+
# @param series_name [String] Series Name to search
|
77
|
+
# @param bucket_name [String] Bucket Name to search
|
78
|
+
# @return [Boolean] is it there?
|
79
|
+
#
|
51
80
|
def has_bucket_in_series?(series_name, bucket_name)
|
52
|
-
has_series?(series_name) && series_with_name(series_name).has_bucket?(bucket_name)
|
81
|
+
has_series?(series_name) && series_with_name(series_name).has_bucket?(bucket_name) rescue false
|
53
82
|
end
|
54
83
|
|
84
|
+
# Check if the group exists in the bucket in the series
|
85
|
+
#
|
86
|
+
# @param series_name [String] Series Name to search
|
87
|
+
# @param bucket_name [String] Bucket Name to search
|
88
|
+
# @param group_name [String] Group Name to search
|
89
|
+
# @return [Boolean] is it there?
|
90
|
+
#
|
55
91
|
def has_group_in_bucket_in_series?(series_name, bucket_name, group_name)
|
56
92
|
has_bucket_in_series?(series_name, bucket_name) &&
|
57
|
-
series_with_name(series_name).bucket_with_name(bucket_name).has_group?(group_name)
|
93
|
+
series_with_name(series_name).bucket_with_name(bucket_name).has_group?(group_name) rescue false
|
58
94
|
end
|
59
95
|
|
96
|
+
# Get the key for the combination
|
97
|
+
#
|
98
|
+
# @param bucketable [Object] any object which responds to :id
|
99
|
+
# @param series_name [String] Series Name
|
100
|
+
# @param bucket_name [String] Bucket Name
|
101
|
+
# @return [String] The key for future use
|
102
|
+
#
|
60
103
|
def key_for_series(bucketable, series_name, bucket_name)
|
61
|
-
"bucket_maker:#{bucketable.class.to_s.underscore}_#{bucketable.id}:#{series_name.to_s}:#{bucket_name.to_s}" if bucketable && bucketable.id
|
104
|
+
"bucket_maker:#{bucketable.class.to_s.underscore}_#{bucketable.id}:#{series_name.to_s}:#{bucket_name.to_s}" if bucketable && bucketable.id rescue nil
|
62
105
|
end
|
63
106
|
|
107
|
+
# Get a random group for the bucket in a series
|
108
|
+
#
|
109
|
+
# @param series_name [String] Series Name
|
110
|
+
# @param bucket_name [String] Bucket Name
|
111
|
+
# @return [String] Random Group Name according to the distribution
|
112
|
+
#
|
64
113
|
def bucketize(series_name, bucket_name)
|
65
|
-
series_with_name(series_name).bucket_with_name(bucket_name).random_group
|
114
|
+
series_with_name(series_name).bucket_with_name(bucket_name).random_group rescue nil
|
66
115
|
end
|
67
116
|
|
117
|
+
# Check if a Bucketable Object conform to the pre-conditions
|
118
|
+
#
|
119
|
+
# @param bucketable [Object] any object which has methods :has_attribute?, :created_at and :id
|
120
|
+
# @param series_name [String] Series Name
|
121
|
+
# @param bucket_name [String] Bucket Name
|
122
|
+
# @param group_name [String] Group Name
|
123
|
+
# @return [Boolean] is it bucketable?
|
124
|
+
#
|
68
125
|
def bucketable?(bucketable, series_name, bucket_name, group_name)
|
69
126
|
if bucketable.has_attribute?(:created_at) &&
|
70
127
|
has_group_in_bucket_in_series?(series_name, bucket_name, group_name)
|
71
128
|
|
72
129
|
series_with_name(series_name).bucket_with_name(bucket_name).is_bucketable?(bucketable) ||
|
73
|
-
series_with_name(series_name).is_bucketable?(bucketable)
|
130
|
+
series_with_name(series_name).is_bucketable?(bucketable) rescue false
|
74
131
|
|
75
132
|
else
|
76
133
|
false
|
data/lib/bucket_maker/version.rb
CHANGED
@@ -2,6 +2,8 @@ require 'rails/generators/active_record'
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Generators
|
5
|
+
# Generator to create Active Record specific classes
|
6
|
+
#
|
5
7
|
class BucketMakerGenerator < ActiveRecord::Generators::Base
|
6
8
|
|
7
9
|
ACTIVE_RECORDABLE = 'bucket'
|
@@ -9,6 +11,10 @@ module ActiveRecord
|
|
9
11
|
source_root File.expand_path("../../templates", __FILE__)
|
10
12
|
argument :store_in, type: :string, default: 'redis'
|
11
13
|
|
14
|
+
# Generate the model
|
15
|
+
#
|
16
|
+
# If store_in is active_record then the ACTIVE_RECORDABLE is also created which holds association
|
17
|
+
#
|
12
18
|
def generate_model
|
13
19
|
invoke "active_record:model", [name], :migration => false unless model_exists? && behavior == :invoke
|
14
20
|
if store_in == 'active_record'
|
@@ -16,6 +22,10 @@ module ActiveRecord
|
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
25
|
+
# Inject the content to the models
|
26
|
+
#
|
27
|
+
# If store_in is actice_record then the ACTIVE_RECORDABLE is also inserted with association
|
28
|
+
#
|
19
29
|
def inject_bucket_maker_content
|
20
30
|
contents = <<-CONTENT.strip_heredoc
|
21
31
|
# Module Inclusion for BucketMaker
|
@@ -40,6 +50,8 @@ module ActiveRecord
|
|
40
50
|
inject_for_active_recordable if store_in == 'active_record' && active_recordable_exists?
|
41
51
|
end
|
42
52
|
|
53
|
+
# Content for association if store_in is active_record
|
54
|
+
#
|
43
55
|
def inject_for_active_recordable
|
44
56
|
ar_contents = <<-CONTENT.strip_heredoc
|
45
57
|
# Association with the bucket
|
@@ -51,6 +63,10 @@ module ActiveRecord
|
|
51
63
|
inject_into_class(active_recordable_path, ACTIVE_RECORDABLE.camelize, ar_contents)
|
52
64
|
end
|
53
65
|
|
66
|
+
# Migration copier
|
67
|
+
#
|
68
|
+
# If store_in is actice_record then the migration runs
|
69
|
+
#
|
54
70
|
def copy_bucket_maker_migration
|
55
71
|
if behavior == :invoke && store_in == 'active_record' && active_recordable_exists?
|
56
72
|
migration_template "active_recordable_migration.rb", "db/migrate/create_#{ACTIVE_RECORDABLE.pluralize}"
|
@@ -59,22 +75,32 @@ module ActiveRecord
|
|
59
75
|
|
60
76
|
private
|
61
77
|
|
78
|
+
# Check if the MODEL exists
|
79
|
+
#
|
62
80
|
def model_exists?
|
63
81
|
File.exists?(File.join(destination_root, model_path))
|
64
82
|
end
|
65
83
|
|
84
|
+
# Check if the association exists
|
85
|
+
#
|
66
86
|
def active_recordable_exists?
|
67
87
|
File.exists?(File.join(destination_root, active_recordable_path))
|
68
88
|
end
|
69
89
|
|
90
|
+
# Get the association path
|
91
|
+
#
|
70
92
|
def active_recordable_path
|
71
93
|
@ar_path ||= File.join("app", "models", "#{ACTIVE_RECORDABLE}.rb")
|
72
94
|
end
|
73
95
|
|
96
|
+
# Get the MODEL path
|
97
|
+
#
|
74
98
|
def model_path
|
75
99
|
@model_path ||= File.join("app", "models", "#{file_path}.rb")
|
76
100
|
end
|
77
101
|
|
102
|
+
# Get the module name to the included for store_in values
|
103
|
+
#
|
78
104
|
def module_name_for_include
|
79
105
|
case store_in
|
80
106
|
when 'redis'
|
@@ -2,11 +2,15 @@ require 'rails/generators/base'
|
|
2
2
|
|
3
3
|
module BucketMaker
|
4
4
|
module Generators
|
5
|
+
# Installs the initializer
|
6
|
+
#
|
5
7
|
class InstallGenerator < Rails::Generators::Base
|
6
8
|
source_root File.expand_path("../../templates", __FILE__)
|
7
9
|
|
8
10
|
desc "Creates a BucketMaker initializer"
|
9
11
|
|
12
|
+
# Copies the initializer bucket_maker from the templates and pastes
|
13
|
+
#
|
10
14
|
def copy_initializer
|
11
15
|
template "bucket_maker.rb", "config/initializers/bucket_maker.rb"
|
12
16
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bucket_maker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dinesh Vasudevan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -337,3 +337,4 @@ test_files:
|
|
337
337
|
- spec/unit/bucketable_spec.rb
|
338
338
|
- spec/unit/series_maker_spec.rb
|
339
339
|
- spec/unit/series_spec.rb
|
340
|
+
has_rdoc:
|