Deprecated: Creation of dynamic property wpdb::$categories is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$post2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$link2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Return type of WP_Theme::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 554

Deprecated: Return type of WP_Theme::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 595

Deprecated: Return type of WP_Theme::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 535

Deprecated: Return type of WP_Theme::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 544

Deprecated: Return type of WP_REST_Request::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 960

Deprecated: Return type of WP_REST_Request::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 980

Deprecated: Return type of WP_REST_Request::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 992

Deprecated: Return type of WP_REST_Request::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 1003

Deprecated: Return type of WP_Block_List::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 151

Deprecated: Return type of WP_Block_List::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 175

Deprecated: Return type of WP_Block_List::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 164

Deprecated: Return type of WP_Block_List::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 186

Deprecated: Return type of WP_Block_List::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 138

Deprecated: Return type of WP_Block_List::offsetExists($index) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 75

Deprecated: Return type of WP_Block_List::offsetGet($index) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 89

Deprecated: Return type of WP_Block_List::offsetSet($index, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 110

Deprecated: Return type of WP_Block_List::offsetUnset($index) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 127

Deprecated: Return type of WP_Block_List::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 199

Deprecated: Creation of dynamic property Automattic\Jetpack\Connection\Manager::$error_handler is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-connection/src/class-manager.php on line 91

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Instagram_Gallery::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-instagram-gallery.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Mailchimp::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php on line 17

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 21

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 22

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Gutenberg_Available_Extensions::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php on line 20

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 24

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Subscribers::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php on line 15

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property WPCOM_Widget_Goodreads::$shelves is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/modules/widgets/goodreads.php on line 33

Deprecated: DateTime::__construct(): Passing null to parameter #1 ($datetime) of type string is deprecated in /var/www/twodee/blog/wp-includes/script-loader.php on line 348

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_classic_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 347

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 349

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block_side is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 350

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$panels_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 351

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$used_block_buttons is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 354

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198
» CS 330: Lecture 37 – Object-relational Mapping Via Metaprogramming

teaching machines

CS 330: Lecture 37 – Object-relational Mapping Via Metaprogramming

May 7, 2018 by . Filed under
Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383
cs330, lectures, spring 2018.

Dear students,

Metaprogramming is our theme for this last week of the semester. What is metaprogramming? It’s when code generates code. It’s when user input is used to generate new classes, methods, and so on. You’ll probably all felt at certain points that the code you are writing probably didn’t need to be written by a human. Perhaps its repetitive. Perhaps its a direct translation of some other structure found elsewhere in your technology stack.

This class has largely been about how we as developers model ideas about computation using programming languages. We’ve mostly ignored the end user and focused on how we can make programming more enjoyable, more efficient, and more intellectually stimulating. Metaprogramming certainly fulfills all these goals.

We will examine metaprogramming in both Ruby and Java. Today, we will concentrate on Ruby. In particular, we will construct a lightweight system for object-relational mapping. In an ORM, you’ve got a representation of some data type in one system, and you’d like to use that same type in another system. Certainly you could specify the type twice, but that’s prone to error. The two specifications will inevitably become unsynchronized.

ORM is often used with databases and the languages that access them. You might write a schema to describe a table in SQL, and you want the code to automatically understand the schema without you having to do any extra work. Here are some things that our ORM should be able to handle:

A full ORM will support more features, like relations to other records. Our database will not be a database but a simpler JSON file:

{
  "headline": "Americans Scroll 2.5 Miles Per Day",
  "nwords": 849,
  "author": "Petey F.",
  "copy": "...",
  "tags": ["internet", "millenials"]
}

Out of the box, we can get reasonably close to turning this text into a Ruby object simply by parsing it using the JSON API:

json = File.read(ARGV[0])
article = JSON.parse(json, symbolize_names: true)
puts article[:author]

But this isn’t really an object. It’s a dictionary/hash/key-value pair manager. It’d be sweet if we could make Ruby more like Javascript, where there’s an equivalence between dictionary lookup and field access:

var foo = {};
foo['name'] = 'Scout';
console.log(foo.name);

We’d like to make our Ruby ORM build structures that feel more like an object. Instead of saying this:

article[:author] = value

we’d like to be able to say this:

article.author = value

Let’s do it. We’ll need a new class to support this:

class Autobject
  # ...
end

What happens currently when we try to read a property of Autobject?

article = Autobject.new
puts article.author

When we run this code, we see that method author cannot be found. Is there any way that we can write a class that has methods for every field/property that our clients may want to assign? No way. We can’t see that far into the future.

For this to happen, we need some metaprogramming. We need to generate these methods on the fly based on our schema. But first, let’s write a minimal constructor for testing out some ideas:

def initialize value = {}
  @data = value
end

If the client provides no value, we’ll just wrap around an empty dictionary, a blank data store.

Now, how do we handle all these infinite methods that are impossible to write? Easy. A catch-all method that Ruby will call on our objects when they don’t support a method. It’s called method_missing:

def method_missing symbol, *args
  ...
end

What might be true when this method is called?

  1. It might be a read operation for a key already in the dictionary.
  2. It might be a read operation for a key not already in the dictionary.
  3. It might be a write operation.

Let’s handle the first case:

if @data.has_key? symbol
  @data[symbol]
end

If the key doesn’t exist, let’s raise an exception:

else
  raise "No such property: #{symbol.to_s}"
end

The last case is a bit more involved. We have to check if the method name suggests an assignment, but this needs to be done on the string version of the symbol:

elsif symbol.to_s =~ /^(\w+)=$/ && args.length == 1
  @data[$1.to_sym] = args[0]
end

Okay, let’s test this out:

f = Autobject.new first: 'Roy', last: 'Biv'
f.middle = 'G'
puts "#{f.first} #{f.middle} #{f.last}"

Now let’s get this to work with JSON data. Let’s add a static method for loading an Autobject from some other source:

def self.load src
  ...
end

If we have a URI or File, we’ll open it and slurp up the JSON contents. Otherwise, we’ll assume we have a JSON string. Once we know we have JSON, we can parse it:

if src.is_a?(URI) || src.is_a?(File)
  json = open(src) do |io|
    io.read
  end
else
  json = src
end

Autobject.new(JSON.parse(json, symbolize_names: true))

Now, let’s try reading some JSON:

g = Autobject.load '{"first": "Roy", "last": "Biv"}'
puts g.inspect

And some weather data:

weather = Autobject.load(URI("http://api.openweathermap.org/data/2.5/weather?q=Eau%20Claire%2CWisconsin&APPID=#{KEY}"))
puts weather.inspect

Let’s try to pull out the current temperature:

puts weather.main.temp

This fails. Why? weather.main gives back a Hash. Calling temp on a Hash is bound to fail. We want main to also be an Autobject so we can access fields inside of it with the . operator. Let’s write a little recursive helper that goes and turns all the parts of our JSON structure into Autobjects:

def self.objectify data
  if data.is_a? Array
    data.map do |element|
      Autobject.objectify(element)
    end
  elsif data.is_a? Hash
    object = data.transform_values do |value|
      Autobject.objectify(value)
    end
    Autobject.new object
  else
    data
  end
end

And then in load, we can apply this method to the data that we parse:

data = JSON.parse(json, symbolize_names: true)
Autobject.objectify(data)

Our temperature reading should work now! How about the time of the sunset?

puts Time.at(weather.sys.sunset)

What’s really nice about metaprogramming is that the code is essentially writing itself. This Autobject should be able to handle any key and any dictionary-ish object. Let’s try getting the high and low temperatures:

weather = Autobject.load(URI("http://api.openweathermap.org/data/2.5/forecast/daily?units=imperial&q=Eau%20Claire%2CWisconsin&APPID=#{KEY}"))
puts weather.list[0].temp.min
puts weather.list[0].temp.max

Let’s try dumping the object en masse to the console:

def inspect
  JSON.pretty_generate(@data)
end

This seems to only work for the top-level dictionary. The pretty_generate method recursively calls to_json on all the values nested inside our object, so we also need to provide that method:

def to_json generator
  JSON.pretty_generate(@data, generator)
end  

Of course, now that we have the ability to print an object, we should provide a method for dumping it out to disk:

def save path
  open(path, 'w') do |file|
    file.puts inspect
  end
end

Now we have automatic getters, setters, serialization, and deserialization for any sort of object we can dream up. Thanks, metaprogramming.

See you next time!

Sincerely,

P.S. It’s time for a haiku!

Taught my car to drive
Now when we go on roadtrips
Two can ride shotgun

P.P.S. Here’s the code we wrote together!

article.json

{
  "headline": "Americans Scroll 2.5 Miles Per Day",
  "nwords": 849,
  "author": "Petey F.",
  "copy": "...",
  "tags": ["internet", "millenials"]
}

meta.rb

#!/usr/bin/env ruby

require 'json'
require 'open-uri'

json = File.read(ARGV[0])
article = JSON.parse(json)

# puts article
# puts "article.class: #{article.class}"

class Auto
  def initialize data = {}
    @data = data
  end

  def method_missing symbol, *args
    if @data.has_key?(symbol)
      @data[symbol]
    elsif symbol[-1] == '='
      @data[symbol.to_s.chop.to_sym] = args[0]
    else
      raise "No such property #{symbol}... I have failed you. No, it's me."
    end
  end

  def self.load(src)
    if src.is_a?(File) || src.is_a?(URI)
      json = open(src) do |io|
        io.read
      end
    else
      json = src
    end

    data = JSON.parse(json, symbolize_names: true)
    Auto.objectify(data)
  end

  def inspect
    JSON.pretty_generate(@data)
  end

  def self.objectify data
    if data.is_a? Array
      data.map do |element|
        Auto.objectify(element)
      end
    elsif data.is_a? Hash
      object = data.transform_values do |value|
        Auto.objectify(value)
      end
      Auto.new object
    else
      data
    end
  end
end

article = Auto.load(File.new(ARGV[0]))
puts "article.author: #{article.author}"
puts "article.nwords: #{article.nwords}"
puts "article.headline: #{article.headline}"

# article = Auto.new
# article.author = "H.G. Wells"
# p article
# puts article.author

# puts article['headline']
# puts article.headline

KEY = 'INSERT-YOUR-KEY-HERE'
weather = Auto.load(URI("http://api.openweathermap.org/data/2.5/weather?q=Eau%20Claire%2CWisconsin&APPID=#{KEY}"))
p Time.at(weather.sys.sunset)

# puts weather.main.temp
# p weather