i_wonder 0.0.6 → 0.0.7

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/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