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 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. You should also check out Vanity (http://vanity.labnotes.org/) as a solution for post of your analytics needs.
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
- belongs_to :ab_test
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.created_at) # event had to come after goal
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 :ab_test_id, :on => :create, :message => "must be unique for this member", :scope => [:member_type, :member_id]
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
- <%= render :partial => "ab_test_group_row", :collection => f.object.test_group_names, :as => :test_group_name %>
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
- <div class="field">
31
- <%= f.label :sym, "Code reference symbol" %>
32
- <%= f.text_field :sym, :size => 10 %>
33
- <span class="description">letters numbers and underscores. NO spaces!</span>
34
- </div>
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
- <p>
19
- <%= button_to "Create a new ABTest", url_for(:action => :new), :method => :get%>
20
- </p>
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
- <p>
3
- <%= link_to "Edit", edit_ab_test_path(@ab_test) %>
4
- |
5
- <%= link_to "Delete", @ab_test, :method => :delete, :confirm => "Are you sure?" %>
6
- </p>
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.integer :ab_test_id
76
+ t.string :ab_test_sym
77
77
  t.text :options # serialized
78
78
  end
79
- add_index :i_wonder_ab_test_goals, :ab_test_id
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.integer :ab_test_id
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, :ab_test_id
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
 
@@ -11,10 +11,9 @@ module IWonder
11
11
  def which_test_group?(event_sym)
12
12
  ab_test = IWonder::AbTest.find_by_sym(event_sym.to_s)
13
13
  return false unless ab_test
14
- ab_test.which_test_group?(self)
14
+ ab_test.which_test_group?(self)
15
15
  end
16
16
 
17
-
18
17
  end
19
18
  end
20
19
  end
@@ -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
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module IWonder
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
data/lib/i_wonder.rb CHANGED
@@ -16,6 +16,7 @@ module IWonder
16
16
 
17
17
  module AbTesting
18
18
  autoload :ActionControllerMixins, 'i_wonder/ab_testing/action_controller_mixins'
19
+ autoload :Loader, 'i_wonder/ab_testing/loader'
19
20
  end
20
21
 
21
22
 
@@ -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.integer "ab_test_id"
39
- t.text "options"
38
+ t.string "ab_test_sym"
39
+ t.text "options"
40
40
  end
41
41
 
42
- add_index "i_wonder_ab_test_goals", ["ab_test_id"], :name => "index_i_wonder_ab_test_goals_on_ab_test_id"
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.integer "ab_test_id"
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", ["ab_test_id"], :name => "index_i_wonder_test_group_memberships_on_ab_test_id"
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