skimming 0.0.2 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c31cbb40931809f9bed74b5d90a9c30058734f50a8818d2694e16630a70c493
4
- data.tar.gz: 272a1af1759927b87add863af0112d8c1ac92ca7353eb040892c58afbfefb4b3
3
+ metadata.gz: 26418f0331bb4b3d54054941f86629eec8f48959ef4df9dba5349f9cf61bc0eb
4
+ data.tar.gz: 0c7555240ff2343ee5348bad95be19a5b1ba326b4297598d0cca0c342fc4b141
5
5
  SHA512:
6
- metadata.gz: 28cea3d3f6ae71f06598ddb1e35bbd6aff2a559199460af4893b9484a63ed67a59ffc24a247629a0c99a83e676f24f7cd3eab58847a61ec74c022f09140517ea
7
- data.tar.gz: 71cd69780026514df89d0193a2060196d06def6f0710f253c378f96ae4c2dcf154c2f133b33b669cc2e0165bb8ccd019c612fb2083b5a9ee4ecfe75a0c7ebff1
6
+ metadata.gz: db658918f56316e975ec52d41776afe0f8d9693d907fb72f58b92fe3cbc99319865befe108730a0fe281dca770c4d974ac39863a71e8ffed61b0fa436df7f861
7
+ data.tar.gz: 0133cb9935aed599bdd3eec639276b5e1f5f7e98a76c8aa4682ce1783847ae646195fa6259f2d0d83ea6e5ba1a65a03e096fbc4bbe8751488b1964107dc633fc
data/README.md CHANGED
@@ -15,12 +15,6 @@ In each model you want to have the feature, just put the following.
15
15
 
16
16
  include Skimming::Model
17
17
 
18
- Create `config/skimming.yml` file in your project and compile it as follows for each of the models you included the code above into (let's say for instance you included Skimming::Model into Role model).
19
-
20
- associations:
21
- roles:
22
- class_name: 'Role'
23
-
24
18
  Once this config file is created, you can launch the following command.
25
19
 
26
20
  rails g skimming_migration
@@ -31,43 +25,36 @@ The command above will generate a migration file in your project. Now you can mi
31
25
 
32
26
  ### Use skimming filters of another model
33
27
 
34
- If you want, you can also allow a related model (for example User) to use Role's skimming rules. In that case the User model should have this instead.
28
+ If you want, you can also allow a related model (for example User) to use Role's skimming rules. In that case the in the User model you should do as follows.
35
29
 
36
- include Skimming::ParasiteModel
30
+ skim_throught :roles
37
31
 
38
- And the config file should have one more configuration.
32
+ ## Usage
39
33
 
40
- associations:
41
- roles:
42
- class_name: 'Role'
43
- options:
44
- user:
45
- skim_through:
46
- - 'roles'
34
+ Create one `Skimming::Item` for each class you want your skimmable models to skim. The name of the item has to be PascalCase.
47
35
 
48
- ## Usage
36
+ item = Skimming::Item.create(name: 'Order')
37
+
38
+ Create one `Skimming::Rule` for each condition you want to evaluate when you decide if the skimmable should keep the items of a certain collection.
49
39
 
50
- Create one `Skimming::CollectionFilter` for each class your model needs to skim and assign a rule that has to be satisfied.
40
+ rule = Skimming::Rule.create(statement: '@order.created_at < 1.month.ago')
51
41
 
52
- role.create_collection_filter(object_name: 'order', rule: '@order.complete?')
53
- role.create_collection_filter(object_name: 'pet', rule: '@pet.nice?')
54
- role.create_collection_filter(object_name: 'room', rule: '@room.clean?')
42
+ This, for example, hides all orders older than 1 month from the collection.
55
43
 
56
- You could also assign the same `Skimming::CollectionFilter` to multiple roles since they have a HABTM relation.
44
+ Create one `Skimming::Filter` for each item your model needs to skim and assign the rule.
57
45
 
58
- If now you call `role.skim rooms_collection` only clean rooms will be returned.
46
+ role.create_filter(item: item, rule: rule)
59
47
 
60
- Also user can do that if it has the role assigned. By default the parasite model skims through all its associated models that have included `Skimming::Model` and sums all results, rejecting the records that all of the skimming models have rejected (if any of the skimming models have the record present in the filter, it will be present in the parasite model skimming result). However in some cases you may want to make the parasite model to skim only through one or some of these models. To do so, just pass one of the models or an array of those.
48
+ If now you call `role.skim orders_collection` only orders newer than 1 month ago will be returned.
61
49
 
62
- user.skim rooms_collection, through: :roles
63
- user.skim rooms_collection, through: [:roles, :whatever]
50
+ Also user can do that if it has the role assigned and you have specified `skim_through :roles` in User model.
64
51
 
65
52
  ### Rules
66
53
 
67
54
  You can store in filters rules whatever conditions you like in plain ruby and the string will be evaulated. Inside the rules, object have to be called as instance variables. These instance variables indeed need to be present and can be declared in a few ways.
68
55
 
69
56
  1. The model name of the instance you called `skim` from, like the user, will be defined automatically based on model name, so if you call `user.skim rooms_collection` you will have `@user` variable defined.
70
- 2. Based on the skimmed collection, instance variables are defined. In the case above, `@room` variable will be present for each of the rooms to be evaluated in. The collection name is calculated based on the class of the first object in the collection and will raise an error if is not the same for all collection elements. You can override this calculation specifying the `object_name` of the collection (the skimming will look for collection_filters with that `object_name`)
57
+ 2. Based on the skimmed collection, instance variables are defined. In the case above, `@order` variable will be present for each of the orders to be evaluated in. The collection name is calculated based on the class of the first object in the collection and will raise an error if is not the same for all collection elements. You can override this calculation specifying the `item_name` of the collection (the skimming will look for collection_filters with that `item_name`). This can be necessary if you are filtering throught different classes instances having STI.
71
58
  3. You can pass as third argument an hash of objects like this `user.skim rooms_collection, time: Time.zone.now, whatever: @you_want` and you will have `@time` and `@whatever` variables defined for rule evaluation.
72
59
 
73
60
  WARNING: since the strings get evaluated, i recommend not to allow anyone except project developers to create collection_filters, in order to prevent malicious code from being executed.
@@ -3,39 +3,41 @@ require 'rails/generators'
3
3
  class SkimmingMigrationGenerator < Rails::Generators::Base
4
4
 
5
5
  def create_migration_file
6
- create_file "db/migrate/#{Time.zone.now.strftime("%Y%m%d%H%M%S")}_skimming_migration.rb", migration_data
6
+ create_file "db/migrate/#{Time.zone.now.strftime("%Y%m%d%H%M%S")}_skimming_migration_1_0_0.rb", migration_data
7
7
  end
8
8
 
9
9
  private
10
10
 
11
11
  def migration_data
12
12
  <<MIGRATION
13
- class SkimmingMigration < ActiveRecord::Migration[5.2]
14
- # 0.0.1 Release
15
- def change
16
- unless table_exists? :collection_filters
17
- create_table :collection_filters do |t|
18
- t.string :object_name
19
- t.string :rule
20
-
21
- t.timestamps
22
- end
23
-
24
- #{generate_join_tables_creation_data}
13
+ class SkimmingMigration100 < ActiveRecord::Migration[5.2]
14
+ def change
15
+ unless table_exists? :items
16
+ create_table :items do |t|
17
+ t.string :name
18
+
19
+ t.timestamps
25
20
  end
26
21
  end
27
- end
28
- MIGRATION
29
- end
30
22
 
31
- def generate_join_tables_creation_data
32
- join_tables_creation_data = ""
33
- associations = Skimming.configuration.associations.keys
23
+ unless table_exists? :filters
24
+ create_table :filters do |t|
25
+ t.bigint :item_id
26
+ t.bigint :skimmable_id
27
+ t.string :skimmable_type
34
28
 
35
- associations.each do |association|
36
- join_tables_creation_data += "create_join_table :collection_filters, :#{association}#{"\n " unless association == associations.last}"
37
- end
29
+ t.timestamps
30
+ end
38
31
 
39
- join_tables_creation_data
32
+ create_table :rules do |t|
33
+ t.bigint :filter_id
34
+ t.string :statement
35
+
36
+ t.timestamps
37
+ end
38
+ end
39
+ end
40
+ end
41
+ MIGRATION
40
42
  end
41
43
  end
@@ -0,0 +1,11 @@
1
+ module Skimming
2
+ class Filter < ActiveRecord::Base
3
+ belongs_to :skimmable, polymorphic: true
4
+ belongs_to :item, class_name: 'Skimming::Item', inverse_of: :filters, foreign_key: :item_id
5
+ has_many :rules
6
+
7
+ validates_presence_of :rules
8
+
9
+ scope :for_item, -> (item_name) { joins(:item).where(items: { name: item_name }) }
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Skimming
2
+ class Item < ActiveRecord::Base
3
+ has_many :filters, class_name: 'Skimming::Filter', inverse_of: :item, foreign_key: :item_id
4
+
5
+ validates :name, presence: true, uniqueness: true
6
+
7
+ attr_readonly :name
8
+
9
+ before_validation :classify_name
10
+
11
+ def classify_name
12
+ self.name = self.name.classify
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Skimming
2
+ class Rule < ActiveRecord::Base
3
+ belongs_to :filter, class_name: 'Skimming::Filter', inverse_of: :rule, foreign_key: :filter_id
4
+
5
+ validates_presence_of :statement
6
+ end
7
+ end
@@ -1,6 +1,6 @@
1
- require 'skimming/configuration'
2
1
  require 'skimming/model'
3
- require 'skimming/parasite_model'
4
2
  require 'skimming/skimmer'
5
- require 'models/skimming/collection_filter'
3
+ require 'models/skimming/filter'
4
+ require 'models/skimming/item'
5
+ require 'models/skimming/rule'
6
6
  require 'generators/skimming_migration_generator'
@@ -3,11 +3,17 @@ module Skimming
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- has_and_belongs_to_many :collection_filters, class_name: 'Skimming::CollectionFilter'
6
+ has_many :filters, class_name: 'Skimming::Filter', as: :skimmable
7
7
  end
8
8
 
9
- def skim(collection, object_name: nil, **skimming_instances)
10
- Skimming::Skimmer.new(self, collection, object_name, skimming_instances).skim
9
+ module ClassMethods
10
+ def skim_through(association)
11
+ has_many :filters, through: association, class_name: 'Skimming::Filter', source: :filters
12
+ end
13
+ end
14
+
15
+ def skim(collection, item_name: nil, **skimming_instances)
16
+ Skimming::Skimmer.new(self, collection, item_name, skimming_instances).skim
11
17
  end
12
18
  end
13
19
  end
@@ -1,23 +1,23 @@
1
1
  module Skimming
2
2
  class Skimmer
3
- attr_reader :subject, :collection, :collection_name, :skimming_instances
3
+ attr_reader :subject, :collection, :item_name, :skimming_instances
4
4
 
5
- def initialize(subject, collection, object_name, skimming_instances)
5
+ def initialize(subject, collection, item_name, skimming_instances)
6
6
  @subject = subject
7
7
  @collection = collection
8
- @collection_name = object_name || calculate_collection_name
8
+ @item_name = calculate_item_name
9
9
  @skimming_instances = skimming_instances
10
10
  end
11
11
 
12
12
  def skim
13
13
  set_instance_variables
14
14
 
15
- filters_rules = subject.collection_filters.for_object(collection_name).map(&:rule)
15
+ filters_rules = subject.filters.for_item(item_name).map(&:rules).flatten.map(&:statement)
16
16
 
17
17
  return collection if filters_rules.empty?
18
18
 
19
- skimming_result = collection.select do |collection_object|
20
- instance_variable_set("@#{collection_name}", collection_object)
19
+ skimming_result = collection.select do |collection_item|
20
+ instance_variable_set("@#{item_name.downcase}", collection_item)
21
21
 
22
22
  filters_rules.all? { |rule| eval rule }
23
23
  end
@@ -25,28 +25,16 @@ module Skimming
25
25
  skimming_result
26
26
  end
27
27
 
28
- def skim_through(skimming_associations)
29
- set_instance_variables
30
-
31
- skimming_associations = Skimming.configuration.options[subject.class.name.underscore][:skim_through] if skimming_associations.blank?
32
- skimming_associations = [skimming_associations] unless skimming_associations.respond_to? :each
33
- skimming_result = []
34
-
35
- skimming_associations.each do |association_name|
36
- subject.send(association_name.to_sym).each do |skimming_object|
37
- skimming_result += skimming_object.skim(collection, subject.class.name.downcase.to_sym => subject)
38
- end
39
- end
28
+ private
40
29
 
41
- skimming_result.uniq
42
- end
30
+ def calculate_item_name
31
+ return item_name.to_s.classify if item_name
43
32
 
44
- private
33
+ items_classes = collection.map(&:class).uniq
45
34
 
46
- def calculate_collection_name
47
- raise 'Invalid collection: contains objects with different classes' unless collection.map(&:class).uniq.count == 1
35
+ raise "Invalid collection: contains items with different classes (#{items.classes.join(', ')}). Use same class items or specify their item_name." unless items_classes.count == 1
48
36
 
49
- collection.first.class.name.downcase
37
+ items_classes.first.name.demodulize
50
38
  end
51
39
 
52
40
  def set_instance_variables
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'skimming'
3
- s.version = '0.0.2'
4
- s.date = '2020-04-18'
3
+ s.version = '1.0.0'
4
+ s.date = '2020-09-14'
5
5
  s.summary = "Collections skimming"
6
6
  s.description = "Filter your collections with database-configurable rules"
7
7
  s.authors = ["Valerio Bellaveglia"]
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skimming
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valerio Bellaveglia
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-18 00:00:00.000000000 Z
11
+ date: 2020-09-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Filter your collections with database-configurable rules
14
- email:
14
+ email:
15
15
  executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
@@ -19,18 +19,18 @@ files:
19
19
  - ".gitignore"
20
20
  - README.md
21
21
  - lib/generators/skimming_migration_generator.rb
22
- - lib/models/skimming/collection_filter.rb
22
+ - lib/models/skimming/filter.rb
23
+ - lib/models/skimming/item.rb
24
+ - lib/models/skimming/rule.rb
23
25
  - lib/skimming.rb
24
- - lib/skimming/configuration.rb
25
26
  - lib/skimming/model.rb
26
- - lib/skimming/parasite_model.rb
27
27
  - lib/skimming/skimmer.rb
28
28
  - skimming.gemspec
29
29
  homepage: https://github.com/ValerioBellaveglia/Skimming
30
30
  licenses:
31
31
  - MIT
32
32
  metadata: {}
33
- post_install_message:
33
+ post_install_message:
34
34
  rdoc_options: []
35
35
  require_paths:
36
36
  - lib
@@ -45,8 +45,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
47
  requirements: []
48
- rubygems_version: 3.0.2
49
- signing_key:
48
+ rubygems_version: 3.1.2
49
+ signing_key:
50
50
  specification_version: 4
51
51
  summary: Collections skimming
52
52
  test_files: []
@@ -1,11 +0,0 @@
1
- module Skimming
2
- class CollectionFilter < ActiveRecord::Base
3
- Skimming.configuration.associations.each do |association_name, association_options|
4
- has_and_belongs_to_many association_name.to_sym, class_name: association_options[:class_name], inverse_of: :collection_filters, foreign_key: "#{association_name}_id".to_sym
5
- end
6
-
7
- validates_presence_of :object_name, :rule
8
-
9
- scope :for_object, -> (object_name) { where(object_name: object_name) }
10
- end
11
- end
@@ -1,18 +0,0 @@
1
- module Skimming
2
- def self.configuration
3
- @configuration ||= Configuration.new(configuration_hash)
4
- end
5
-
6
- def self.configuration_hash
7
- @configuration_hash ||= HashWithIndifferentAccess.new(YAML.load_file('config/skimming.yml'))
8
- end
9
-
10
- class Configuration
11
- attr_accessor :associations, :options
12
-
13
- def initialize(configuration_hash)
14
- @associations = configuration_hash[:associations]
15
- @options = configuration_hash[:options]
16
- end
17
- end
18
- end
@@ -1,15 +0,0 @@
1
- module Skimming
2
- module ParasiteModel
3
- extend ActiveSupport::Concern
4
-
5
- included do
6
- Skimming.configuration.options[name.demodulize.underscore][:skim_through].each do |association_name|
7
- has_many :collection_filters, through: association_name.to_sym, class_name: 'Skimming::CollectionFilter'
8
- end
9
- end
10
-
11
- def skim(collection, through: nil, object_name: nil, **skimming_instances)
12
- Skimming::Skimmer.new(self, collection, object_name, skimming_instances).skim_through through
13
- end
14
- end
15
- end