Skip to content

Commit

Permalink
closes #116 #127 #399 #358 (again)
Browse files Browse the repository at this point in the history
  • Loading branch information
catmando committed Apr 2, 2021
1 parent 17293a1 commit c016dbe
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 13 deletions.
29 changes: 28 additions & 1 deletion docs/hyper-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,23 @@ scope :completed,

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

BTW: to save typing you can skip the `all`: Models will respond like enumerators.

```ruby
Word.all.each { |word| LI { word.text }}
```

BTW: to save typing you can skip the `all`: Models will respond like enumerators.
`where`: The where method can be used to filter records:

```ruby
Word.where("LENGTH(text) = ?", n)
```

> The `where` method is implemented internally as a scope on the client that
will execute the where method on the server. If the parameters to the where
method the scope will be updated on the client, but using SQL in the where as
in the above example will get executed on the server.


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

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

#### Applying Class Methods to Collections

Like Rails if you define a class method on a model, you can apply it to collection of those records, allowing you
to chain methods with scopes (and relationships)

```ruby
class Word < ApplicationRecord
def self.page(pg)
offset(pg-1 * 20).limit(20)
end
end
...
Word.some_scope.page(3)
```

#### Relationships and Aggregations

`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.**
Expand Down
8 changes: 7 additions & 1 deletion release-notes/1.0.alpha1.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@


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

### Security

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


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


### Not Reproducible
+ [#47](https://github.com/hyperstack-org/hyperstack/issues/47) Added spec - passing a proc for children works fine.
Expand Down
10 changes: 3 additions & 7 deletions ruby/hyper-model/lib/active_record_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def _synchromesh_scope_args_check(args)
else
{ server: args[0] }
end
return opts if opts && opts[:server].respond_to?(:call)
return opts if opts[:server].respond_to?(:call) || RUBY_ENGINE == 'opal'
raise 'must provide either a proc as the first arg or by the '\
'`:server` option to scope and default_scope methods'
end
Expand Down Expand Up @@ -393,13 +393,9 @@ def __hyperstack_secure_attributes(acting_user)
end
end

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

def self.where(attrs)
__hyperstack_internal_where_scope(attrs)
end if RUBY_ENGINE == 'opal'
scope :__hyperstack_internal_where_sql_scope, ->(*args) { where(*args) }
end
end

Expand Down
5 changes: 3 additions & 2 deletions ruby/hyper-model/lib/enumerable/pluck.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Add pluck to enumerable... its already done for us in rails 5+
module Enumerable
def pluck(key)
map { |element| element[key] }
def pluck(*keys)
map { |element| keys.map { |key| element[key] } }
.flatten(keys.count > 1 ? 0 : 1)
end
end unless Enumerable.method_defined? :pluck
15 changes: 15 additions & 0 deletions ruby/hyper-model/lib/reactive_record/active_record/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ class Base
finder_method :__hyperstack_internal_scoped_last
scope :__hyperstack_internal_scoped_last_n, ->(n) { last(n) }

def self.where(*args)
if args[0].is_a? Hash
# we can compute membership in the scope when the arg is a hash
__hyperstack_internal_where_hash_scope(args[0])
else
# otherwise the scope has to always be computed on the server
__hyperstack_internal_where_sql_scope(*args)
end
end

scope :__hyperstack_internal_where_hash_scope,
client: ->(attrs) { !attrs.detect { |k, v| self[k] != v } }

scope :__hyperstack_internal_where_sql_scope

ReactiveRecord::ScopeDescription.new(
self, :___hyperstack_internal_scoped_find_by,
client: ->(attrs) { !attrs.detect { |attr, value| attributes[attr] != value } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -677,10 +677,15 @@ def method_missing(method, *args, &block)
all.send(method, *args, &block)
elsif ScopeDescription.find(@target_klass, method)
apply_scope(method, *args)
elsif @target_klass.respond_to?(method) && ScopeDescription.find(@target_klass, "_#{method}")
elsif !@target_klass.respond_to?(method)
super
elsif ScopeDescription.find(@target_klass, "_#{method}")
apply_scope("_#{method}", *args).first
else
super
fake_class = Class.new(@target_klass)
fake_class.singleton_class.attr_accessor :all
fake_class.all = self
fake_class.send(method, *args, &block)
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require 'spec_helper'
require 'rspec-steps'

RSpec::Steps.steps 'the where method and class delegation', js: true do

before(:each) do
require 'pusher'
require 'pusher-fake'
Pusher.app_id = "MY_TEST_ID"
Pusher.key = "MY_TEST_KEY"
Pusher.secret = "MY_TEST_SECRET"
require "pusher-fake/support/base"

Hyperstack.configuration do |config|
config.transport = :pusher
config.channel_prefix = "synchromesh"
config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret, use_tls: false}.merge(PusherFake.configuration.web_options)
end
end

before(:step) do
stub_const 'TestApplicationPolicy', Class.new
TestApplicationPolicy.class_eval do
always_allow_connection
regulate_all_broadcasts { |policy| policy.send_all }
allow_change(to: :all, on: [:create, :update, :destroy]) { true }
end
ApplicationController.acting_user = nil
isomorphic do
User.alias_attribute :surname, :last_name
User.class_eval do
def self.with_size(attr, size)
where("LENGTH(#{attr}) = ?", size)
end
end
end

@user1 = User.create(first_name: "Mitch", last_name: "VanDuyn")
User.create(first_name: "Joe", last_name: "Blow")
@user2 = User.create(first_name: "Jan", last_name: "VanDuyn")
User.create(first_name: "Ralph", last_name: "HooBo")
end

it "can take a hash like value" do
expect do
ReactiveRecord.load { User.where(surname: "VanDuyn").pluck(:id, :first_name) }
end.on_client_to eq User.where(surname: "VanDuyn").pluck(:id, :first_name)
end

it "and will update the collection on the client " do
User.create(first_name: "Paul", last_name: "VanDuyn")
expect do
User.where(surname: "VanDuyn").pluck(:id, :first_name)
end.on_client_to eq User.where(surname: "VanDuyn").pluck(:id, :first_name)
end

it "or it can take SQL plus params" do
expect do
Hyperstack::Model.load { User.where("first_name LIKE ?", "J%").pluck(:first_name, :surname) }
end.on_client_to eq User.where("first_name LIKE ?", "J%").pluck(:first_name, :surname)
end

it "class methods will be called from collections" do
expect do
Hyperstack::Model.load { User.where(last_name: 'VanDuyn').with_size(:first_name, 3).pluck('first_name') }
end.on_client_to eq User.where(last_name: 'VanDuyn').with_size(:first_name, 3).pluck('first_name')
end

it "where-s can be chained (cause they are just class level methods after all)" do
expect do
Hyperstack::Model.load { User.where(last_name: 'VanDuyn').where(first_name: 'Jan').pluck(:id) }
end.on_client_to eq User.where(last_name: 'VanDuyn', first_name: 'Jan').pluck(:id)
end

end

0 comments on commit c016dbe

Please sign in to comment.