partial_ks 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 68505ca861cdb0855c62be32cd6b9377f5c6f687
4
- data.tar.gz: 1dabcb9334a38d27ccbf823237a031aadc4c8b26
3
+ metadata.gz: a6cd50565fe47324715461ded8eb3123a717f1f8
4
+ data.tar.gz: 2c5c3a0ab04b6a056b29760bc4b4a8aa1a061a24
5
5
  SHA512:
6
- metadata.gz: fcb48cf2bdcc7151bb99b69a684798185ff6344cc7c7f77fd4316642d0b8ff0eb93f3e00a1f3d41c47f02334473f3b11d9c1a842a32abe976e5cd46e0a6b54e7
7
- data.tar.gz: 9e50c50f69aab5dff5a040943406d90e35241baa3adf0cef32ea8ce5abbb685a65839d236c9566cddcce5ab86408cb6227402c9bd04c4aa1c8c947c73e39fdc6
6
+ metadata.gz: b9e40d3d927f2b87ddb13831701eabb48d0fa8d9842fc1a47fcd7d4b87fe20f8244a16c3cff410303c5ce651f8e3e85334873f85eecf524df911a6c96d6e2989
7
+ data.tar.gz: 0108a674dc8eb3c0ab3bba60c11b8f9601d7e64e553b1f2a00e72c48594eeae04c8b30c27de663cc1ed2d0f6fab3fb0ba67090273ea476f12f51041fee012980
data/CHANGES.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ # 0.1.0
4
+
5
+ * Switch to using Rails models rather than table names.
6
+
7
+ You must now specify rails models in your manual_configuration.
8
+
9
+ Remember, if you want to configure all models, you must eager load in development.
10
+
11
+ ```
12
+ Rails.application.eager_load!
13
+ Rails::Engine.subclasses.map(&:eager_load!)
14
+ ```
15
+
16
+ * Reject polymorphic relationships from ever being a candidate parent.
17
+
18
+ This has the implication that a table that wasn't considered top level could be
19
+ top level.
20
+
21
+ * Bugfix: Emit valid where SQL fragments for models which has a assocation with `:foreign_key`
22
+ that does not follow convention
23
+
3
24
  ## 0.0.6
4
25
 
5
26
  Allow manual_configuration to transition to using model names (moving off from table names)
data/README.md CHANGED
@@ -18,15 +18,22 @@ You can specify manual configurations if needed.
18
18
 
19
19
  ```
20
20
  manual_configuration = [
21
- ["users", nil, User.where(:id => [1])], # specify a subset of users. as users have no parent, specify `nil`
22
- ["blog_posts", User] # filter blog_posts by User
21
+ [User, nil, User.where(:id => [1])], # specify a subset of users. as users have no parent, specify `nil`
22
+ [BlogPost, User] # filter blog_posts by User
23
23
  ]
24
24
  ```
25
25
 
26
26
  TODO :
27
27
 
28
28
  * Rename PartialKs::ConfigurationGenerator#call to something better
29
- * What in the world is the tuples in `manual_configuration` meant to be ?
30
29
  * Minimize Public API
30
+ * Tool to run report using bundle exec
31
+
32
+ # Not supported
33
+
34
+ Things that are not supported in this version.
35
+
36
+ * Polymorphic relationships
37
+ * Tables with STI
31
38
 
32
39
 
@@ -0,0 +1,9 @@
1
+ module PartialKs
2
+ def self.all_rails_models
3
+ if defined?(Rails)
4
+ ::Rails.application.eager_load!
5
+ ::Rails::Engine.subclasses.map(&:eager_load!)
6
+ end
7
+ ActiveRecord::Base.direct_descendants
8
+ end
9
+ end
@@ -3,11 +3,11 @@
3
3
  # and attempts to automatically populate the table into the table graph
4
4
  module PartialKs
5
5
  class ConfigurationGenerator
6
- attr_reader :manual_configuration, :table_names
6
+ attr_reader :manual_configuration, :models
7
7
 
8
- def initialize(manual_configuration, table_names: nil)
8
+ def initialize(manual_configuration, models: nil)
9
9
  @manual_configuration = manual_configuration
10
- @table_names = table_names || ActiveRecord::Base.connection.tables
10
+ @models = models || PartialKs.all_rails_models
11
11
  end
12
12
 
13
13
  def call
@@ -16,14 +16,14 @@ module PartialKs
16
16
 
17
17
  protected
18
18
  def all_tables
19
- @all_tables ||= @table_names.map {|table_name| PartialKs::Table.new(table_name) }.select(&:model?).index_by(&:table_name)
19
+ @all_tables ||= models.map {|model| PartialKs::Table.new(model) }.select(&:model?).index_by(&:table_name)
20
20
  end
21
21
 
22
22
  def filtered_tables
23
23
  synced_tables = {}
24
24
 
25
- manual_configuration.each do |table_name_or_model, specified_parent_model, filter_for_table|
26
- table_name = table_name_or_model.is_a?(String) ? table_name_or_model : table_name_or_model.table_name
25
+ manual_configuration.each do |model, specified_parent_model, filter_for_table|
26
+ table_name = model.table_name
27
27
  next unless all_tables[table_name]
28
28
 
29
29
  parent_model = specified_parent_model
@@ -35,8 +35,7 @@ module PartialKs
35
35
 
36
36
  begin
37
37
  inferrer = PartialKs::ParentInferrer.new(table)
38
- #TODO get rid of try!
39
- parent_model = all_tables[inferrer.inferred_parent_table].try!(:model)
38
+ parent_model = inferrer.inferred_parent_class
40
39
  synced_tables[table_name] = PartialKs::FilteredTable.new(table, parent_model)
41
40
  rescue PartialKs::ParentInferrer::CannotInfer
42
41
  next
@@ -8,7 +8,25 @@ class PartialKs::FilteredTable
8
8
  @custom_filter_relation = custom_filter_relation
9
9
  end
10
10
 
11
- def filter_condition
12
- custom_filter_relation || parent_model
11
+ def kitchen_sync_filter
12
+ filter_condition = custom_filter_relation || parent_model
13
+
14
+ if !filter_condition.nil?
15
+ if filter_condition.is_a?(ActiveRecord::Relation) || filter_condition.respond_to?(:where_sql)
16
+ only_filter = filter_condition.where_sql.to_s.sub("WHERE", "")
17
+ elsif filter_condition.is_a?(String)
18
+ only_filter = filter_condition
19
+ else
20
+ # this only supports parents where it's a belongs_to
21
+ # TODO we can make it work with has_many
22
+ # e.g. SomeModel.reflect_on_association(:elses)
23
+ association = table.model.reflect_on_all_associations(:belongs_to).find {|assoc| assoc.class_name == filter_condition.name}
24
+ raise "#{filter_condition.name} not found in #{table.model.name} associations" if association.nil?
25
+
26
+ only_filter = "#{association.foreign_key} IN (#{[0, *filter_condition.pluck(:id)].join(',')})"
27
+ end
28
+
29
+ {"only" => only_filter}
30
+ end
13
31
  end
14
32
  end
@@ -7,11 +7,11 @@ class PartialKs::ParentInferrer
7
7
  @table = table
8
8
  end
9
9
 
10
- def inferred_parent_table
10
+ def inferred_parent_class
11
11
  if table.top_level_table?
12
12
  nil
13
- elsif table.non_nullable_parent_tables.size == 1
14
- table.non_nullable_parent_tables.first
13
+ elsif table.candidate_parent_classes.size == 1
14
+ table.candidate_parent_classes.first
15
15
  else
16
16
  raise CannotInfer, "table has multiple candidates for parents"
17
17
  end
@@ -12,18 +12,10 @@ class PartialKs::Runner
12
12
 
13
13
  generation.each do |table|
14
14
  table_names << table.table_name
15
- filter_config = table.filter_condition
15
+ filter_config = table.kitchen_sync_filter
16
16
 
17
17
  if !filter_config.nil?
18
- if filter_config.is_a?(ActiveRecord::Relation) || filter_config.respond_to?(:where_sql)
19
- only_filter = filter_config.where_sql.to_s.sub("WHERE", "")
20
- elsif filter_config.is_a?(String)
21
- only_filter = filter_config
22
- else
23
- only_filter = "#{filter_config.to_s.foreign_key} IN (#{[0, *filter_config.pluck(:id)].join(',')})"
24
- end
25
-
26
- tables_to_filter[table.table_name] = {"only" => only_filter}
18
+ tables_to_filter[table.table_name] = filter_config
27
19
  end
28
20
  end
29
21
 
@@ -36,7 +28,7 @@ class PartialKs::Runner
36
28
  result = []
37
29
  each_generation.with_index do |generation, depth|
38
30
  generation.each do |table|
39
- result << [table.table_name, table.parent_model, table.filter_condition, depth]
31
+ result << [table.table_name, table.parent_model, table.parent_model || table.custom_filter_relation, depth]
40
32
  end
41
33
  end
42
34
  result
@@ -1,15 +1,10 @@
1
1
  module PartialKs
2
2
  class Table
3
- attr_reader :table_name
3
+ attr_reader :model
4
+ delegate :table_name, :to => :model
4
5
 
5
- def initialize(table_name)
6
- @table_name = table_name
7
- end
8
-
9
- def model
10
- @model ||= table_name.classify.constantize
11
- rescue NameError
12
- nil
6
+ def initialize(model)
7
+ @model = model
13
8
  end
14
9
 
15
10
  # sometimes the table is present, but the model is not defined
@@ -19,15 +14,17 @@ module PartialKs
19
14
  end
20
15
 
21
16
  def top_level_table?
22
- non_nullable_reflections.empty?
17
+ candidate_parent_classes.empty?
23
18
  end
24
19
 
25
- def non_nullable_parent_tables
26
- non_nullable_reflections.map(&:plural_name)
20
+ # NB: can't do polymorphic for now, rails errors on reflection#klass
21
+ # see, e.g. https://github.com/rails/rails/issues/15833
22
+ def candidate_parent_classes
23
+ non_nullable_reflections.reject(&:polymorphic?).map(&:klass)
27
24
  end
28
25
 
29
26
  def parent_tables
30
- belongs_to_reflections.map(&:plural_name)
27
+ belongs_to_reflections.map(&:table_name)
31
28
  end
32
29
 
33
30
  private
@@ -1,3 +1,3 @@
1
1
  module PartialKs
2
- VERSION = '0.0.6'
2
+ VERSION = '0.1.0'
3
3
  end
data/lib/partial_ks.rb CHANGED
@@ -3,6 +3,7 @@ require 'active_record'
3
3
  module PartialKs
4
4
  end
5
5
 
6
+ require_relative 'partial_ks/all_rails_models'
6
7
  require_relative 'partial_ks/filtered_table'
7
8
  require_relative 'partial_ks/parent_inferrer'
8
9
  require_relative 'partial_ks/runner'
data/partial_ks.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  require File.expand_path('../lib/partial_ks/version', __FILE__)
2
2
 
3
- spec = Gem::Specification.new do |gem|
3
+ Gem::Specification.new do |gem|
4
4
  gem.name = 'partial_ks'
5
5
  gem.version = PartialKs::VERSION
6
6
  gem.summary = "Partial KS"
@@ -1,9 +1,5 @@
1
1
  require 'test_helper'
2
2
 
3
- def pks_tables(*args)
4
- args
5
- end
6
-
7
3
  def generator(manual, tables)
8
4
  PartialKs::ConfigurationGenerator.new(manual, tables).call.
9
5
  map {|f| [f.table_name, f.parent_model, f.custom_filter_relation] }
@@ -12,12 +8,12 @@ end
12
8
  describe "generating dependencies" do
13
9
  let(:manual_configuration) do
14
10
  [
15
- ["users", nil, User.where(:id => [1])],
11
+ [User, nil, User.where(:id => [1])],
16
12
  ]
17
13
  end
18
14
 
19
15
  it "auto infers single belongs-to dependencies" do
20
- generator(manual_configuration, table_names: pks_tables("users", "blog_posts")).
16
+ generator(manual_configuration, models: [User, BlogPost]).
21
17
  must_equal [
22
18
  ["users", nil, User.where(:id => [1])],
23
19
  ["blog_posts", User, nil]
@@ -25,34 +21,21 @@ describe "generating dependencies" do
25
21
  end
26
22
 
27
23
  it "auto infers top level tables" do
28
- generator(manual_configuration, table_names: pks_tables("users", "tags")).
24
+ generator(manual_configuration, models: [User, Tag]).
29
25
  must_equal [
30
26
  ["users", nil, User.where(:id => [1])],
31
27
  ["tags", nil, nil]
32
28
  ]
33
29
  end
34
- end
35
30
 
36
- describe "transition to model based dependencies" do
37
- let(:manual_configuration) do
38
- [
39
- [User, nil, User.where(:id => [1])],
40
- ]
41
- end
31
+ it "can infer models with different table_name" do
32
+ model = OldEntry
33
+ model.table_name.wont_equal model.name.tableize
42
34
 
43
- it "auto infers single belongs-to dependencies" do
44
- generator(manual_configuration, table_names: pks_tables("users", "blog_posts")).
35
+ generator(manual_configuration, models: [User, model]).
45
36
  must_equal [
46
37
  ["users", nil, User.where(:id => [1])],
47
- ["blog_posts", User, nil]
48
- ]
49
- end
50
-
51
- it "auto infers top level tables" do
52
- generator(manual_configuration, table_names: pks_tables("users", "tags")).
53
- must_equal [
54
- ["users", nil, User.where(:id => [1])],
55
- ["tags", nil, nil]
38
+ ["cms_table", nil, nil]
56
39
  ]
57
40
  end
58
41
  end
@@ -1,22 +1,31 @@
1
1
  require 'test_helper'
2
2
 
3
- describe "filter condition" do
4
- let(:table) { PartialKs::Table.new("users") }
3
+ describe "kitchen sync filter" do
4
+ let(:table) { PartialKs::Table.new(PostTag) }
5
5
 
6
6
  it "uses parent as the filter" do
7
7
  parent = Tag
8
8
  filtered_table = PartialKs::FilteredTable.new(table, parent)
9
- filtered_table.filter_condition.must_equal parent
9
+ filtered_table.kitchen_sync_filter.must_equal({"only" => 'tag_id IN (0)'})
10
10
  end
11
11
 
12
12
  it "uses the custom filter if provided" do
13
- filter = User.where(:id => 1)
13
+ filter = PostTag.where(:id => [1, 2])
14
14
  filtered_table = PartialKs::FilteredTable.new(table, nil, custom_filter_relation: filter)
15
- filtered_table.filter_condition.must_equal filter
15
+ filtered_table.kitchen_sync_filter.must_equal({"only" => ' "post_tags"."id" IN (1, 2)'})
16
16
  end
17
17
 
18
18
  it "returns nil if parent is nil" do
19
19
  filtered_table = PartialKs::FilteredTable.new(table, nil)
20
- filtered_table.filter_condition.must_be_nil
20
+ filtered_table.kitchen_sync_filter.must_be_nil
21
+ end
22
+
23
+ describe "table with different :foreign_key" do
24
+ let(:table) { PartialKs::Table.new(OldTag) }
25
+
26
+ it "uses the foreign key that's present in the table" do
27
+ filtered_table = PartialKs::FilteredTable.new(table, OldEntry)
28
+ filtered_table.kitchen_sync_filter.must_equal({"only" => 'blog_post_id IN (0)'})
29
+ end
21
30
  end
22
31
  end
@@ -2,17 +2,17 @@ require 'test_helper'
2
2
 
3
3
  describe "inferring parents" do
4
4
  it "infers no parent for a top level table" do
5
- table = PartialKs::Table.new("tags")
6
- PartialKs::ParentInferrer.new(table).inferred_parent_table.must_equal nil
5
+ table = PartialKs::Table.new(Tag)
6
+ PartialKs::ParentInferrer.new(table).inferred_parent_class.must_be_nil
7
7
  end
8
8
 
9
9
  it "infers a parent for a table that has a single belongs_to" do
10
- table = PartialKs::Table.new("blog_posts")
11
- PartialKs::ParentInferrer.new(table).inferred_parent_table.must_equal "users"
10
+ table = PartialKs::Table.new(BlogPost)
11
+ PartialKs::ParentInferrer.new(table).inferred_parent_class.must_equal User
12
12
  end
13
13
 
14
14
  it "infers no parent for a table has multiple belongs_to" do
15
- table = PartialKs::Table.new("post_tags")
16
- lambda { PartialKs::ParentInferrer.new(table).inferred_parent_table }.must_raise PartialKs::ParentInferrer::CannotInfer
15
+ table = PartialKs::Table.new(PostTag)
16
+ lambda { PartialKs::ParentInferrer.new(table).inferred_parent_class }.must_raise PartialKs::ParentInferrer::CannotInfer
17
17
  end
18
18
  end
data/test/runner_test.rb CHANGED
@@ -18,9 +18,9 @@ describe 'running based on output from generator' do
18
18
 
19
19
  let(:generator_output) do
20
20
  [
21
- PartialKs::FilteredTable.new(PartialKs::Table.new("users"), nil, custom_filter_relation: User.where(:id => [1])),
22
- PartialKs::FilteredTable.new(PartialKs::Table.new("tags"), nil),
23
- PartialKs::FilteredTable.new(PartialKs::Table.new("blog_posts"), User),
21
+ PartialKs::FilteredTable.new(PartialKs::Table.new(User), nil, custom_filter_relation: User.where(:id => [1])),
22
+ PartialKs::FilteredTable.new(PartialKs::Table.new(Tag), nil),
23
+ PartialKs::FilteredTable.new(PartialKs::Table.new(BlogPost), User),
24
24
  ]
25
25
  end
26
26
 
@@ -45,7 +45,7 @@ describe 'running based on output from generator' do
45
45
  end
46
46
 
47
47
  it "yields all non-null filters" do
48
- expected_filters = generator_output.each_with_object({}) {|ft, hash| hash[ft.table_name] = ft.filter_condition if ft.filter_condition}
48
+ expected_filters = generator_output.each_with_object({}) {|ft, hash| hash[ft.table_name] = ft.kitchen_sync_filter if ft.kitchen_sync_filter}
49
49
  actual_filters = {}
50
50
 
51
51
  runner.run! do |tables_to_filter, table_names|
@@ -55,7 +55,7 @@ describe 'running based on output from generator' do
55
55
  actual_filters.size.must_equal expected_filters.size
56
56
  actual_filters.keys.must_equal expected_filters.keys
57
57
  expected_filters.each do |table_name, filter_condition|
58
- actual_filters[table_name]["only"].must_be_kind_of String # TODO test different kinds of filters.
58
+ actual_filters[table_name]["only"].must_be_kind_of String
59
59
  end
60
60
  end
61
61
  end
data/test/schema.rb CHANGED
@@ -20,4 +20,14 @@ ActiveRecord::Schema.define(:version => 0) do
20
20
  t.references :tag, null: false
21
21
  t.timestamps null: false
22
22
  end
23
+
24
+ create_table :cms_table, :force => true do |t|
25
+ t.string :cms_id
26
+ t.string :some_legacy_thing
27
+ end
28
+
29
+ create_table :old_tags, :force => true do |t|
30
+ t.integer :blog_post_id
31
+ t.timestamps null: false
32
+ end
23
33
  end
data/test/setup_models.rb CHANGED
@@ -12,3 +12,11 @@ class PostTag < ActiveRecord::Base
12
12
  belongs_to :blog_post
13
13
  belongs_to :tag
14
14
  end
15
+
16
+ class OldEntry < ActiveRecord::Base
17
+ self.table_name = "cms_table"
18
+ end
19
+
20
+ class OldTag < ActiveRecord::Base
21
+ belongs_to :old_entry, :foreign_key => 'blog_post_id'
22
+ end
data/test/table_test.rb CHANGED
@@ -3,8 +3,16 @@ require 'test_helper'
3
3
  describe PartialKs::Table do
4
4
  describe "#model" do
5
5
  it "has a model" do
6
- table = PartialKs::Table.new("users")
6
+ table = PartialKs::Table.new(User)
7
7
  table.must_respond_to :model
8
8
  end
9
9
  end
10
+
11
+ describe "candidate parents" do
12
+ it "returns nothing for a top level table" do
13
+ table = PartialKs::Table.new(User)
14
+
15
+ table.candidate_parent_classes.must_equal []
16
+ end
17
+ end
10
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: partial_ks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thong Kuah
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-02 00:00:00.000000000 Z
11
+ date: 2016-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -37,6 +37,7 @@ files:
37
37
  - README.md
38
38
  - Rakefile
39
39
  - lib/partial_ks.rb
40
+ - lib/partial_ks/all_rails_models.rb
40
41
  - lib/partial_ks/configuration_generator.rb
41
42
  - lib/partial_ks/filtered_table.rb
42
43
  - lib/partial_ks/parent_inferrer.rb