horza 0.3.9 → 0.4.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: 05dec4f4ed4ea42ae1721a8b519496ec70f2a803
4
- data.tar.gz: 3fbd9ab81b756c4e4666a5334d7dda52b44a6fca
3
+ metadata.gz: 85bbbb02cee6bd4f2fe40c56bd9c37d16307b22f
4
+ data.tar.gz: 0398edae9ed16b2bb8d13650b1869741e8213a2c
5
5
  SHA512:
6
- metadata.gz: 3afecde1421ecf764dfb2b8ebe1a3f7450526632488fc052376e46c1f0f35d7b3fd8dec25f2465bd3a1db3f65dd3dbef2041064ef7f53c59f03e0f7d88b13179
7
- data.tar.gz: 42e7183c12b8733dd2db997ebccb2df9741c3ef5e5dbf5b0f57c7ef01c1f33684b280698594e52ae5c53b57c41c14c30ad8831678474cf8d7e3b8ee958728640
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
- user = Horza.adapt(User)
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
- Key | Type | Details
69
- --- | ---- | -------
70
- `conditions` | Hash | Key value pairs for the query
71
- `order` | Hash | { `field` => `:asc`/`:desc` }
72
- `limit` | Integer | Number of records to return
73
- `offset` | Integer | Number of records to offset
74
- `id` | Integer | The id of the root object (associations only)
75
- `target` | Symbol | The target of the association - ie. employer.users would have a target of :users (associations only)
76
- `eager_load` | Boolean | Whether to eager_load the association (associations only)
77
-
78
- **Association Options**
79
-
80
- Key | Type | Details
81
- --- | ---- | -------
82
- `id` | Integer | The id of the root object
83
- `target` | Symbol | The target of the association - ie. employer.users would have a target of :users
84
- `eager_load` | Boolean | Whether to eager_load the association
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 = user.find_first(first_name: 'Blake')
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 = user.find_all(last_name: 'Turner')
123
+ result = horza_user.find_all(last_name: 'Turner')
104
124
 
105
125
  result.class.name # => "Horza::Entities::Collection"
106
126
  result.length # => 1
@@ -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'
@@ -29,6 +29,10 @@ module Horza
29
29
  not_implemented_error
30
30
  end
31
31
 
32
+ def join(options = {})
33
+ not_implemented_error
34
+ end
35
+
32
36
  def create!(options = {})
33
37
  not_implemented_error
34
38
  end
@@ -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
- adapter = Horza.adapter.new(record)
32
- singular_entity_class(record).new(adapter.to_hash)
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
@@ -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
@@ -76,7 +76,7 @@ describe Horza::Adapters::Options do
76
76
  offset: 10,
77
77
  target: :sports_cars,
78
78
  via: [:employer],
79
- eager_load: true
79
+ eager_load: true,
80
80
  }
81
81
  end
82
82
 
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.3.9
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-01 00:00:00.000000000 Z
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