Understanding why I need reopenClass()


#1

Hey all,

Haven’t touched Ember in a bit and now getting back into it.

Trying to wrap my head around reopenClass(). I created the following class in my demo app which will be my model:

App.RedditLink = Ember.Object.extend({

thumbnailUrl: function() {
	var thumbnail = this.get('thumbnail');
	return (thumbnail === 'default') ? null : thumbnail;
}.property('thumbnail')

});

Then I’m using reopenClass() to pull data from Reddit and render it:

App.RedditLink.reopenClass({

findAll: function(subreddit) {

	var links = [];
	
	$.getJSON("http://www.reddit.com/r/" + subreddit + "/.json?jsonp=?").then(function(response) {

		response.data.children.forEach(function (child) {

    			links.pushObject(App.RedditLink.create(child.data));

  		});
	
	});

  	return links;		
}

});

The question I have is “why do I need to use reopenClass() for this? Why can’t I just specify my findAll() method in my original class declaration?”

When I’ve tried to move findAll() into the App.RedditLink = Ember.Object.extend({…}) declaration, I get all types of errors.

The full mini-app code is here: http://www.sendspace.com/file/raj32a Demo link is here: http://reybango.com/demos/ember/reddit/index.html

Help?


#2

Use extend when you want every instance of the object have that property/function.

Use reopenClass when you want to add properties to the class definition.

Ruby equivalent(ish):

class Foo
  # reopenClass
  def self.bar
  end

  # extend
  def baz
  end
end

Foo.bar      # class
Foo.new.baz  # instance

Does this clear it up for you a bit?


#3

Thanks for the reply.

Sorry, no. According to the Ember docs, extend() allows me to define a new Ember class:

http://emberjs.com/guides/object-model/classes-and-instances/

So from that it would seem that specifying findAll() in the Ember.Object.Extend() declaration would make sense and I wouldn’t need reopenClass().

Also, I don’t know Ruby. It’s on my list of languages to learn (next month actually). :slight_smile:

Rey


#4

In OOP (Object-Oriented Programming) terms everything that goes in Ember.Object.extend({...}) are instance members. They can only be invoked on instances of the class. The scope (this) of methods will be the instance itself. Example:

var link = Ember.RedditLink.create(); //`link` is an instance of `Ember.RedditLink`
link.get('thumbnailUrl');

Everything in .reopenClass({...}) are class members or static members. They can only be accessed through the name of the class, not on instances. The scope (this) of methods will be the class object. Example:

var links = Ember.RedditLink.findAll(); //`Ember.RedditLink` is the class

If you were to put the findAll method inside .extend(), then it would become an instance method and it wouldn’t be accessible through Ember.RedditLink.findAll.


#5

It’s all about how Ember’s object model works.

Suppose you were doing this in plain old Javascript, you might do something like:

// The constructor function
App.RedditLink = function() {};

// The thumbnailUrl method
App.RedditLink.prototype.thumbnailUrl = function() {
  return this.thumbnail === 'default' ? null : this.thumbnail;
};

Now say you want a findAll method. You don’t want to need to have an instance of RedditLink yet, so you define the method on the constructor function itself:

App.RedditLink.findAll = function() {
   var links = [];
   $.getJSON(...).then(function(response) {
     response.data.children.forEach(function(child) {
       links.push(new App.RedditLink(child.data));
     });
   });
   return links;
};

The insight is that findAll isn’t an instance method, it’s a class method.

Ember’s object model just allows for some sugar (and some magic) for doing the above.

App.RedditLink = Ember.Object.extend({
  // The properties in here are on App.RedditLink.prototype
});

typeof App.RedditLink === 'function'; // It's just a fancy constructor function built for you

// You can even use new with it:
var instance = new App.RedditLink(data);
var instance = App.RedditLink.create(data);

So, you might ask, why not just attach findAll directly to App.RedditLink?

You can, of course, and it will work.

Until you want findAll to also work as a class method in subclasses of App.RedditLink.

CrazyLink = App.RedditLink.extend({/* crazy stuff */});

// If you did...
App.RedditLink.findAll = function() {};

// .. then findAll won't be defined on CrazyLink

CrazyLink.findAll === undefined.

// But if you reopen the class
App.RedditLink.reopenClass({findAll: function() {}});

// Then findAll will also be available on CrazyLink
typeof CrazyLink.findAll === 'function'

TL;DR - use reopenClass when you need to be able to inherit class methods


#6

Hello all

ok, just to be clear. Is

App.Person = Ember.Object.extend({
  surname: "empty surname",
  forename: "empty forenamename",
  fullname: function() {
    return this.get('forename') + " " + this.get('surname');
  }.property('surname', 'forename')
});

equals to

App.Person = Em.Object.extend({});

App.Person.reopen({
  surname: "empty surname",
  forename: "empty forenamename",
  fullname: function() {
    return this.get('forename') + " " + this.get('surname');
  }.property('surname', 'forename')
});

and equals to

App.Person = Em.Object.extend({
  surname: "empty surname",
});

App.Person.reopen({
  forename: "empty forenamename",
  fullname: function() {
    return this.get('forename') + " " + this.get('surname');
  }.property('surname', 'forename')
});

?

So, “extend” AND “reopen” are to define instance propertys, that are unique for each instance. “reopenClass” is to define stuff on the Class, and with “create” u can add aditional propertys to the instance.

so, the only diffrence between extend and create is, that the propertys added with extend, are on ALL instances. (except that extend returns a Class and not a instance.)

So, the myPerson object in the following code:

App.Person = Em.Object.extend({
  name: 'anonymous'
});

var myPerson = App.Person.create({});

Is equals to the myPerson object in that code:

App.Person = Em.Object.extend({});
var myPerson = App.Person.create({
  name: 'anonymous'
});

?

Or not? why?

Thanks for help.


#7

Thanks so much for the detailed answer. Really helpful.

Looking at this point you made:

“Until you want findAll to also work as a class method in subclasses of App.RedditLink.”

But if I defined findAll() as an instance method via extend(), then wouldn’t it be available to all instances of App.RedditLink? If so, then it sounds like the part of my code that would need to be adjusted would be how I referenced findAll() rather than where findAll() was defined.


#8

seilund, thanks for the reply.

This helps a lot and puts some things in context, especially in terms of the “why” I’m referencing code in a specific way. The method I’m using is something like this:

App.IndexRoute = Ember.Route.extend({ model: function() { return App.RedditLink.findAll(‘aww’); } });

Which I picked up from a blog post and now trying to get a better understanding of. It sounds from what you’ve written and from looking at the code that using reopenClass() facilitates direct class referencing of methods and properties regardless of whether their instantiated. Almost like convenience methods in an object literal.

What I’d like to get a better understanding is the practical use cases for this. Like, why wouldn’t I just defined findAll() in extend, make it available to all instances and simply tweak the way I call it? That type of understanding (the “why”) I think is invaluable. Could you help with that?


#9

How would you do this? It wouldn’t make sense. If you define the findAll method on every instance using .extend, then you need an instance before finding instances (chicken-and-egg problem).

I think you need to read up on object-oriented programming. The difference between classes and instances. And class members (.reopenClass) vs. instance members (.extend and .reopen). JavaScript is an object-oriented language without classes. But with Ember’s object model you do get “classes” when you use Ember.Object.extend that you can call .create on to instantiate them.

@krutius You are spot on!


#10

You are correct, you could just have findAll be an instance method.

It just seems weird to me to have the function that finds a bunch of RedditLink need to have an unrealized instance around:

// feels strange - why do I need an instance to find other instances?
var links = App.RedditLink.create().findAll(...);

That said, it would work just fine.


#11

I’m familiar with OOP. I’m just not familiar with Ember’s object model to completely understand how these things are working and the documentation doesn’t clearly articulate how all of this works.

And what’s interesting is that you’re saying that I couldn’t add findAll() while @satchmorun is actually saying I can. It’s an example of how there’s a clear disconnect in terms of Ember’s capabilities and the documentation that articulates its usage. It’s something I’ve mentioned to both Tom & Yehuda personally.


#12

Thanks bud. I agree it seems weird but this is why I’m asking these questions. I’m trying to narrow does the use cases so I can understand Ember’s object model.


#13

Since you are familiar with OOP, what language(s) do you use? Someone can explain using examples/analogies from that language.


#14

I learned OOP back in the 90s using a product called PowerBuilder and it’s language Powerscript (http://en.wikipedia.org/wiki/PowerBuilder#Summary_of_PowerBuilder_features). I’d be surprised if anyone nowadays has used it to much extent but the OOP principles are still applicable.


#15

In the future, these sorts of questions are best asked on Stack Overflow. Thanks!


#16

Huh? Which questions are you referring to?


#17

Sorry, I may have misunderstood the focus of your question. It seemed to me that “Why do I need reopenClass?” would make for a good StackOverflow question. That said, I left this thread open because it also does get into some theoretical discussion which is appropriate for this forum. I went through a bunch of SO appropriate threads today and probably should have given this a more detailed response.


#18

Hey, nice discussion. I have another question, how can you access to a property defined by reopenClass inside a class method. If I access by App.Person.NAME_OF_PROPERTY is ok but Im looking for something like “self” in PHP. thanks in advance !


#19

http://jsfiddle.net/NQKvy/774/