querii 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -10
  3. data/lib/querii/query_object.rb +34 -18
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 446635ffc0f477018660c9586b56705a49e3b899
4
- data.tar.gz: 5fb91f4beedc05e8d1e31f357548ce22a6f73a62
3
+ metadata.gz: bf2dcc2616587bd7e2f4533aa0675ac139c56fd1
4
+ data.tar.gz: 832407b904834d3d10adc3ef22acef6e93e41df2
5
5
  SHA512:
6
- metadata.gz: 6c0b430fb59333ddff6abc66cb01fc48be087b66db5f97151851bab6750053ba60d3d3a58113e110ba23fe7171685b502ff2768f79abc4512519aecc5f9d92a8
7
- data.tar.gz: 49a835f5f301a63c8db269e5f8f80bccad9b783c14b10be88f89b0662333fa8d11b680c6c2a37d16e292137613ccec268f668541d13bfd5aea6e9de7d418154e
6
+ metadata.gz: dc9da4056c84585c20338b699b61102874b3f95ebac86ab237b4967410bd4c4e859cea1d70e606f0c52da7b4dd8cb1c5e4306ab649fa44a0dc5500cb4e544661
7
+ data.tar.gz: f7dcc042f860a3575c79e8932a600dc615c6e3f25652f977de628ed38465b1af278ab3239751cb1b56faeca2a3ae106bfe3c6c2fe8d3211951fa0acb85251b18
data/README.md CHANGED
@@ -6,7 +6,11 @@ Querii makes it easy to create query objects that return active record relations
6
6
 
7
7
  ## Why?
8
8
 
9
- Querii allows you to extract complicated query collection logic out of a ActiveRecord model to prevent littering the base model and encourage breaking up your query logic into multiple methods, constants, etc, while still returning a standard activerecord collection (now extended by your additional querii methods) that can be further acted upon.
9
+ Querii allows you to extract complicated query collection logic out of an ActiveRecord model.
10
+
11
+ This prevents littering the base model and encourages breaking up your query logic into multiple methods, constants, etc. Querii creates a singleton that returns a standard activerecord collection (now extended by your additional querii scopes) that can be further acted upon.
12
+
13
+ This provides a pattern for extracting complicated common queries from your model or one off queries from a controller for easier testing and separation of concerns.
10
14
 
11
15
  ## Basic Usage
12
16
 
@@ -15,11 +19,9 @@ module Accounts
15
19
  module FancySubset
16
20
  include Querii::QueryObject
17
21
 
18
- module Scopes
19
- def applied(*args, **key_args)
20
- where(cool: 'things', many: key_args.fetch(:hats, 2))
21
- end
22
- end
22
+ default_scope { where(cool: 'things') }
23
+
24
+ scope :filter, ->(hats: 2) { where(hats: hats) }
23
25
  end
24
26
  end
25
27
 
@@ -27,17 +29,26 @@ end
27
29
  Accounts::FancySubset.call # or .all
28
30
 
29
31
  # or more advanced chain
30
- Accounts::FancySubset.call(hats: 4).where(typical: 'filtered')
32
+ Accounts::FancySubset.call.filter(hats: 4).where(typical: 'filtered')
31
33
  # ...Account::ActiveRecord_Relation < ActiveRecord::Relation
32
34
  ```
33
35
 
34
- Querii infers the base collection from the top level query object namespace of `Accounts` -> `Account.all`, you can override this by setting `self.default_relation = 'ClassName'` within your module. Querii defines a default `Scopes` module that will just return all but to do anything useful by default you will need to define your own `Scopes` module. During a query this module is extended onto the base `ActiveRecord::Relation` and the `applied` method is executed returning an active record collection that can further be queried by both any additional methods defined within your Querii object `Scopes` and scopes or conditions on the standard AR class.
36
+ ### Base class
35
37
 
36
- ## More
38
+ Querii infers the base model from the top level query object namespace. For the example above `Accounts` represents `Account`. You can easily override this by setting `self.model_name = 'ClassName'` within your query module.
37
39
 
38
40
  ### Default scope
39
41
 
40
- The `applied` method is entirely optional and designed for query objects that only contain a single base scope always intended to be called so you can simply do `MyQueryObj.call` or equivalently `MyQueryObj.all`. Additionally the applied method can accept and use any number of args or keyword args.
42
+ A default scope is automatically defined for you with a collection of `.all`. You can set a customized default by calling `default_scope` and passing a block, proc, or lambda.
43
+
44
+ The default scope is designed for query objects that only contain a single base scope always intended to be called so you can simply do `MyQueryObj.call` or equivalently `MyQueryObj.all`.
45
+
46
+ ### How it works
47
+ Querii dynamically defines a `Scopes` module when included that contains scopes defined either via `default_scope` or `scope :name, ->{}`. This pattern is taken from AR.
48
+
49
+ During a query this module is extended onto the base `ActiveRecord::Relation` and the defined default scope method is called. This returns an active record collection that can further be queried by both additional scopes defined within your Querii object and scopes or conditions on the base AR class.
50
+
51
+ Scopes can also be defined as methods explicitly by opening up the scopes class within your query object.
41
52
 
42
53
  ### Passing an already filtered relation
43
54
 
@@ -5,36 +5,52 @@ module Querii
5
5
 
6
6
  base.class_eval do
7
7
  # dynamically defines a base Scopes module
8
- self.const_set(
9
- :Scopes,
10
- Module.new do
11
- def applied(*args, **key_args)
12
- all
13
- end
14
- end
15
- )
8
+ self.const_set(:Scopes, Module.new)
9
+ default_scope { all }
16
10
  end
17
11
  end
18
12
 
19
13
  module ClassMethods
20
- def call(*args, relation: default_relation_all, **key_args)
21
- relation.extending(self::Scopes).applied(args, key_args)
14
+ # optionally override the default determined by top level namespace
15
+ attr_accessor :model_name
16
+
17
+ def call(*args, relation: base_class.all, **kargs)
18
+ relation.extending(self::Scopes).default(*args, **kargs)
22
19
  end
23
20
 
24
- def default_relation_all
25
- # .all returns current scope || all
26
- relation_base.all
21
+ alias_method :all, :call
22
+
23
+ def scope(name, body, &block)
24
+ unless body.respond_to?(:call)
25
+ raise ArgumentError, 'The scope body needs to be callable.'
26
+ end
27
+
28
+ self::Scopes.send(:define_method, name) do |*args, **kargs|
29
+ # unfortunately empty double splatted kargs still count as arguments
30
+ # for a lambda's argument arity checking
31
+ if kargs.empty?
32
+ scope = all.scoping { instance_exec(*args, &body) }
33
+ else
34
+ scope = all.scoping { instance_exec(*args, **kargs, &body) }
35
+ end
36
+
37
+ scope || all
38
+ end
39
+ end
40
+
41
+ def default_scope(scope = nil)
42
+ scope = Proc.new if block_given?
43
+ scope(:default, scope)
27
44
  end
28
45
 
29
46
  # queries are namespaced by plural model name
30
47
  # from which we can infer query base model
31
- def relation_base
32
- default_relation.to_s.constantize || self.to_s.split('::').first.singularize.constantize
48
+ def base_class
49
+ klass = model_name || self.to_s.split('::').first.singularize
50
+ klass.to_s.constantize
33
51
  end
52
+ private :base_class
34
53
 
35
- alias_method :all, :call
36
-
37
- attr_accessor :default_relation
38
54
  end
39
55
  end
40
56
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: querii
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-03-15 00:00:00.000000000 Z
12
+ date: 2017-03-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler