From 2e478d8537de717595193b5534476c131fc83e19 Mon Sep 17 00:00:00 2001 From: Dan Johnson <ComputerDruid@gmail.com> Date: Mon, 24 Jun 2013 18:30:32 -0400 Subject: [PATCH 1/3] TopicLinkClick: convert 'ip' (bigint) -> 'ip_address' (inet) When accessed over IPv6, the ip address of the user is a 128-bit number, too big for PostgreSQL's bigint data type. Since PostgresSQL has the built-in inet type, which handles both IPv4 and IPv6 addresses, we should use that instead. Where this is done elsewhere in the codebase, the column is called ip_address, so we should follow that convention as well. This migration uses a SQL command to populate the new field from the old one, so as not to rely on the TopicLinkClick model class, which should keep the migration from failing if that class is modified in the future. --- app/models/topic_link_click.rb | 8 +++---- ..._change_ip_to_inet_in_topic_link_clicks.rb | 21 +++++++++++++++++++ spec/models/topic_link_click_spec.rb | 4 ++-- spec/models/topic_link_spec.rb | 2 +- 4 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20130624203206_change_ip_to_inet_in_topic_link_clicks.rb diff --git a/app/models/topic_link_click.rb b/app/models/topic_link_click.rb index 560450525..174122b6c 100644 --- a/app/models/topic_link_click.rb +++ b/app/models/topic_link_click.rb @@ -5,10 +5,8 @@ class TopicLinkClick < ActiveRecord::Base belongs_to :topic_link, counter_cache: :clicks belongs_to :user - has_ip_address :ip - validates_presence_of :topic_link_id - validates_presence_of :ip + validates_presence_of :ip_address # Create a click from a URL and post_id def self.create_from(args={}) @@ -28,7 +26,7 @@ class TopicLinkClick < ActiveRecord::Base rate_key = "link-clicks:#{link.id}:#{args[:user_id] || args[:ip]}" if $redis.setnx(rate_key, "1") $redis.expire(rate_key, 1.day.to_i) - create!(topic_link_id: link.id, user_id: args[:user_id], ip: args[:ip]) + create!(topic_link_id: link.id, user_id: args[:user_id], ip_address: args[:ip]) end args[:url] @@ -43,9 +41,9 @@ end # id :integer not null, primary key # topic_link_id :integer not null # user_id :integer -# ip :integer not null # created_at :datetime not null # updated_at :datetime not null +# ip_address :string not null # # Indexes # diff --git a/db/migrate/20130624203206_change_ip_to_inet_in_topic_link_clicks.rb b/db/migrate/20130624203206_change_ip_to_inet_in_topic_link_clicks.rb new file mode 100644 index 000000000..a0c3924bf --- /dev/null +++ b/db/migrate/20130624203206_change_ip_to_inet_in_topic_link_clicks.rb @@ -0,0 +1,21 @@ +require 'ipaddr' + +class ChangeIpToInetInTopicLinkClicks < ActiveRecord::Migration + def up + add_column :topic_link_clicks, :ip_address, :inet + + execute "UPDATE topic_link_clicks SET ip_address = inet( + (ip >> 24 & 255) || '.' || + (ip >> 16 & 255) || '.' || + (ip >> 8 & 255) || '.' || + (ip >> 0 & 255) + );" + + change_column :topic_link_clicks, :ip_address, :inet, { :null => false } + remove_column :topic_link_clicks, :ip + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/spec/models/topic_link_click_spec.rb b/spec/models/topic_link_click_spec.rb index fce47cb59..1580de1ac 100644 --- a/spec/models/topic_link_click_spec.rb +++ b/spec/models/topic_link_click_spec.rb @@ -26,7 +26,7 @@ describe TopicLinkClick do context 'create' do before do - TopicLinkClick.create(topic_link: @topic_link, ip: '192.168.1.1') + TopicLinkClick.create(topic_link: @topic_link, ip_address: '192.168.1.1') end it 'creates the forum topic link click' do @@ -39,7 +39,7 @@ describe TopicLinkClick do end it 'serializes and deserializes the IP' do - TopicLinkClick.first.ip.to_s.should == '192.168.1.1' + TopicLinkClick.first.ip_address.to_s.should == '192.168.1.1' end end diff --git a/spec/models/topic_link_spec.rb b/spec/models/topic_link_spec.rb index 820d45b59..29d7422a2 100644 --- a/spec/models/topic_link_spec.rb +++ b/spec/models/topic_link_spec.rb @@ -236,7 +236,7 @@ describe TopicLink do it 'has the correct results' do TopicLink.extract_from(post) topic_link = post.topic.topic_links.first - TopicLinkClick.create(topic_link: topic_link, ip: '192.168.1.1') + TopicLinkClick.create(topic_link: topic_link, ip_address: '192.168.1.1') counts_for[post.id].should be_present counts_for[post.id].find {|l| l[:url] == 'http://google.com'}[:clicks].should == 0 From 9f6b7889a8b516a9720e26496ae8235538166dce Mon Sep 17 00:00:00 2001 From: Dan Johnson <ComputerDruid@gmail.com> Date: Tue, 25 Jun 2013 00:18:54 -0400 Subject: [PATCH 2/3] views: convert 'ip' (bigint) -> 'ip_address' (inet) This fixes all known issues when connecting to discourse over IPv6. This table has no primary key, so the migration is done with update_all, for each ip address in the views table. Since this table can potentially grow quite large, this process might take a long time. I don't know any way around this, though. This migration uses a SQL command to populate the new field from the old one, so as not to rely on the View model class, which should keep the migration from failing if that class is modified in the future. --- app/models/view.rb | 6 ++--- ...130625022454_change_ip_to_inet_in_views.rb | 22 +++++++++++++++++++ spec/models/view_spec.rb | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20130625022454_change_ip_to_inet_in_views.rb diff --git a/app/models/view.rb b/app/models/view.rb index d7e29c2f2..efe6423e5 100644 --- a/app/models/view.rb +++ b/app/models/view.rb @@ -3,7 +3,7 @@ require 'ipaddr' class View < ActiveRecord::Base belongs_to :parent, polymorphic: true belongs_to :user - validates_presence_of :parent_type, :parent_id, :ip, :viewed_at + validates_presence_of :parent_type, :parent_id, :ip_address, :viewed_at # TODO: This could happen asyncronously def self.create_for(parent, ip, user=nil) @@ -20,7 +20,7 @@ class View < ActiveRecord::Base $redis.expire(redis_key, 1.day.to_i) View.transaction do - View.create(parent: parent, ip: IPAddr.new(ip).to_i, viewed_at: Date.today, user: user) + View.create(parent: parent, ip_address: ip, viewed_at: Date.today, user: user) # Update the views count in the parent, if it exists. if parent.respond_to?(:views) @@ -37,9 +37,9 @@ end # # parent_id :integer not null # parent_type :string(50) not null -# ip :integer not null # viewed_at :date not null # user_id :integer +# ip_address :string not null # # Indexes # diff --git a/db/migrate/20130625022454_change_ip_to_inet_in_views.rb b/db/migrate/20130625022454_change_ip_to_inet_in_views.rb new file mode 100644 index 000000000..30a0e8390 --- /dev/null +++ b/db/migrate/20130625022454_change_ip_to_inet_in_views.rb @@ -0,0 +1,22 @@ +require 'ipaddr' + +class ChangeIpToInetInViews < ActiveRecord::Migration + def up + table = :views + add_column table, :ip_address, :inet + + execute "UPDATE views SET ip_address = inet( + (ip >> 24 & 255) || '.' || + (ip >> 16 & 255) || '.' || + (ip >> 8 & 255) || '.' || + (ip >> 0 & 255) + );" + + change_column table, :ip_address, :inet, { :null => false } + remove_column table, :ip + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/spec/models/view_spec.rb b/spec/models/view_spec.rb index 6a2ac8d9f..b4e0052db 100644 --- a/spec/models/view_spec.rb +++ b/spec/models/view_spec.rb @@ -6,7 +6,7 @@ describe View do it { should belong_to :user } it { should validate_presence_of :parent_type } it { should validate_presence_of :parent_id } - it { should validate_presence_of :ip } + it { should validate_presence_of :ip_address } it { should validate_presence_of :viewed_at } From 98f926f193a75137b327630922f33193da51db4a Mon Sep 17 00:00:00 2001 From: Dan Johnson <ComputerDruid@gmail.com> Date: Tue, 25 Jun 2013 00:31:11 -0400 Subject: [PATCH 3/3] remove unneeded has_ip_address gem --- Gemfile | 1 - Gemfile.lock | 2 -- docs/SOFTWARE.md | 1 - 3 files changed, 4 deletions(-) diff --git a/Gemfile b/Gemfile index 454b161c2..6fd9de1aa 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,6 @@ gem 'fast_xs' gem 'fast_xor', git: 'https://github.com/CodeMonkeySteve/fast_xor.git' gem 'fastimage' gem 'fog', require: false -gem 'has_ip_address' gem 'hiredis' gem 'email_reply_parser' diff --git a/Gemfile.lock b/Gemfile.lock index a5a7f415d..ccb1b7428 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -207,7 +207,6 @@ GEM guard (>= 1.1) spork (>= 0.8.4) handlebars-source (1.0.0.rc4) - has_ip_address (0.0.1) hashie (2.0.4) highline (1.6.18) hike (1.2.2) @@ -484,7 +483,6 @@ DEPENDENCIES guard-rspec guard-spork handlebars-source (= 1.0.0.rc4) - has_ip_address highline hiredis image_optim diff --git a/docs/SOFTWARE.md b/docs/SOFTWARE.md index ba1a139ac..54f26c0c6 100644 --- a/docs/SOFTWARE.md +++ b/docs/SOFTWARE.md @@ -27,7 +27,6 @@ The following Ruby Gems are used in Discourse: * [omniauth-facebook](https://github.com/mkdynamic/omniauth-facebook) * [omniauth-twitter](https://github.com/arunagw/omniauth-twitter) * [omniauth-github](https://github.com/intridea/omniauth-github) -* [has_ip_address](https://rubygems.org/gems/has_ip_address) * [vestal_versions](https://rubygems.org/gems/vestal_versions) * [uglifier](https://rubygems.org/gems/uglifier) * [nokogiri](https://rubygems.org/gems/nokogiri)