Opal + react = isomorphic ruby that plays well with rails

Facebook’s “React” has a lot going for it. It has a very short learning curve, it fits in well with existing frameworks, it renders isomorphically, and it is well supported.

David Chang has developed a thin wrapper in Opal so now you can access React from ruby. I am working on a branch of this that adds a lot of “noise reducing” syntactic support so the resulting ruby code is clean and easy to read and maintain. I’ll hopefully be getting this branch brought back into the main react.rb gem soon with documentation.

Meanwhile the Facebook team has a react-rails gem that allows react components to be generated directly from (or even as) rails views, while supporting isomorphic pre-rendering on the rails server.

Finally we are also working on a reactive-record gem that will allow access to your rails active models from within react.

Its a little premature to get this topic rolling as while all the above exists, and is being used on a production site, its not documented or even published. Still I thought it might trigger some good discussion.

2 Likes

Sounds great Catmando :+1: I look forward to hearing more and maybe even see your app in production if you’re allowed to share it at some point.

I’d heard of react-rails but didn’t know it was an official FB gem. Might have to take a closer look (am I generally not much of a fan of Facebook haha).

React-Rails is very simple. It just allows you to say <%= react_component ‘MyComponent’ {param1: value1, …} %>
AND it will optionally prerender everything server side by using execjs.

4 Likes

Thank’s for working on that gem, you’re doing a great job. Ruby on client does really rock. But I encountered problems with JSX usage in react.rb classes. I use react-rails with react.rb.
Native js jsx without react.rb works fine, as well as react.rb with it’s DSL, BUT JSX throws errors. Have you tried JSX in react.rb on rails with react-rails gem?

1 Like

We use just the dsl. I like keeping everything 100% Ruby. My fork of the gem has a lot of syntactic help that makes the dsl quite nice. Sorry I am behind getting the tests and docs updated. If anybody wants a live tutorial I’d be happy for the feedback

2 Likes

I wish you best of luck in your work! and tutorial would be great if you have spare time!
There’s no other place to ask: can you please share the concepts of react.rb evolution and how you see it as major contributor:
Will there be first-class rails support with something like gem “rails-react.rb”?
Will there be possibility to prerender components on server?
Are there any plans for router?

1 Like

To directly answer your questions:

Yes we have been integrating already with rails-react so that react.rb will play nicely. Here are the features we have so far:

  1. in you React component you simply add the “export_component” directive to the class. Then its accessible directly in you view file by using the react-rails react_component helper.

  2. In fact you can skip the view completely and just say render_component in your rails controller, which will render your top level React component.

This makes react.rb components first class views that just “work” in your rails environment. But to really work you need access to your active record models, so…

I have been working on reactive-record, which allows access to your active record models from react. This is also integrated with react-rails. When your component is rendered server side the initial set of active record models needed for the first page view are collected, and shipped down with your other assets. The page loads, and than can continue to request and update your active record models.

Philosophically, I want to have a inter-operating collection of gems that developers can pick and choose from as they need, rather than a monolithic framework.

So Opal provides the capability to write in ruby and React.rb provides a beautiful way to write reactive components. If you are already using rails, then you just add the react-rails gem. If you need access to your active-record models then you include the reactive-record gem. There is even an opal-aasm (acts as state machine) gem that lets you structure your components with complex state, as state machines.

The other thing I want to see happen is moving the opal code out of “assets” and into the normal rails directories. For example components should live in the views folder.

Which brings us to controllers, and yes, getting react-router integrated, is next on the list after we get reactive-record deployed.

When? Well here is the deal - all this is being done in aid of restructuring our companies website (www.catprint.com) which is badly in need of a front end redo. So that’s top priority. We are busy rewriting all the views and javascript helpers in opal / react, and hope to be done in about a month. With that a big sigh of relief, and documentation and testing, and we should be able to get these gems available for general use.

Want to help? Please - I could really use some help with getting tests written. In particular reactive-record has got me stumped, as the code by necessity is isomorphic and runs literally in three co-operating environments - the browser, opal running on the server (for server side rendering) and regular ruby running in rails (to access your active record models.) Getting a good test structure around this, is way beyond me, so I could use some help :smile:

2 Likes

oh yeah, and it all works with bootstrap as well :slight_smile:

Here is a more detailed progress report. I know a demo would be even better, but frankly we are working around the clock on rebuilding our current site using Opal + React and just have not had the time! Feel free to view our progress here: http://catprintdemo.fwd.wf/ Hopefully we will be able to extract some demos and tutorials once our conversion is complete next month.

Meanwhile that said I do want to let folks know what we are doing, because we frankly think its fantastically productive.

The basic setup is React.js at the base with an Opal-Ruby wrapper on top of that. The wrapper follows the base semantics of React, but adds a lot of features and syntactic helpers. For example react params, and states are directly accessible as methods in the component. We never interact directly with React.js

Here is a simple component:

class MyComponent
  include React::Component
  define_state :input_string    #states are basically instance variables that cause 
                                #rerendering when they change
  def render                
    div do
      input(type: :text, placeholder: "enter some text").
      on(:change) { |e| input_string! e.target.value }
      if input_string.delete(' ').reverse == input_string.delete(' ')
        "that's a palindrome!".span(class: :palindrome)
      else
        "that is not a palindrome".span(class: :not_palindrome)
      end
    end
  end
end

On top of this is a modified version of react-rails, plus a very simple helper that allows you to directly render a component from a controller, thus the top level component can replace a view. For example

class UserController < ApplicationController
  def index
    render_component users: Users.active # renders Components::Users::Index passing it the active users
  end
end

React-rails also provides prerendering support for your react components. This means that the components will be prerendered on the server and delivered as ready to show html to the browser. All the react code will follow later, and react will take over managing the components state change once its loaded.

On top of this is the reactive_record gem, which provides access to your active record models from within react.
Within your opal-react component you have full access to your ActiveRecord models, and ReactiveRecord provides a stubbed version of ActiveRecord that works both during prerendering and on the client to access your AR models seamlessly within the React

So in Components::Users::Index render method you might have code like this:

users.each do |user|
  email_class.div {user.email}  # thanks to @dancinglightning  for the idea of haml like class prefixes!
  name_class.div {user.name}
end

If these lines are executed during prerendering then we are on the server and we simply execute the AR query as normal and would be equivalent to the following ERB

<% users.each do |user| %>
  <div class="email-class"><%= user.email %></div>
  <div class="name-class"><%= user.name %></div>
<% end %>

Lets say now the value of users changes on the client, for example a new search for users was made. Everything is asynchronous so we don’t know any values yet, so reactive_record will provide dummy values (i.e. empty strings for attributes, and arrays with 1 item for scopes) and queue up a request to the server to get the actual data. When the actual data returns its state changes thus triggering a re-render of any effected components. The requests for data are bundled together and processed on the server as one request to minimize hits to the database as well.

The default behavior is that the user will see the structure of the page form and then a second later get filled with actual values.

The developer need do nothing. However its also possible to add a while_loading attribute to the code block which will be displayed while the data is being fetched. For example:

users.each do |user|
  email_class.div {user.email}
  name_class.div {user.name}
end.while_loading do
  spinner.div {}
end

The last piece of the puzzle is updating the AR models. This all works as expected, you can update any AR model instance (for example user.name = “fred”) and then save the model. The only difference is because things are asynchronous you might attach while_saving, and on_save handlers.

Each attribute and each AR record has its own react state variable, so changes cause immediate update to the appropriate part of the display with no additional code.

Whoops - not quite the last piece: A quick word on testing. Opal-rails comes with Opal-Rspec and we have some little helpers that let you define test cases on components nicely in rspec…

Oh boy - was just reminded that I did not mention react-router. we have an opal-react-router gem that provides a nice wrapper on the react-router library. Again the power of ruby meta-programming allowed us to make a very clean API. Here is an example:

module Components
  module Jobs

    class Doodle

      include React::Router 

      routes do
        route(name: "Gallery", path: "jobs/gallery", handler: Components::Jobs::Gallery)
        route(name: "Personalize", path: "jobs/personalize/:background_id", handler: Components::Jobs::Personalize)
        redirect(from: "", to: "Gallery")
      end

      def show
        div do
          route_handler
        end
      end

    end
  end
end

Why???

We needed to solve 3 problems:

  1. We were sick and tired of having to deal with 4 or even 5 different languages to deliver functionality to our users. HTML, Ruby, Javascript, ERB, and possibly Coffeescript. Now we have one: We write in ruby. Period.

  2. We needed a way to write highly maintainable UI code quickly and efficiently. The problem with UI coding is you get hit at any time from any direction with user inputs, and you have to respond, and keep the UI state correct, and have this state reflected appropriately on the server. Whew. The beauty of React is that it takes this whole problem and says: Just draw the UI based on the current state. Done. Period. Under the hood React will take care of doing that efficiently but you can just think of your UI components as functional entities. State + parameters in, UI out.

  3. We had to work within our existing rails code base. Opal and React solve 1 + 2 without requiring any rethinking of our existing system. None. React components simply replace views, and reactive_record allows us to use our existing AR models.

I have been asked several times about why we don’t rewrite react in Opal. To us React is a low level implementation, its a black box, and its more efficiently implemented directly in JS than in Opal, and besides its maintained. There would be no advantage of rewriting it, and its basic feature set and model of operation is so simple its hard to image an alternative model that would be better.

Other things we have thought about and experimented with:

Node.js + React - Does not solve 1) or 3). Besides not to get into a war here, but Javascript is ugly. Sorry. And besides that why would i give up the rich code base available to me in the rails ecosystem, plus a working and simple tool chain?

Ember. This was my original direction a couple of years ago. Does not solve 1, or 3, and in the end it was way to hard to understand to practically solve 2.

Volt. Love volt, and some key ideas for reactive-record came from volt. Originally I tried developing a “third-rail” gem that would interface volt with rails. I gave up. The problem is that it is too monolithic for our use. I still recommend volt to anybody who has a clean sheet project that is mainly UI and will use volts many advantages.

Where are we at?

We almost all 60 of our current views at least partially done, and have about 1 month of coding and testing to go. Its frankly just too easy.

As a side effect we have had a lot more time to concentrate on just making the pages “nice”, with things like on page routing, and responsive UIs. (try out http://catprintdemo.fwd.wf/jobs/gallery )

One fun note - because of the prerendering, there are sections of code that are “meta-isomorphic” in that they actually run in three different environments:

Opal client (the browser), Opal server (during prerendering) and on the server proper.

2 Likes

That looks so sweet!

About reactive-record part:
I was thinking of a way to implement sort of it before, and one thing I was looking at: that in some div on server serializing model to string besides component, than on client parsing it and storing in array as state, than e.g. new user is added, server replies with jsoned user and it pushed to that array causing state change.

But how reactive-record works (conceptually)? In the example how users are passed to client for server rendered component?

Please don’t drop that project and thanks for developing it. And yeah hello word level tuts (for both server/client parts) will be super cool (if you have time of course)!

p.s. thanks @AstonJ for restoring @catmando 's reply

1 Like

Lets take a simple example to see how it works.

#user_list.rb
class UserList

  include Reactive::Component

  optional_param :users, type: [User]

  def render
    (users || User.all).each do |user|  
      email_class.div {user.email}
      name_class.div {user.name}
    end
  end

end

#user_controller.rb

def user_list
   render_component users: User.active
end

Okay some background to understand. A model like User will exist in three places: On the server in the normal rails environment, as a set of stubs in the V8 javascript engine on the server, and as another set of stubs on the client. The V8 stubs will fetch any data as its needed from the server (because its on the same box) but the client stubs have to queue up any requests, which are then batched up to the server and returned.

But because the client has the models, it does have a lot of info about associations, etc that it can use while waiting for the data to come back from the server.

Okay lets assume prerendering has been turned off, so we can see how the client side progressive rendering works.

The render_component hooks up with the UserList component, and the component is rendered in the V8 engine on the server. Before rendering begins the User.active collection is translated to a list of record ids, and handed over to the components as the users param (params = React Props). The type [User] means an array like collection of users. Its an optional param, but in this case we do give it values.

The placeholder for the component along with the param data (the list of users) is all that exists when the view is delivered to the client (remember we are assuming prerendering is off)

Once the client is loaded the first render begins. We progress through the loop, and as we hit each user a request for the email and name are queued up, but for now the value is just an empty string. Each time the component hits a missing piece of data it attaches itself to a hidden react state variable that will be updated once the values are delivered.

Rendering completes and we will have one div for each user, but the contents will be blank.

When rendering completes, the request for the values is sent to the server, once it returns the data is sorted out, and attached to the correct records, and finally the internal react state is updated, causing any components waiting on data to render.

And the view updates showing your data!

Now lets add prerendering to the mix. If prerendering is on, then everything proceeds as above, EXCEPT the rendering happens on the server, so instead of waiting for values we fetch them from the data base. In this case the view delivered to the client will have all the data, and is effectively the same as if you had run the whole thing as a loop in ERB.

But we do one other thing. As the prerendering proceeds all the data used in the process is collected, and sent down to the client with the view, and will preload the clients mirror of the data base.

Dynamic Searches

What if we didn’t know the list of users, or what if this was a “search” view that displayed a user after some kind of lookup. Well actually its no different than the “no - prerender” case (which is why we started with that.) So lets say the component was rendered without being sent the list of users. Remember if we don’t know the value of an attribute, we just return an empty string. Well if the value is a has_many association then we get back a collection with just one dummy item of the right class. This allows the loop to go around once, so we will know that we need to get the name and email for ALL users (or whatever the scope was.)

The same works for belongs_to associations but in that case a single dummy item is created.

Updating Data

You can update or create new records on the client. When you save them they go up to the server. We treat things slightly differently than active-record. For example if you create a new item and push it on a has_many list, then both the records will be marked as changed, and saving either one, will always save the other. Another difference is if you create a new record it can be destroyed which is will mark it as “unchanged”

Remember the hidden state variable mentioned above? There are 2 other types of these at finer granularity. Each record also has a hidden state that can be used to track if the record is changed, or is being saved. Thus you can put spinners up or “cancel” buttons as appropriate. And each attribute and association also has a state variable that tracks if that specific value has changed. These variables don’t actually exist until some component cares about them, and they are deleted when the component stops caring about them. The different levels of granularity keeps from having massive re-renders going on for every little change.

2 Likes

As far as I’ve seen from source, it predefines route points for fetching - saving, and it kind of evals model info? So it’s basically one entry point for fetching/saving models.

What about security issues, I mean, for example how can one filter saving/fetching model depending on user rights for example. Is there a way you see it or one needs to alter that with custom hacks?

P.S.
BTW, your approach is even cooler than I thought before, it absolutely can replace view part the right way for Rails, I mean it solves the main problem of clientside code of hellish JS frameworks that you had to duplicating things you had on backend for your fronts.

Well adding potential action-cable or faye right now in the mix to your architecture will make Rails having all that Volt can offer.

1 Like

My 2 cents (mbtcs ^^) on the matter:

React.rb, for letting you use reactive view components directly in ruby is worth 100%. You don’t have to write code that updates the view, you just change the state™ - Also, React.rb it is a good substitute of js jsx - like clojurescript (reagent-project.github.io) , and paggio (github.com/meh/paggio) - I imagine a python and a go version of the same concept will come soon.

Also I think that Rails and Sprockets are too big for the job - considering a simple webpage that has js can compile ruby via opal (like the site/doc does: http://opalrb.org/try - you don’t to want to do it on a prod app because it’s client cpu time wasted atm, anyway)

Sample React.rb app featuring Guard and Opal::Build for the build process, vendored dependencies (opal, opal-browser, react, react-rb) so the compiling is a simple process and happens in development - also it can be done and is more similar on something you will do if using nodejs with gulp (webpack seems like spockets, too much code/configs to nail a nail)

Also this compiles a static app so you just need to drag the index.html into a browser (or upload them to dropbox/s3 ^^) or run a simple server like python -m SimpleHTTPServer then open localhost:3000 (my preferred way to do it)

sample app:

rb-js (opal) code:
react_rb_sample_app/app.rb at master · makevoid/react_rb_sample_app · GitHub (from the example but without loading every lib, here I use `` to get the values from js, I could wrap them or use the right libs I know)

It’s not that I don’t like sprokets but there are other alternatives, Tilt for example:
https://github.com/opal/opal/blob/master/lib/opal/sprockets /processor.rb

Also you can take a look at 2 example apps that I’ve made as basic sample apps with react.rb reading from a websocket and updating an array + rendering each of them as element (from a ruby react component):

They’re both following the same logic, realtime, websockets updates the state of the component and the virtual dom gets diffed the changes are rendered

Blockchain transactions visualizer (super cool data):

http://btc-transactions-viz.mkvd.net <<<< tl;dr example app
code: GitHub - makevoid/btc_transactions_viz: Bitcoin Transaction Visualizer - Opal, React.rb, blockchain.info websockets api
bundle: btc_transactions_viz/bundle.rb at master · makevoid/btc_transactions_viz · GitHub

Bitstamp exchange order book (more ‘serious’ data):

http://bitstamp-react-pusher.mkvd.net <<<
code: GitHub - makevoid/bitstamp_react_pusher: Bistamp Orderbook React Viz - Visualizes the Bitstamp BTC/USD Orderbook using Opal and React
bundle: github.com/makevoid/bitstamp_react_pusher/blob/master/bundle.rb

I would like to do cool visualizations in the future if I have enough time

The next step will be choosing a layout (bootstrap, materialize, or nothing) as a base and instead of changing the html view you could change an svg element and/or a d3 one: github.com/esbullington/react-d3 (or github.com/jhudson8/react-chartjs)
The Opal way it seems to be to build wrappers around existing js objects and use them in creative ways, it’s the same feeling I get when I use RubyMotion and look at the current available cool and creative gui solutions.

Also now they leak memory a lot, I would like to optimize the memory consumption, I tried to remove the element after use, it helped: btc_transactions_viz/tx_fetcher.rb at master · makevoid/btc_transactions_viz · GitHub
but it’s still leaking memory, I wonder if that’s a react thing and so I have to use immutable objects or something similar. If anybody here has suggestion / can help me please do :smiley: thanks

tl;dr
this is fun stuff!

I also recommend Roda (or Lotus maybe) as an alternative to Rails - and finally we will be able to share ruby code between the server and the client in a lightweight and efficient way, yay

1 Like

We should discuss more. My hope is to build a series of layered gems so u can pick and choose appropriate technologies. U r right that sometimes rails is overkill.

1 Like

Yes reactive-record’s engine defines a couple of routes: fetch, save and destroy. These act on all models.

Security will have to be solved. It will have to be added to the model since there is no controller. I am thinking of some way of attaching permission logic to associations, and scoping methods.

The problem with sockets is the logic on the server side to determine when to update which sockets. Perhaps the permission logic could also be used to help determine when to update a client. I don’t know, but its not a problem specific to react.

Essentially each client has some kind of live query, that responds to the client as db updated r made that change the query.

Thanks for the support.

2 Likes

Just stumbled on hobo. Their permission system looks perfect. http://hobocentral.net/manual/permissions . Thanks @classyPimp classypimp for proding me to look into this.

Does anybody have any experience or insight into hobo?

1 Like

Hobo looks like a heavy library, maybe Pundit (with Devise and Rolify) will do? It will allow checking permissions before giving anything to users or saving.

1 Like

Hey @catmando! how are react.rb and friends? Are there any news?

I’ve checked out that hobo permission stuff of yours. Is hobo coupled hard now? will it work if ApplicationController#acting_user would return current_user from devise for example?

Can’t wait to start some app with your ~> 1.0.0. gems.

1 Like

we are just cranking away on our rewrite. We are down to the last couple of pages but they are the biggest and will be a good stress test of reactive-record!

I just stole the hobo concepts, there is no actual tie in to hobo.

Other news is we have done some neat stuff with our skin on react-router called reactive-router. The big add was allowing modal dialogs to have their own history. So for example when you bring up a help page it pushes the current router state, and then you can navigate around the help system. When you close the help dialog it returns to the original state of the main app.

I can’t wait to document everything so others can use it. Hold on tight!

2 Likes

hey @classyPimp noticed your stack exchange question. Wasn’t sure how to answer, as thought you knew about reactive-record and were perhaps looking for a different answer.

I can elaborate more on the stack exchange answer if that would be useful to you and others.

Bottom line is your question is very good, and was the motivating reason for implementing reactive-record, which works exactly like embedded calls in erb (on the sever) but also will execute the same on the client.

2 Likes