i_wonder 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/app/controllers/i_wonder/ab_tests_controller.rb +2 -0
- data/app/models/i_wonder/ab_test.rb +37 -4
- data/app/models/i_wonder/ab_test_goal.rb +4 -3
- data/app/models/i_wonder/test_group_membership.rb +2 -2
- data/app/views/i_wonder/ab_tests/_ab_test_groups.html.erb +5 -1
- data/app/views/i_wonder/ab_tests/_form.html.erb +7 -5
- data/app/views/i_wonder/ab_tests/index.html.erb +5 -3
- data/app/views/i_wonder/ab_tests/show.html.erb +9 -5
- data/db/migrate/20111022230720_i_wonder_migrations.rb +4 -4
- data/lib/i_wonder/ab_testing/action_controller_mixins.rb +1 -2
- data/lib/i_wonder/ab_testing/loader.rb +54 -0
- data/lib/i_wonder/engine.rb +6 -1
- data/lib/i_wonder/version.rb +1 -1
- data/lib/i_wonder.rb +1 -0
- data/test/dummy/db/schema.rb +5 -5
- data/test/dummy/log/development.log +917 -0
- data/test/dummy/log/test.log +31889 -0
- data/test/functional/ab_controller_mixins_test.rb +2 -0
- data/test/test_assets/i_wonder/save_test.xml +24 -0
- data/test/test_helper.rb +3 -1
- data/test/unit/i_wonder/ab_test_test.rb +1 -1
- data/test/unit/i_wonder/loader_test.rb +89 -0
- metadata +38 -22
data/README.rdoc
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Flexible Analytics, and Testing for every corner of your rails application. I read "The Lean Startup" and got inspired. There were a few good solutions, but none of them quite did what I was looking for.
|
4
4
|
|
5
|
-
Eventually I want it to be a drop-in replacement for Google analytics.
|
5
|
+
Eventually I want it to be a drop-in replacement for Google analytics. Vanity (http://vanity.labnotes.org/) is a good alternative depending on your analytics needs.
|
6
6
|
|
7
7
|
More information can be found at https://github.com/forrest/i_wonder/wiki
|
8
8
|
|
@@ -18,6 +18,7 @@ module IWonder
|
|
18
18
|
@ab_test = AbTest.new(params[:ab_test])
|
19
19
|
|
20
20
|
if @ab_test.save
|
21
|
+
AbTesting::Loader.save_ab_tests
|
21
22
|
redirect_to @ab_test, :notice => "Successfully created ABTest"
|
22
23
|
else
|
23
24
|
render "new"
|
@@ -33,6 +34,7 @@ module IWonder
|
|
33
34
|
@ab_test = AbTest.find(params[:id])
|
34
35
|
|
35
36
|
if @ab_test.update_attributes(params[:ab_test])
|
37
|
+
AbTesting::Loader.save_ab_tests
|
36
38
|
redirect_to @ab_test, :notice => "Successfully updated ABTest"
|
37
39
|
else
|
38
40
|
render "edit"
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module IWonder
|
2
2
|
class AbTest < ActiveRecord::Base
|
3
|
-
attr_accessible :name, :sym, :description, :ab_test_goals_attributes, :test_applies_to, :test_group_names
|
3
|
+
attr_accessible :name, :sym, :description, :ab_test_goals_attributes, :test_applies_to, :test_group_names, :options, :test_group_data
|
4
4
|
serialize :options, Hash
|
5
5
|
serialize :test_group_data, Hash
|
6
6
|
|
7
|
-
has_many :test_group_memberships, :dependent => :destroy
|
7
|
+
has_many :test_group_memberships, :dependent => :destroy, :foreign_key => "ab_test_sym", :primary_key => :sym
|
8
8
|
|
9
|
-
has_many :ab_test_goals, :dependent => :destroy
|
9
|
+
has_many :ab_test_goals, :dependent => :destroy, :foreign_key => "ab_test_sym", :primary_key => :sym
|
10
10
|
accepts_nested_attributes_for :ab_test_goals, :allow_destroy => true
|
11
11
|
|
12
12
|
hash_accessor :options, :test_group_names, :type => :array, :reject_blanks => true
|
@@ -28,7 +28,17 @@ module IWonder
|
|
28
28
|
errors.add(:base, "Must have atleast one goal")
|
29
29
|
end
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
|
+
after_save :save_to_file
|
33
|
+
def save_to_file
|
34
|
+
AbTesting::Loader.save_ab_test(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
after_destroy :remove_file
|
38
|
+
def remove_file
|
39
|
+
AbTesting::Loader.remove_file_for(self)
|
40
|
+
end
|
41
|
+
|
32
42
|
def started?
|
33
43
|
test_group_memberships.count > 0
|
34
44
|
end
|
@@ -63,7 +73,30 @@ module IWonder
|
|
63
73
|
|
64
74
|
scoped_groups_with_goal_events.count
|
65
75
|
end
|
76
|
+
|
77
|
+
def from_xml(xml)
|
78
|
+
[super]
|
79
|
+
|
80
|
+
# make sure it's not blank. Then make sure it's a hash. Then Make sure it's symbolized.
|
81
|
+
self.options ||= {}
|
82
|
+
self.test_group_data ||= {}
|
83
|
+
self.options = {} if self.options.is_a?(String)
|
84
|
+
self.test_group_data = {} if self.test_group_data.is_a?(String)
|
85
|
+
self.options.symbolize_keys!
|
86
|
+
self.test_group_data.symbolize_keys!
|
87
|
+
|
88
|
+
self.ab_test_goals.each(&:mark_for_destruction)
|
89
|
+
|
90
|
+
hash = Hash.from_xml(xml)
|
91
|
+
hash["ab_test"]["ab_test_goals"].each{|ab_test_goal_hash|
|
92
|
+
ab_test_goal_hash["options"].symbolize_keys!
|
93
|
+
self.ab_test_goals.build(ab_test_goal_hash)
|
94
|
+
}
|
95
|
+
end
|
66
96
|
|
97
|
+
def started_at
|
98
|
+
test_group_memberships.minimum(:created_at)
|
99
|
+
end
|
67
100
|
|
68
101
|
private
|
69
102
|
|
@@ -1,8 +1,9 @@
|
|
1
1
|
module IWonder
|
2
2
|
class AbTestGoal < ActiveRecord::Base
|
3
|
-
|
3
|
+
attr_accessible :goal_type, :event_type, :page_view_controller, :page_view_action, :options
|
4
|
+
|
5
|
+
belongs_to :ab_test, :foreign_key => "ab_test_sym", :primary_key => :sym
|
4
6
|
|
5
|
-
attr_accessible :goal_type, :event_type, :page_view_controller, :page_view_action
|
6
7
|
serialize :options, Hash
|
7
8
|
hash_accessor :options, :goal_type, :default => "Event"
|
8
9
|
hash_accessor :options, :event_type
|
@@ -32,7 +33,7 @@ module IWonder
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
|
-
sc.where("i_wonder_events.created_at > ?", self.ab_test.
|
36
|
+
sc.where("i_wonder_events.created_at > ?", self.ab_test.started_at) # event had to come after goal
|
36
37
|
end
|
37
38
|
|
38
39
|
def to_s
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module IWonder
|
2
2
|
class TestGroupMembership < ActiveRecord::Base
|
3
|
-
belongs_to :ab_test
|
3
|
+
belongs_to :ab_test, :foreign_key => "ab_test_sym", :primary_key => :sym
|
4
4
|
|
5
5
|
validates_presence_of :test_group_name, :on => :create, :message => "can't be blank"
|
6
6
|
validates_inclusion_of :member_type, :in => %w( account user session ), :on => :create
|
7
7
|
validates_presence_of :member_id, :on => :create, :message => "can't be blank"
|
8
8
|
|
9
|
-
validates_uniqueness_of :
|
9
|
+
validates_uniqueness_of :ab_test_sym, :on => :create, :message => "must be unique for this member", :scope => [:member_type, :member_id]
|
10
10
|
|
11
11
|
end
|
12
12
|
end
|
@@ -3,7 +3,11 @@
|
|
3
3
|
<h4>Test Groups</h4>
|
4
4
|
|
5
5
|
<ul id="ab_test_groups">
|
6
|
-
|
6
|
+
<% if f.object.test_group_names.empty? %>
|
7
|
+
<%= render :partial => "ab_test_group_row", :collection => ["", ""], :as => :test_group_name %>
|
8
|
+
<% else %>
|
9
|
+
<%= render :partial => "ab_test_group_row", :collection => f.object.test_group_names, :as => :test_group_name %>
|
10
|
+
<% end %>
|
7
11
|
</ul>
|
8
12
|
|
9
13
|
<br style="clear:both"/>
|
@@ -27,11 +27,13 @@
|
|
27
27
|
</div>
|
28
28
|
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
<% if @ab_test.new_record? %>
|
31
|
+
<div class="field">
|
32
|
+
<%= f.label :sym, "Code reference symbol" %>
|
33
|
+
<%= f.text_field :sym, :size => 10 %>
|
34
|
+
<span class="description">letters numbers and underscores. NO spaces!</span>
|
35
|
+
</div>
|
36
|
+
<% end %>
|
35
37
|
|
36
38
|
<hr/>
|
37
39
|
|
@@ -15,6 +15,8 @@
|
|
15
15
|
</ul>
|
16
16
|
<% end %>
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
<% unless Rails.env.production? %>
|
19
|
+
<p>
|
20
|
+
<%= button_to "Create a new ABTest", url_for(:action => :new), :method => :get%>
|
21
|
+
</p>
|
22
|
+
<% end %>
|
@@ -1,9 +1,13 @@
|
|
1
1
|
<div class="show">
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
<% if Rails.env.production? %>
|
3
|
+
<span class="description">No Modifying tests in production</span>
|
4
|
+
<% else %>
|
5
|
+
<p>
|
6
|
+
<%= link_to "Edit", edit_ab_test_path(@ab_test) %>
|
7
|
+
|
|
8
|
+
<%= link_to "Delete", @ab_test, :method => :delete, :confirm => "Are you sure?" %>
|
9
|
+
</p>
|
10
|
+
<% end %>
|
7
11
|
|
8
12
|
<h2>I wonder <%= @ab_test.name%>?</h2>
|
9
13
|
<p class="description">
|
@@ -73,16 +73,16 @@ class IWonderMigrations < ActiveRecord::Migration
|
|
73
73
|
add_index :i_wonder_ab_tests, :sym
|
74
74
|
|
75
75
|
create_table :i_wonder_ab_test_goals do |t|
|
76
|
-
t.
|
76
|
+
t.string :ab_test_sym
|
77
77
|
t.text :options # serialized
|
78
78
|
end
|
79
|
-
add_index :i_wonder_ab_test_goals, :
|
79
|
+
add_index :i_wonder_ab_test_goals, :ab_test_sym
|
80
80
|
|
81
81
|
|
82
82
|
|
83
83
|
|
84
84
|
create_table :i_wonder_test_group_memberships do |t|
|
85
|
-
t.
|
85
|
+
t.string :ab_test_sym
|
86
86
|
t.string :test_group_name
|
87
87
|
|
88
88
|
t.string :member_type
|
@@ -90,7 +90,7 @@ class IWonderMigrations < ActiveRecord::Migration
|
|
90
90
|
|
91
91
|
t.timestamps
|
92
92
|
end
|
93
|
-
add_index :i_wonder_test_group_memberships, :
|
93
|
+
add_index :i_wonder_test_group_memberships, :ab_test_sym
|
94
94
|
add_index :i_wonder_test_group_memberships, :test_group_name
|
95
95
|
add_index :i_wonder_test_group_memberships, [:member_type, :member_id], :name => "i_wonder_test_group_memberships_member"
|
96
96
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module IWonder
|
2
|
+
module AbTesting
|
3
|
+
class Loader
|
4
|
+
I_WONDER_DATA_DIR = Rails.root.join("i_wonder").to_s
|
5
|
+
AB_TEST_DATA_DIR = File.join(I_WONDER_DATA_DIR,"ab_tests").to_s
|
6
|
+
|
7
|
+
def self.save_ab_test(ab_test)
|
8
|
+
Dir.mkdir(I_WONDER_DATA_DIR) unless File.exists?(I_WONDER_DATA_DIR)
|
9
|
+
Dir.mkdir(AB_TEST_DATA_DIR) unless File.exists?(AB_TEST_DATA_DIR)
|
10
|
+
|
11
|
+
File.open(file_name(ab_test.sym), 'w') do |file|
|
12
|
+
file.write ab_test.to_xml(:except => [:id, :created_at, :updated_at], :include => :ab_test_goals)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.load_all
|
17
|
+
Dir.open(AB_TEST_DATA_DIR) do |dir|
|
18
|
+
dir.each{|file_name|
|
19
|
+
if file_name =~ /(.+).xml/
|
20
|
+
load_sym($1)
|
21
|
+
end
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.load_sym(ab_test_sym)
|
27
|
+
if File.exists?(file_name(ab_test_sym))
|
28
|
+
File.open(file_name(ab_test_sym), 'r') do |file|
|
29
|
+
|
30
|
+
ab_test = AbTest.find_by_sym(ab_test_sym)
|
31
|
+
ab_test ||= AbTest.new
|
32
|
+
|
33
|
+
ab_test.from_xml(file.read)
|
34
|
+
|
35
|
+
ab_test.save!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.remove_file_for(ab_test)
|
41
|
+
if File.exists?(file_name(ab_test.sym))
|
42
|
+
File.delete(file_name(ab_test.sym))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.file_name(sym)
|
49
|
+
AB_TEST_DATA_DIR + "/#{sym}.xml"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/i_wonder/engine.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
module IWonder
|
2
2
|
class Engine < Rails::Engine
|
3
3
|
# This is for the mounting the engine ===========
|
4
|
-
|
5
4
|
isolate_namespace IWonder
|
5
|
+
|
6
|
+
initializer "i_wonder.loading_tests" do |app|
|
7
|
+
ActiveRecord::Base.send :include, HashAccessor # this is to avoid load order issues from the required gem
|
8
|
+
AbTesting::Loader.load_all
|
9
|
+
end
|
10
|
+
|
6
11
|
end
|
7
12
|
end
|
8
13
|
|
data/lib/i_wonder/version.rb
CHANGED
data/lib/i_wonder.rb
CHANGED
data/test/dummy/db/schema.rb
CHANGED
@@ -35,11 +35,11 @@ ActiveRecord::Schema.define(:version => 20111023231947) do
|
|
35
35
|
add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority"
|
36
36
|
|
37
37
|
create_table "i_wonder_ab_test_goals", :force => true do |t|
|
38
|
-
t.
|
39
|
-
t.text
|
38
|
+
t.string "ab_test_sym"
|
39
|
+
t.text "options"
|
40
40
|
end
|
41
41
|
|
42
|
-
add_index "i_wonder_ab_test_goals", ["
|
42
|
+
add_index "i_wonder_ab_test_goals", ["ab_test_sym"], :name => "index_i_wonder_ab_test_goals_on_ab_test_sym"
|
43
43
|
|
44
44
|
create_table "i_wonder_ab_tests", :force => true do |t|
|
45
45
|
t.string "name"
|
@@ -114,7 +114,7 @@ ActiveRecord::Schema.define(:version => 20111023231947) do
|
|
114
114
|
end
|
115
115
|
|
116
116
|
create_table "i_wonder_test_group_memberships", :force => true do |t|
|
117
|
-
t.
|
117
|
+
t.string "ab_test_sym"
|
118
118
|
t.string "test_group_name"
|
119
119
|
t.string "member_type"
|
120
120
|
t.string "member_id"
|
@@ -122,7 +122,7 @@ ActiveRecord::Schema.define(:version => 20111023231947) do
|
|
122
122
|
t.datetime "updated_at"
|
123
123
|
end
|
124
124
|
|
125
|
-
add_index "i_wonder_test_group_memberships", ["
|
125
|
+
add_index "i_wonder_test_group_memberships", ["ab_test_sym"], :name => "index_i_wonder_test_group_memberships_on_ab_test_sym"
|
126
126
|
add_index "i_wonder_test_group_memberships", ["member_type", "member_id"], :name => "i_wonder_test_group_memberships_member"
|
127
127
|
add_index "i_wonder_test_group_memberships", ["test_group_name"], :name => "index_i_wonder_test_group_memberships_on_test_group_name"
|
128
128
|
|