horza 0.3.9 → 0.4.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 +4 -4
- data/README.md +45 -25
- data/lib/horza.rb +2 -1
- data/lib/horza/adapters/abstract_adapter.rb +4 -0
- data/lib/horza/adapters/{active_record.rb → active_record/active_record.rb} +7 -0
- data/lib/horza/adapters/active_record/arel_join.rb +80 -0
- data/lib/horza/entities/collection.rb +4 -4
- data/spec/active_record_spec.rb +154 -2
- data/spec/horza_spec.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85bbbb02cee6bd4f2fe40c56bd9c37d16307b22f
|
4
|
+
data.tar.gz: 0398edae9ed16b2bb8d13650b1869741e8213a2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f890a99acceefb96521d07a8b0ef41a290438f4672f917bfefd68cc0085379de73dd050de8eff44b442bb0f91daa7e4a39f409893200bc2b2dd4d82dd4560347
|
7
|
+
data.tar.gz: 98f821f258db1e72ce67439eb7aff920098a5c941954cb5fc8192ff491b68a7b12bd8247ce3aee336db9c14ed405a22ccb17b8e96d25195f70f58e767820aa59
|
data/README.md
CHANGED
@@ -16,7 +16,8 @@ end
|
|
16
16
|
**Get Adapter for your ORM Object**
|
17
17
|
```ruby
|
18
18
|
# ActiveRecord Example
|
19
|
-
|
19
|
+
# Don't worry, We don't actually call things horza_users in our codebase, this is just for emphasis
|
20
|
+
horza_user = Horza.adapt(User)
|
20
21
|
|
21
22
|
# Examples
|
22
23
|
user.get(id) # Find by id - Return nil on fail
|
@@ -59,29 +60,48 @@ user.find_all(conditions: conditions, offset: 50)
|
|
59
60
|
|
60
61
|
# Eager loading associations
|
61
62
|
employer.association(target: :users, eager_load: true)
|
62
|
-
```
|
63
|
-
|
64
|
-
## Options
|
65
|
-
|
66
|
-
**Base Options**
|
67
63
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
64
|
+
# Joins are slightly more complex
|
65
|
+
join_params = {
|
66
|
+
with: :employers,
|
67
|
+
on: { employer_id: :id }, # field for adapted model => field for join model
|
68
|
+
fields: {
|
69
|
+
users: [:first_name, :last_name, :email],
|
70
|
+
employers: [:company_name, :address, :phone],
|
71
|
+
},
|
72
|
+
conditions: {
|
73
|
+
users: { last_name: 'Turner' },
|
74
|
+
employers: { company_name: 'Corporation ltd.' }
|
75
|
+
},
|
76
|
+
limit: 20,
|
77
|
+
offset: 5,
|
78
|
+
}
|
79
|
+
horza_user.join(join_params)
|
80
|
+
|
81
|
+
# You can join on multiple fields by passing an array
|
82
|
+
join_params = {
|
83
|
+
with: :employers,
|
84
|
+
on: [
|
85
|
+
{ employer_id: :id }, # field for adapted model => field for join model
|
86
|
+
{ email: :email }, # field for adapted model => field for join model
|
87
|
+
]
|
88
|
+
}
|
89
|
+
horza_user.join(join_params)
|
90
|
+
|
91
|
+
# You can also alias field names
|
92
|
+
join_params = {
|
93
|
+
with: :employers,
|
94
|
+
on: [
|
95
|
+
{ employer_id: :id }, # field for adapted model => field for join model
|
96
|
+
{ email: :email }, # field for adapted model => field for join model
|
97
|
+
],
|
98
|
+
fields: {
|
99
|
+
users: [:id, :last_name, :email],
|
100
|
+
employers: [{id: :employer_id}], # Fieldname in db => alias for output
|
101
|
+
},
|
102
|
+
}
|
103
|
+
horza_user.join(join_params)
|
104
|
+
```
|
85
105
|
|
86
106
|
## Outputs
|
87
107
|
|
@@ -91,7 +111,7 @@ Collection entities behave like arrays.
|
|
91
111
|
|
92
112
|
```ruby
|
93
113
|
# Singular Entity
|
94
|
-
result =
|
114
|
+
result = horza_user.find_first(first_name: 'Blake')
|
95
115
|
|
96
116
|
result # => {"id"=>1, "first_name"=>"Blake", "last_name"=>"Turner", "employer_id"=>1}
|
97
117
|
result.class.name # => "Horza::Entities::Single"
|
@@ -100,7 +120,7 @@ result.id # => 1
|
|
100
120
|
result.id? # => true
|
101
121
|
|
102
122
|
# Collection Entity
|
103
|
-
result =
|
123
|
+
result = horza_user.find_all(last_name: 'Turner')
|
104
124
|
|
105
125
|
result.class.name # => "Horza::Entities::Collection"
|
106
126
|
result.length # => 1
|
data/lib/horza.rb
CHANGED
@@ -2,7 +2,8 @@ require 'horza/adapters/class_methods'
|
|
2
2
|
require 'horza/adapters/instance_methods'
|
3
3
|
require 'horza/adapters/options'
|
4
4
|
require 'horza/adapters/abstract_adapter'
|
5
|
-
require 'horza/adapters/active_record'
|
5
|
+
require 'horza/adapters/active_record/active_record'
|
6
|
+
require 'horza/adapters/active_record/arel_join'
|
6
7
|
require 'horza/core_extensions/string'
|
7
8
|
require 'horza/entities/single'
|
8
9
|
require 'horza/entities/collection'
|
@@ -32,6 +32,13 @@ module Horza
|
|
32
32
|
run_and_convert_exceptions { entity_class(query(options)) }
|
33
33
|
end
|
34
34
|
|
35
|
+
def join(options = {})
|
36
|
+
run_and_convert_exceptions do
|
37
|
+
sql = ArelJoin.sql(self.context, options)
|
38
|
+
entity_class(::ActiveRecord::Base.connection.exec_query(sql).to_a)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
35
42
|
def create!(options = {})
|
36
43
|
run_and_convert_exceptions do
|
37
44
|
record = @context.new(options)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Horza
|
2
|
+
module Adapters
|
3
|
+
class ArelJoin < Options
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def sql(context, options)
|
7
|
+
new(context, options).query.to_sql
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(context, options)
|
12
|
+
@options = options
|
13
|
+
|
14
|
+
@base_table_key = context.table_name.to_sym
|
15
|
+
@join_table_key = options[:with]
|
16
|
+
|
17
|
+
@base_table = Arel::Table.new(@base_table_key)
|
18
|
+
@join_table = Arel::Table.new(@join_table_key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def query
|
22
|
+
join = @base_table.project(fields).join(@join_table).on(predicates)
|
23
|
+
join = join.where(where_clause(@base_table, @base_table_key)) if conditions_for_table?(@base_table_key)
|
24
|
+
join = join.where(where_clause(@join_table, @join_table_key)) if conditions_for_table?(@join_table_key)
|
25
|
+
join = join.take(@options[:limit]) if @options[:limit]
|
26
|
+
join = join.skip(@options[:offset]) if @options[:offset]
|
27
|
+
join
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def fields
|
33
|
+
return [@base_table[:id], Arel.star] unless @options[:fields] && @options[:fields].present?
|
34
|
+
|
35
|
+
@options[:fields].map do |table, fields|
|
36
|
+
fields.map do |field|
|
37
|
+
case table
|
38
|
+
when @base_table_key
|
39
|
+
alias_field(@base_table, field)
|
40
|
+
when @join_table_key
|
41
|
+
alias_field(@join_table, field)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def alias_field(table, field)
|
48
|
+
return table[field.to_s] unless field.is_a?(Hash)
|
49
|
+
table[field.keys.first.to_s].as(field.values.first.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
def predicates
|
53
|
+
@options[:on] = [@options[:on]] unless @options[:on].is_a?(Array)
|
54
|
+
|
55
|
+
predicate_list = @options[:on].map { |on| predicate(on) }.flatten
|
56
|
+
chain_with_method(predicate_list, :and)
|
57
|
+
end
|
58
|
+
|
59
|
+
def predicate(on)
|
60
|
+
on.map { |base_field, join_field| @base_table[base_field].eq(@join_table[join_field]) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def conditions_for_table?(table_key)
|
64
|
+
@options[:conditions].present? && @options[:conditions][table_key].present?
|
65
|
+
end
|
66
|
+
|
67
|
+
def where_clause(table, table_key)
|
68
|
+
clauses = @options[:conditions][table_key].map { |key, value| table[key].eq(value) }
|
69
|
+
chain_with_method(clauses, :and)
|
70
|
+
end
|
71
|
+
|
72
|
+
def chain_with_method(statements, method)
|
73
|
+
statements.reduce(nil) do |chained, statement|
|
74
|
+
next statement if chained.nil?
|
75
|
+
chained.send(method, statement)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Horza
|
2
2
|
module Entities
|
3
3
|
class Collection
|
4
|
-
def initialize(collection)
|
5
|
-
@collection = collection
|
4
|
+
def initialize(collection, singular_entity = nil)
|
5
|
+
@collection, @singular_entity = collection, singular_entity
|
6
6
|
end
|
7
7
|
|
8
8
|
def [](index)
|
@@ -28,8 +28,8 @@ module Horza
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def singular_entity(record)
|
31
|
-
|
32
|
-
singular_entity_class(record).new(
|
31
|
+
attributes = record.respond_to?(:to_hash) ? record.to_hash : Horza.adapter.new(record).to_hash
|
32
|
+
singular_entity_class(record).new(attributes)
|
33
33
|
end
|
34
34
|
|
35
35
|
# Collection classes have the form Horza::Entities::Users
|
data/spec/active_record_spec.rb
CHANGED
@@ -7,8 +7,8 @@ else
|
|
7
7
|
|
8
8
|
ActiveRecord::Migration.suppress_messages do
|
9
9
|
ActiveRecord::Schema.define(:version => 0) do
|
10
|
-
create_table(:employers, force: true) {|t| t.string :name }
|
11
|
-
create_table(:users, force: true) {|t| t.string :first_name; t.string :last_name; t.references :employer; }
|
10
|
+
create_table(:employers, force: true) {|t| t.string :name; t.string :boss_email }
|
11
|
+
create_table(:users, force: true) {|t| t.string :first_name; t.string :last_name; t.string :email; t.references :employer; }
|
12
12
|
create_table(:customers, force: true) {|t| t.string :first_name; t.string :last_name; }
|
13
13
|
create_table(:sports_cars, force: true) {|t| t.string :make; t.references :employer; }
|
14
14
|
create_table(:dummy_models, force: true) {|t| t.string :key }
|
@@ -65,6 +65,8 @@ describe Horza do
|
|
65
65
|
after do
|
66
66
|
HorzaSpec::User.delete_all
|
67
67
|
HorzaSpec::Employer.delete_all
|
68
|
+
HorzaSpec::Customer.delete_all
|
69
|
+
HorzaSpec::SportsCar.delete_all
|
68
70
|
end
|
69
71
|
|
70
72
|
context '#context_for_entity' do
|
@@ -515,6 +517,156 @@ describe Horza do
|
|
515
517
|
end
|
516
518
|
end
|
517
519
|
end
|
520
|
+
|
521
|
+
context '#join' do
|
522
|
+
let(:simple) do
|
523
|
+
{
|
524
|
+
with: :employers,
|
525
|
+
on: { employer_id: :id } # field for adapted model => field for join model
|
526
|
+
}
|
527
|
+
end
|
528
|
+
|
529
|
+
let(:complex_predicate) do
|
530
|
+
{
|
531
|
+
with: :employers,
|
532
|
+
on: [
|
533
|
+
{ employer_id: :id }, # field for adapted model => field for join model
|
534
|
+
{ email: :boss_email }, # field for adapted model => field for join model
|
535
|
+
],
|
536
|
+
fields: {
|
537
|
+
users: [:id, :email],
|
538
|
+
employers: [:boss_email]
|
539
|
+
}
|
540
|
+
}
|
541
|
+
end
|
542
|
+
|
543
|
+
let(:fields) do
|
544
|
+
{
|
545
|
+
fields: {
|
546
|
+
users: [:id, :first_name, :last_name],
|
547
|
+
employers: [{id: :employer_id}, :name]
|
548
|
+
}
|
549
|
+
}.merge(simple)
|
550
|
+
end
|
551
|
+
|
552
|
+
let(:conditions) do
|
553
|
+
{
|
554
|
+
conditions: {
|
555
|
+
users: { last_name: 'Turner' },
|
556
|
+
employers: { name: 'Corporation ltd.' }
|
557
|
+
}
|
558
|
+
}.merge(fields)
|
559
|
+
end
|
560
|
+
|
561
|
+
context 'without conditions' do
|
562
|
+
context 'when one join record exists' do
|
563
|
+
let!(:employer) { HorzaSpec::Employer.create(name: 'Corporation ltd.', boss_email: 'boss@boss.com') }
|
564
|
+
let!(:user) { HorzaSpec::User.create(employer: employer, first_name: 'John', last_name: 'Turner', email: 'email@email.com') }
|
565
|
+
|
566
|
+
context 'without fields' do
|
567
|
+
it 'returns joined record' do
|
568
|
+
result = user_adapter.join(simple)
|
569
|
+
expect(result.length).to eq 1
|
570
|
+
|
571
|
+
expect(result.first.first_name).to eq user.first_name
|
572
|
+
expect(result.first.last_name).to eq user.last_name
|
573
|
+
expect(result.first.email).to eq user.email
|
574
|
+
expect(result.first.boss_email).to eq employer.boss_email
|
575
|
+
expect(result.first.name).to eq employer.name
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
context 'with fields' do
|
580
|
+
it 'returns joined record and the specified fields' do
|
581
|
+
result = user_adapter.join(fields)
|
582
|
+
expect(result.length).to eq 1
|
583
|
+
|
584
|
+
record = result.first
|
585
|
+
|
586
|
+
expect(record.id).to eq user.id
|
587
|
+
expect(record.first_name).to eq user.first_name
|
588
|
+
expect(record.last_name).to eq user.last_name
|
589
|
+
expect(record.name).to eq employer.name
|
590
|
+
expect(record.employer_id).to eq employer.id
|
591
|
+
|
592
|
+
expect(record.respond_to?(:email)).to be false
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
context 'complex predicate' do
|
597
|
+
let!(:match_user) { HorzaSpec::User.create(employer: employer, first_name: 'John', last_name: 'Turner', email: 'boss@boss.com') }
|
598
|
+
let!(:match_user2) { HorzaSpec::User.create(employer: employer, first_name: 'Helen', last_name: 'Jones', email: 'boss@boss.com') }
|
599
|
+
it 'returns joined records and the specified fields' do
|
600
|
+
result = user_adapter.join(complex_predicate)
|
601
|
+
expect(result.length).to eq 2
|
602
|
+
|
603
|
+
expect(result.first.id).to eq match_user.id
|
604
|
+
expect(result.first.email).to eq match_user.email
|
605
|
+
expect(result.first.boss_email).to eq employer.boss_email
|
606
|
+
end
|
607
|
+
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
context 'when no join record exists' do
|
612
|
+
let!(:employer) { HorzaSpec::Employer.create(name: 'Corporation ltd.') }
|
613
|
+
let!(:user) { HorzaSpec::User.create(employer_id: 9999, first_name: 'John', last_name: 'Turner', email: 'email@email.com') }
|
614
|
+
|
615
|
+
it 'returns an empty collection' do
|
616
|
+
result = user_adapter.join(simple)
|
617
|
+
expect(result.length).to eq 0
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
context 'when multiple join records exists' do
|
622
|
+
let!(:employer) { HorzaSpec::Employer.create(name: 'Corporation ltd.') }
|
623
|
+
let!(:user) { HorzaSpec::User.create(employer: employer, first_name: 'John', last_name: 'Turner', email: 'email@turner.com') }
|
624
|
+
let!(:user2) { HorzaSpec::User.create(employer: employer, first_name: 'Adam', last_name: 'Boots', email: 'email@boots.com') }
|
625
|
+
let!(:user3) { HorzaSpec::User.create(employer: employer, first_name: 'Tim', last_name: 'Socks', email: 'email@socks.com') }
|
626
|
+
|
627
|
+
it 'returns an empty collection' do
|
628
|
+
result = user_adapter.join(simple)
|
629
|
+
expect(result.length).to eq 3
|
630
|
+
end
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
context 'with conditions' do
|
635
|
+
let!(:employer) { HorzaSpec::Employer.create(name: 'Corporation ltd.') }
|
636
|
+
let!(:employer2) { HorzaSpec::Employer.create(name: 'BigBucks ltd.') }
|
637
|
+
let!(:user) { HorzaSpec::User.create(employer: employer, first_name: 'John', last_name: 'Turner', email: 'email@turner.com') }
|
638
|
+
let!(:user2) { HorzaSpec::User.create(employer: employer, first_name: 'Adam', last_name: 'Boots', email: 'email@boots.com') }
|
639
|
+
let!(:user3) { HorzaSpec::User.create(employer: employer2, first_name: 'Tim', last_name: 'Socks', email: 'email@socks.com') }
|
640
|
+
|
641
|
+
it 'returns only the joins that match the conditions' do
|
642
|
+
result = user_adapter.join(conditions)
|
643
|
+
expect(result.length).to eq 1
|
644
|
+
expect(result.first.id).to eq user.id
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
context 'limits/offset' do
|
649
|
+
let!(:employer) { HorzaSpec::Employer.create(name: 'Corporation ltd.') }
|
650
|
+
let!(:employer2) { HorzaSpec::Employer.create(name: 'BigBucks ltd.') }
|
651
|
+
let!(:user) { HorzaSpec::User.create(employer: employer, first_name: 'John', last_name: 'Turner', email: 'email@turner.com') }
|
652
|
+
let!(:user2) { HorzaSpec::User.create(employer: employer, first_name: 'Adam', last_name: 'Turner', email: 'email@boots.com') }
|
653
|
+
let!(:user3) { HorzaSpec::User.create(employer: employer2, first_name: 'Tim', last_name: 'Socks', email: 'email@socks.com') }
|
654
|
+
|
655
|
+
it 'limits the joins that match the conditions' do
|
656
|
+
params = conditions.merge(limit: 1)
|
657
|
+
result = user_adapter.join(params)
|
658
|
+
expect(result.length).to eq 1
|
659
|
+
expect(result.first.id).to eq user.id
|
660
|
+
end
|
661
|
+
|
662
|
+
it 'offsets the joins that match the conditions' do
|
663
|
+
params = conditions.merge(offset: 1)
|
664
|
+
result = user_adapter.join(params)
|
665
|
+
expect(result.length).to eq 1
|
666
|
+
expect(result.first.id).to eq user2.id
|
667
|
+
end
|
668
|
+
end
|
669
|
+
end
|
518
670
|
end
|
519
671
|
|
520
672
|
describe 'Entities' do
|
data/spec/horza_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: horza
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Blake Turner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
@@ -105,7 +105,8 @@ files:
|
|
105
105
|
- README.md
|
106
106
|
- lib/horza.rb
|
107
107
|
- lib/horza/adapters/abstract_adapter.rb
|
108
|
-
- lib/horza/adapters/active_record.rb
|
108
|
+
- lib/horza/adapters/active_record/active_record.rb
|
109
|
+
- lib/horza/adapters/active_record/arel_join.rb
|
109
110
|
- lib/horza/adapters/class_methods.rb
|
110
111
|
- lib/horza/adapters/instance_methods.rb
|
111
112
|
- lib/horza/adapters/options.rb
|