Basics: How does Javascript communicate with Ruby on Rails?

Hello to All,

I’m trying to understand something that I seem to get lost on, which is how exactly does Javascript communicate with Rails? I understand how to make it work, I can even make a React front-end and Rails API work together. But I feel like I am missing something fundamental when it comes to how this communication actually occurs. If you know if a resource that explains this, I would be grateful.

Also, how does SQL communicate with Ruby or viceversa? A good resource that breaks it down would be great.

On the JS side, the JavaScript application (or simple inline script, no difference) makes an HTTP request to or from the server, and the server (Rails) responds to that request, just as it would any other request. The Rails framework uses the "accept" headers sent as part of that request to determine what sort of response to send (JSON, inline JS, HTML, text, whatever else) and that's the long and short of that.

As far as databases go, ActiveRecord (the library, not the pattern) has connection adapters for each of the major database engines, and there are others you can plug in, say for Oracle or MS SQL Server, if you're not using Postgres or MySQL or similar. That's all handled at the gem + configuration level. Once you have plugged in the adapter for your specific engine, the actual communication between Rails (well more accurately, ActiveRecord) and the database engine is quite clever.

Ruby code like:   class Foo < ApplicationRecord     has_many :bars   end   class Bar < ApplicationRecord     belongs_to :foo   end   f = Foo.new   f.bars << Bar.first   f.save

...is first converted into an Arel AST (abstract syntax tree) that is database-independent, and then the database adapter is responsible for converting that AST into actual SQL for the specific database engine and sending and retrieving and interpreting data from the server. (Despite SQL being an ISO standard, no two implementations are identical.)

The upshot of this is that you can have (as I do at work) your tests running against SQLite, and your production server running Oracle 12. And your tests work and predict the results from your production server.

You can learn more about this at the Rails Guides https://guides.rubyonrails.org

Walter

To add-on a react-specific answer, you can also embed your data as JSON in the view (note that this is not limited to just react):

<%# Embed data for react to use as initial state %>
<script>window.reactProps = <%== @react_props.to_json %>;</script>

When you need to initialize your app, you can do:

// Spread reactProps
render(<Videos {...window.reactProps} />, document.getElementById('Videos'));

Developers may or may not want to use this flow because binding data to the window is sometimes considered poor form (global namespace pollution). In this case, the server already has the data we need in memory during the initial request, so why use another request? (plus it’s good for SEO) Try looking at the page source for pinterest.com, it is a good example of a site that uses this flow to great effect.

Above all, remember that anything you pass to the browser is public!

Genuinely, I thank both of you for your insights. Without stretching this thread out any more, I want to figure out one last concept.

Form data, based on what was mentioned above, is it that the headers dictate what is passed on as data to be persisted? Like where in the chain of events does the SQL know that this is the data to be persisted? I think that’s where I get foggy, I understand the concepts of the http request/response but at what point is the data from a form understood or decided or interpreted by SQL? That’s all in the adapter?

Here's an incoming form submission:

Started POST "/coupons" for ::1 at 2019-08-07 20:52:23 -0400 Processing by CouponsController#create as HTML   Parameters: {"utf8"=>"✓", "authenticity_token"=>"xyBXVRs1DfQlkDxXlG/iTmxDMChOsPVfcgbNGX8dNOi5uPwfyBluAXcPFssxf5IQLPyoP2VWSPVjzZKeudwrHA==", "coupon"=>{"code"=>"TESTING", "discount_percent"=>"30", "discount_amount"=>"", "free_shipping"=>"0", "never_expires"=>"1", "uses_until_expiration"=>"", "user_email"=>"", "book_number"=>"", "category"=>"", "collection"=>"", "description"=>""}, "commit"=>"Create Coupon"}

It hits the controller, where the params are run through the strong parameters (whitelist) and assigned to the object (instance of the model):

    def coupon_params       params.require(:coupon).permit(:code, :discount_percent, :discount_amount, :expires_on,         :never_expires, :uses_until_expiration, :redemptions, :user_email, :book_number, :description, :free_shipping,         :collection, :category)     end

  def create     @coupon = Coupon.new(coupon_params)     respond_to do |format|       if @coupon.save         format.html { redirect_to @coupon, notice: "Coupon was successfully created." }         format.json { render :show, status: :created, location: @coupon }       else         format.html { render :new }         format.json { render json: @coupon.errors, status: :unprocessable_entity }       end     end   end

When you call save or update on the instance, the instance is persisted to the database. The actual implementation of this happens in the superclass of ActiveRecord::Base that all of your models descend from.

# File activerecord/lib/active_record/persistence.rb, line 274 def save(*args, &block)
  create_or_update(*args, &block)
rescue ActiveRecord::RecordInvalid   false
end

This in turn calls create_or_update

# File activerecord/lib/active_record/persistence.rb, line 702
def create_or_update(*args, &block)
  _raise_readonly_record_error if readonly?
  return false if destroyed?
  result = new_record? ? _create_record(&block) : _update_record(*args, &block)
  result != false
end

You can keep on digging all the way down through a bunch more layers of indirection, but in the end, you get to the money:

# File activerecord/lib/active_record/persistence.rb, line 168
def _insert_record(values) # :nodoc:
  primary_key_value = nil

  if primary_key && Hash === values
    primary_key_value = values[primary_key]

    if !primary_key_value && prefetch_primary_key?
      primary_key_value = next_sequence_value
      values[primary_key] = primary_key_value
    end
  end            if values.empty?
    im = arel_table.compile_insert(connection.empty_insert_statement_value)
    im.into arel_table
  else
    im = arel_table.compile_insert(_substitute_values(values))
  end            connection.insert(im, "#{self} Create", primary_key || false, primary_key_value)
end

That's where you can see the Arel layer coming into play. `connection` is an instance of the connection adapter you configured, depending on which database you're using. When you call connection.insert, the `im` is passed to it, and that's an instance of the Arel AST representing the insert statement, filled in with the values assigned to the model instance.

Back to my live example above, here's the SQL that it got turned into (for MySQL):

  Coupon Create (0.9ms) INSERT INTO `coupons` (`code`, `discount_percent`, `user_email`, `created_at`, `updated_at`, `description`, `collection`, `category`) VALUES ('TESTING', 30, '', '2019-08-08 00:52:23', '2019-08-08 00:52:23', '', '', '')   ↳ app/controllers/coupons_controller.rb:32

Hope this little tour helps you better understand the "magic" happening in ActiveRecord and Rails.

Walter

You just hit everything that I was trying to figure out and then some. This is what I’ve been needing to know(I can’t be certain of the that). Last question, how did you figure that out?; because that’s where this is kind of embarrassing, that I’ve held out to ask this for so long for fear. I’m sure intuition and experience comes with understanding what’s under the hood, but how does a junior dev figure stuff out like this? Please be merciful, don’t say look at the documentation, the documentation needs documentation. The documentation is not very clear for a junior dev because some stuff is abstracted and it’s like you have to be in the know about some other concept(that is omitted because reasons) that isn’t mentioned but understood.

Goes without saying but thanks man, this is solid knowledge I can use.

Well, in my particular case, I already understood the way that the controller accepts the incoming request and assigns the attributes. That understanding came from many many years of working in Rails.

The save part I understood intuitively, because years ago, I wrote an implementation of Active Record (the pattern: Active record pattern - Wikipedia) in PHP. So I knew approximately where to look, and googled 'active record base save'.

That took me to a disambiguation page on APIdock, where I found out that the method had moved around a few times. I knew that the concept of saving something to the database is known in Rails as "persistence" (a model instance has the method `persisted?` on it, for example), so I picked the version of #save that was in the persistence module, and followed the source. Each listing in APIdock has the snippet of source code that implements that method, and each method in turn contained more sub-methods that further processed the request to save. I followed the trail down one path only (I didn't bother showing all the details of what happens when you try to save something that is already persisted, which makes an UPDATE query rather than an INSERT query).

I've been following Rails as a technology since it was publicly announced, back in 2005 or so. I tried it briefly at that time, found it "too hard" for a struggling PHP hacker, and put it aside for a long time. I discovered a solid implementation of the ActiveRecord pattern in PHP, and used it successfully for several years in my commercial work. When the developer abandoned it, I took over the project and further refined it.

When PHP 5.3 made that library stop working, I wrote a clean-room replacement that followed the newer rules of PHP's classes. After using that for a couple more years, and looking at a several fairly mature Rails clones in PHP, I finally decided to stop trying to use copies and go to the original. My skills had leveled up, and Rails' tooling had improved, to the point that we were now compatible.

I made a "bet" with myself that I would just use Rails for the next project that came through my door. This was a request for a document repository with multiple levels of authorization (some people could author, some people could approve, some people could read-only). This was a great fit for Rails and its constellation of RubyGems. At the end of that project, I ran `rake stats` on the project. I found that I had written ~170 lines of code, ~250 lines of tests, and everything else came from a Gem or the framework itself. For those lines of code, I was paid $17K. That was enough of a reason to dive in and never look back.

Walter

I think perhaps one of the things you may be up against is that this learning curve is actually very out-dated now.

It's an old book, but this was one of the most popular books from 2007-2009 when a lot of us were learning the basics of Rails you are asking about here.

https://www.thriftbooks.com/w/agile-web-development-with-rails-a-pragmatic-guide_dave-thomas_david-heinemeier-hansson/313251/?mkwid=sHKLUDuV1|dc&pcrid=70112900832&pkw=&pmt=&plc=&pgrid=21329340912&ptaid=pla-293510751240&gclid=CjwKCAjwnMTqBRAzEiwAEF3ndnLhRREJENrkPly581-PMj-QFOLuzmC2IBUYrUtYbVHYpfSeM3hqCRoCalUQAvD_BwE#isbn=097669400X&idiq=2753678

It might reference some things in older version of Rails but the basics of what you are asking are thoroughly covered in the MVC pattern.

The Rails documentation can be difficult, but have you been through the Rails Guides? I would recommend working your way through the Rails Guides one by one and typing out the examples in your editor to learn what each piece of code does.

You're right, developers can write docs with too many assumptions and not enough explanation. But nothing substitutes good old fashioned trial & error.

Finally, I would strongly recommend you start with a solid foundation of Ruby, as I know many people who tried to learn Rails before Ruby and got lost.

Good luck

Jason

Hey Jason,

Thank you for the book recommendation, a good addition to what I have so far with books on Rails and Ruby. I have gone through many of the sections of the Rails guide and the edge version. The drill down that approach that Walter described is something I’m doing, but now I’m just trying to find the point of origin with each gem/module. So much magic, hard to tell when you hit paydirt. As for Ruby itself, I’ve explored it regularly for a while now, just feel like it sticks when I need to use the information. Thank you again for the recommendations, much obliged.