Skip to content

Commit c016dbe

Browse files
committed
closes #116 #127 #399 #358 (again)
1 parent 17293a1 commit c016dbe

File tree

7 files changed

+138
-13
lines changed

7 files changed

+138
-13
lines changed

docs/hyper-model/README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,23 @@ scope :completed,
173173

174174
`unscoped` and `all`: These builtin scopes work just like standard ActiveRecord.
175175

176+
BTW: to save typing you can skip the `all`: Models will respond like enumerators.
177+
176178
```ruby
177179
Word.all.each { |word| LI { word.text }}
178180
```
179181

180-
BTW: to save typing you can skip the `all`: Models will respond like enumerators.
182+
`where`: The where method can be used to filter records:
183+
184+
```ruby
185+
Word.where("LENGTH(text) = ?", n)
186+
```
187+
188+
> The `where` method is implemented internally as a scope on the client that
189+
will execute the where method on the server. If the parameters to the where
190+
method the scope will be updated on the client, but using SQL in the where as
191+
in the above example will get executed on the server.
192+
181193

182194
`find`: takes an id and delivers the corresponding record.
183195

@@ -195,6 +207,21 @@ Word.find_by_text('hello') # short for Word.find_by(text: 'hello')
195207
Word.offset(500).limit(20) # get words 500-519
196208
```
197209

210+
#### Applying Class Methods to Collections
211+
212+
Like Rails if you define a class method on a model, you can apply it to collection of those records, allowing you
213+
to chain methods with scopes (and relationships)
214+
215+
```ruby
216+
class Word < ApplicationRecord
217+
def self.page(pg)
218+
offset(pg-1 * 20).limit(20)
219+
end
220+
end
221+
...
222+
Word.some_scope.page(3)
223+
```
224+
198225
#### Relationships and Aggregations
199226

200227
`belongs_to, has_many, has_one`: These all work as on the server. **However it is important that you fully specify both sides of the relationship.**

release-notes/1.0.alpha1.7.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@
1313

1414

1515
### Breaking Changes
16-
+ [#396](https://github.com/hyperstack-org/hyperstack/issues/396) Fixed: Rejected promises do not move operations to the failure track
1716

1817
### Security
1918

2019
### Added
20+
+ [#116](https://github.com/hyperstack-org/hyperstack/issues/396) ActiveRecord `where` implemented
21+
2122

2223
### Fixed
24+
+ [#396](https://github.com/hyperstack-org/hyperstack/issues/396) Fixed: Rejected promises do not move operations to the failure track
25+
+ [#399](https://github.com/hyperstack-org/hyperstack/issues/399) Pluck now takes multiple keys
26+
+ [#358](https://github.com/hyperstack-org/hyperstack/issues/358) Fixed: (again) changing primary_key causes some failures
27+
+ [#127](https://github.com/hyperstack-org/hyperstack/issues/127) Complex expressions work better in on_client (due to upgrade in Parser gem)
28+
2329

2430
### Not Reproducible
2531
+ [#47](https://github.com/hyperstack-org/hyperstack/issues/47) Added spec - passing a proc for children works fine.

ruby/hyper-model/lib/active_record_base.rb

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def _synchromesh_scope_args_check(args)
1414
else
1515
{ server: args[0] }
1616
end
17-
return opts if opts && opts[:server].respond_to?(:call)
17+
return opts if opts[:server].respond_to?(:call) || RUBY_ENGINE == 'opal'
1818
raise 'must provide either a proc as the first arg or by the '\
1919
'`:server` option to scope and default_scope methods'
2020
end
@@ -393,13 +393,9 @@ def __hyperstack_secure_attributes(acting_user)
393393
end
394394
end
395395

396-
scope :__hyperstack_internal_where_scope,
397-
->(attrs) { where(attrs) }, # server side we just call where
398-
filter: ->(attrs) { !attrs.detect { |k, v| self[k] != v } } # client side optimization
396+
scope :__hyperstack_internal_where_hash_scope, ->(*args) { where(*args) }
399397

400-
def self.where(attrs)
401-
__hyperstack_internal_where_scope(attrs)
402-
end if RUBY_ENGINE == 'opal'
398+
scope :__hyperstack_internal_where_sql_scope, ->(*args) { where(*args) }
403399
end
404400
end
405401

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Add pluck to enumerable... its already done for us in rails 5+
22
module Enumerable
3-
def pluck(key)
4-
map { |element| element[key] }
3+
def pluck(*keys)
4+
map { |element| keys.map { |key| element[key] } }
5+
.flatten(keys.count > 1 ? 0 : 1)
56
end
67
end unless Enumerable.method_defined? :pluck

ruby/hyper-model/lib/reactive_record/active_record/base.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@ class Base
1010
finder_method :__hyperstack_internal_scoped_last
1111
scope :__hyperstack_internal_scoped_last_n, ->(n) { last(n) }
1212

13+
def self.where(*args)
14+
if args[0].is_a? Hash
15+
# we can compute membership in the scope when the arg is a hash
16+
__hyperstack_internal_where_hash_scope(args[0])
17+
else
18+
# otherwise the scope has to always be computed on the server
19+
__hyperstack_internal_where_sql_scope(*args)
20+
end
21+
end
22+
23+
scope :__hyperstack_internal_where_hash_scope,
24+
client: ->(attrs) { !attrs.detect { |k, v| self[k] != v } }
25+
26+
scope :__hyperstack_internal_where_sql_scope
27+
1328
ReactiveRecord::ScopeDescription.new(
1429
self, :___hyperstack_internal_scoped_find_by,
1530
client: ->(attrs) { !attrs.detect { |attr, value| attributes[attr] != value } }

ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,10 +677,15 @@ def method_missing(method, *args, &block)
677677
all.send(method, *args, &block)
678678
elsif ScopeDescription.find(@target_klass, method)
679679
apply_scope(method, *args)
680-
elsif @target_klass.respond_to?(method) && ScopeDescription.find(@target_klass, "_#{method}")
680+
elsif !@target_klass.respond_to?(method)
681+
super
682+
elsif ScopeDescription.find(@target_klass, "_#{method}")
681683
apply_scope("_#{method}", *args).first
682684
else
683-
super
685+
fake_class = Class.new(@target_klass)
686+
fake_class.singleton_class.attr_accessor :all
687+
fake_class.all = self
688+
fake_class.send(method, *args, &block)
684689
end
685690
end
686691

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
require 'spec_helper'
2+
require 'rspec-steps'
3+
4+
RSpec::Steps.steps 'the where method and class delegation', js: true do
5+
6+
before(:each) do
7+
require 'pusher'
8+
require 'pusher-fake'
9+
Pusher.app_id = "MY_TEST_ID"
10+
Pusher.key = "MY_TEST_KEY"
11+
Pusher.secret = "MY_TEST_SECRET"
12+
require "pusher-fake/support/base"
13+
14+
Hyperstack.configuration do |config|
15+
config.transport = :pusher
16+
config.channel_prefix = "synchromesh"
17+
config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options)
18+
end
19+
end
20+
21+
before(:step) do
22+
stub_const 'TestApplicationPolicy', Class.new
23+
TestApplicationPolicy.class_eval do
24+
always_allow_connection
25+
regulate_all_broadcasts { |policy| policy.send_all }
26+
allow_change(to: :all, on: [:create, :update, :destroy]) { true }
27+
end
28+
ApplicationController.acting_user = nil
29+
isomorphic do
30+
User.alias_attribute :surname, :last_name
31+
User.class_eval do
32+
def self.with_size(attr, size)
33+
where("LENGTH(#{attr}) = ?", size)
34+
end
35+
end
36+
end
37+
38+
@user1 = User.create(first_name: "Mitch", last_name: "VanDuyn")
39+
User.create(first_name: "Joe", last_name: "Blow")
40+
@user2 = User.create(first_name: "Jan", last_name: "VanDuyn")
41+
User.create(first_name: "Ralph", last_name: "HooBo")
42+
end
43+
44+
it "can take a hash like value" do
45+
expect do
46+
ReactiveRecord.load { User.where(surname: "VanDuyn").pluck(:id, :first_name) }
47+
end.on_client_to eq User.where(surname: "VanDuyn").pluck(:id, :first_name)
48+
end
49+
50+
it "and will update the collection on the client " do
51+
User.create(first_name: "Paul", last_name: "VanDuyn")
52+
expect do
53+
User.where(surname: "VanDuyn").pluck(:id, :first_name)
54+
end.on_client_to eq User.where(surname: "VanDuyn").pluck(:id, :first_name)
55+
end
56+
57+
it "or it can take SQL plus params" do
58+
expect do
59+
Hyperstack::Model.load { User.where("first_name LIKE ?", "J%").pluck(:first_name, :surname) }
60+
end.on_client_to eq User.where("first_name LIKE ?", "J%").pluck(:first_name, :surname)
61+
end
62+
63+
it "class methods will be called from collections" do
64+
expect do
65+
Hyperstack::Model.load { User.where(last_name: 'VanDuyn').with_size(:first_name, 3).pluck('first_name') }
66+
end.on_client_to eq User.where(last_name: 'VanDuyn').with_size(:first_name, 3).pluck('first_name')
67+
end
68+
69+
it "where-s can be chained (cause they are just class level methods after all)" do
70+
expect do
71+
Hyperstack::Model.load { User.where(last_name: 'VanDuyn').where(first_name: 'Jan').pluck(:id) }
72+
end.on_client_to eq User.where(last_name: 'VanDuyn', first_name: 'Jan').pluck(:id)
73+
end
74+
75+
end

0 commit comments

Comments
 (0)