js_objects 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # Javascript Objects Gem
2
+ ## About
3
+ The not-so-creative name pretty much gives it all away. This gem allows you to use
4
+ a Javascript like object in Ruby. Some documentation as well as what I assume will
5
+ be frequently asked questions are below.
6
+ ## Dependencies
7
+ Ruby 1.9 or Ruby 2.0. Might work on 1.8.7, but I haven't checked.
8
+ ## How to install
9
+ Y'know, the way you normally install gems. Either: ```gem install js_objects``` or
10
+ add ```gem js_objects``` to your Gemfile and ```bundle install```.
11
+ ## JsObject
12
+ JsObjects are supposed to behave like Javascript objects, meaning you can define methods
13
+ and attributes on them through a ```#["attribute"]```, calling ```#attribute=``` (where 'attribute'
14
+ is mostly whatever you want it to be). They also implement a prototypal inheritance scheme, just
15
+ like real Javascript. That means they have a prototype (either another JsObject or an instance of
16
+ the Prototype class) that you can modify or set yourself. JsObject inherits from Hash, so it has
17
+ all of the Hash methods available to it. Not all of these methods are wrapped up in by this code
18
+ so it's possible you could experience some weird behavior or get your object into a weird state.
19
+ But that's all of programming though.
20
+ ### Basics
21
+ First, get a Javascript object like so:
22
+ ```ruby
23
+ js_obj = JsObject.new
24
+ ```
25
+ You can also give it an argument if you don't want to use its default prototype (more on prototypes later).
26
+ ```ruby
27
+ JsObject.new(prototype)
28
+ ```
29
+ Once you have your JS object, you can treat it pretty much like... well, an object in Javascript.
30
+ ```ruby
31
+ js_obj.something = "something"
32
+ js_obj.something # => "something"
33
+ js_obj["something"] # => "something"
34
+ js_obj[:something] # => "something"
35
+
36
+ js_obj[:false] = false
37
+ js_obj.false # => false
38
+
39
+ js_obj["nil"] = nil
40
+ js_obj[:nil] # => nil
41
+ ```
42
+ Call a method for an attribute that hasn't been defined yet and you'll get back ```nil``` (unless
43
+ you do some stuff with the prototype, but again, more on that later).
44
+ ```ruby
45
+ js_obj.never_defined # => nil
46
+ ```
47
+ ### Indifference
48
+ JsObjects are _extremely_ indifferent. Strings, symbols, numbers; they're all the same to your JsObject.
49
+ When you do use numbers as keys, remember that you can't later call ```.5``` on JsObjects or any other object for that
50
+ matter. Ruby doesn't like it. It also doesn't like ```:5``` so keep that in mind too. Or not if you like errors.
51
+ ```ruby
52
+ js_obj[5] = "hello"
53
+ js_obj[5] # => "hello"
54
+ js_obj["5"] # => "hello"
55
+ js_obj[:"5"] # => "hello"
56
+ js_obj.send(:"5") # => "hello"
57
+ ```
58
+ Be careful with anything that isn't a string, symbol, number or something that doesn't implement ```#to_s``` like
59
+ Objects do (ie, like ```"#<Object:0x007f8003825200>"```). If changing the object changes the return value of ```#to_s```
60
+ then your JsObject will treat it like a different key. A couple examples are below:
61
+ ```ruby
62
+ hash = { word: "hello", number: 5 }
63
+ hash.to_s # => "{:word=>\"hello\", :number=>5}"
64
+ js_obj[hash] = 5
65
+ js_obj[hash] # => 5
66
+
67
+ hash["new"] = "something"
68
+ hash.to_s # => "{:word=>\"hello\", :number=>5, \"new\"=>\"something\"}"
69
+ js_obj[hash] # => nil
70
+
71
+
72
+ array = [1, "two", 3]
73
+ array.to_s # => "[1, \"two\", 3]"
74
+ js_obj[array] = 8
75
+ js_obj[array] # => 8
76
+
77
+ array << 4
78
+ array.to_s # => "[1, \"two\", 3, 4]"
79
+ js_obj[array] # => nil
80
+ ```
81
+ Of course, anything that doesn't implement ```#to_s``` will raise an error if you try to use it as a key:
82
+ ```ruby
83
+ basic = BasicObject.new
84
+ js_obj[basic] # => NoMethodError: undefined method `to_s' for #<BasicObject:0x007f8001a1c6a0>
85
+ js_obj[basic] = 5 # => NoMethodError: undefined method `to_s' for #<BasicObject:0x007f8001a1c6a0>
86
+ js_obj[basic] # => NoMethodError: undefined method `to_s' for #<BasicObject:0x007f8001a1c6a0> still.
87
+ ```
88
+ ### Blocks, Procs, and Lambdas become methods.
89
+ If you set a Proc as a property, it becomes a method, but it's still accessible via ```#[]```.
90
+ ```ruby
91
+ some_proc = Proc.new{ |x| x * x }
92
+ js_obj[:proc] = some_proc
93
+ js_obj["proc"] # => some_proc
94
+ js_obj.proc(3) # => 9
95
+ ```
96
+ You can even set Procs that take Procs as methods:
97
+ ```ruby
98
+ some_lambda = ->(x, &block){ block.call x }
99
+ js_obj.lambda = some_lambda
100
+ js_obj[:lambda] # => some_lambda
101
+ js_obj.lambda(2) { |z| z + 1 } # => 3
102
+ js_obj.lambda(2, &some_proc) # => 4
103
+
104
+ js_obj.new_method(&some_lambda)
105
+ js_obj.lambda(2) { |z| z + 1 } # => 3
106
+
107
+ js_obj.even_newer_method { |x| x.to_sym }
108
+ js_obj.even_newer_method("hi") # => :hi
109
+
110
+ js_obj.num = 5
111
+ js_obj.other_num = 3
112
+ ```
113
+ Hate typing ```Proc.new``` or```->```? Well, you don't have to: You can simply call an unknown
114
+ method and pass it a block to define the method on the object. You can still access the Proc object
115
+ (say if you wanted another JS object to have the same method) via ```#[]```.
116
+
117
+ ```ruby
118
+ js_obj.new_method { |x| x * x }
119
+ js_obj.new_method(5) # => 25
120
+
121
+ js_obj.another_method do |word|
122
+ word.upcase
123
+ end
124
+ js_obj.another_method("word") # => "WORD"
125
+ ```
126
+ Also, every method set ona JS object is executed in the context of the object, so feel free to use ```self``` when defining methods
127
+ through blocks, procs, or lambdas. Just remember that you shouldn't use ```self``` in a block, proc, or lambda
128
+ that's passed as an argument to another method, unless you want that ```self``` to be the scope the proc was created
129
+ in. So this is good:
130
+ ```ruby
131
+ js_obj.context_is? { self }
132
+ js_obj.context_is? # => js_obj
133
+
134
+ js_obj.number_method do |x|
135
+ self.num = x + self.num
136
+ self.num + self.other_num
137
+ end
138
+
139
+ js_obj.number_method(2) # => 10
140
+ js_obj.num # => 7
141
+ ```
142
+ But this will be weird.
143
+ ```ruby
144
+ js_obj.proc = Proc.new{ |&block| block.call }
145
+ js_obj.proc{ "this context: #{self}"" } # => "this context: main"
146
+ ```
147
+ Sick of property or method on your object? Get rid of it with ```#delete```.
148
+ ```ruby
149
+ js_obj.tired = "of this property"
150
+ js_obj.delete(:tired)
151
+ js_obj.tired # => nil
152
+ ```
153
+ ## Prototype
154
+ Prototype objects share a lot in common with JsObject objects as JsObject inherits from Prototype,
155
+ which inherits from Hash. The main difference between JsObject and Prototype does not implement
156
+ prototypal inheritance which sounds like it doesn't make sense, but don't worry: It does.
157
+ Unknown methods are not deferred to a prototype, they simply return ```nil```, unless you use the
158
+ Hash method ```#default=``` or ```#default_proc``` to specify a different return value. You can make
159
+ it raise an error so you don't get immediate errors instead of ```NoMethodError``` for ```nil``` later.
160
+
161
+ Since you have access to the Prototype class, you can utilize them as a kind of OpenStruct that can call
162
+ methods or have as many different prototypal inheritance trees as you'd like.
163
+
164
+ ```PROTOTYPE``` is the default prototype for JsObjects. So instead of doing this:
165
+ ```ruby
166
+ js_obj.something # => nil
167
+ js_obj.prototype.something = "something"
168
+ js_obj.something # => "something"
169
+ ```
170
+ You can just do this:
171
+ ```ruby
172
+ js_obj.something # => nil
173
+ PROTOTYPE.something = "something"
174
+ js_obj.something # => "something"
175
+ ```
176
+ Whatever you want.
177
+ ## Prototypal Inheritance
178
+ [Protoypal Inheritance](http://en.wikipedia.org/wiki/Prototype-based_programming) can be read about that link. The short version is that objects don't inherit from a class, but instead inherit methods and attributes from a prototype.
179
+
180
+ For instance, let's say we have 3 objects: ```PROTOTYPE``` (the default instance of the Prototype class and the default prototype
181
+ for JsObjects), ```js_obj```, and ```js_obj2```.
182
+ ```ruby
183
+ js_obj2.prototype = js_obj
184
+
185
+ PROTOTYPE == js_obj.prototype # => true
186
+ js_obj2.prototype == js_obj # => true
187
+ js_obj2.prototype.prototype == PROTOTYPE # => true
188
+ ```
189
+ If a method or attribute is defined somewhere in an object's inheritance tree, then that value will bubble up.
190
+ ```ruby
191
+ PROTOTYPE.something = "something"
192
+
193
+ PROTOTYPE.something # => "something"
194
+ js_obj.something # => "something"
195
+ js_obj2.something # => "something"
196
+
197
+ js_obj.something_else = "something else"
198
+ PROTOTYPE.something_else # => nil
199
+ js_obj.something_else # => "something else"
200
+ js_obj2.something_else # => "something else"
201
+
202
+ js_obj2.another_thing = "anothing thing"
203
+
204
+ PROTOTYPE.another_thing # => nil
205
+ js_obj.another_thing # => nil
206
+ js_obj2.another_thing # => "another thing"
207
+ ```
208
+ This also applies to procs, blocks, and lambdas. If you have a proc set on an object's prototype,
209
+ then calling it on the child object will invoke in the context of the child object. So:
210
+ ```ruby
211
+ PROTOTYPE.set_something { self.something = "something" }
212
+ PROTOTYPE.something # => nil
213
+ js_obj.something # => nil
214
+
215
+ js_obj.set_something
216
+ js_obj.something # => "something"
217
+ PROTOTYPE.something # => nil
218
+ ```
219
+ If you don't want to invoke the proc in the context of the object and would rather get
220
+ at the proc object itself, then just use ```#[]```.
221
+ ```ruby
222
+ PROTOTYPE.proc { |x| x * 2 }
223
+
224
+ js_obj[:proc] # => (a Proc-ified version of the block used to def the proc method above)
225
+ ```
226
+ ## Frequently Asked Questions (aka FAQ)
227
+ ### Why would anyone want this?
228
+ * Why do you gotta hate?
229
+ * If they really liked Javascript objects but had to write Ruby.
230
+ * If they hate brackets and would rather just call methods on their objects
231
+ * If they hate classical inheritance and prefer prototypal inheritance.
232
+ * If they wanted really badass OpenStructs with prototypal inheritance.
233
+
234
+ ### Why not make this out of OpenStruct instead of Hash?
235
+ Then it wouldn't work in Ruby 1.9.3. OpenStructs only got the cool new ```#[]``` syntax in Ruby 2.0.
236
+ Also, OpenStructs are pretty bare methodwise while Hashes already have a ton of methods.
237
+ That is, I'd rather wrap those methods with super than write a bunch new methods for OpenStruct.
238
+ Granted, I haven't actually wrapped a lot of those methods yet, but I probably will at some point.
239
+ ### Why not make this out of HashWithIndifferentAccess?
240
+ It was pretty easy to make it pretty indifferent without pulling in _all_ of ActiveSupport, so I did
241
+ that instead. Plus, in Javascript you can't really use Arrays and Objects as keys, so partially taking
242
+ that away actually makes this more like Javascript. There are always tradeoffs and in this case not using
243
+ ActiveSupport costs us being able to use some objects as keys. That's fine with me.
244
+ ### Everyone hates Javascript and prefers Ruby, so why make Ruby more like Javascript?
245
+ Again, why do you gotta hate? In particular, on Javascript? I understand what you mean, but
246
+ this was a good learning exercise. Besides, prototypal inheritance and passing procs around can actually
247
+ create some pretty expressive and powerful code. Maybe this will help you practice Javascript and you'll
248
+ eventually learn to love it.
249
+
250
+
251
+
data/lib/js_object.rb ADDED
@@ -0,0 +1,63 @@
1
+ PROTOTYPE = Prototype.new
2
+
3
+ class JsObject < Prototype
4
+
5
+ def initialize(prototype=nil)
6
+ self.nil_keys = []
7
+ self.false_keys = []
8
+ self[:prototype] = prototype || PROTOTYPE
9
+ end
10
+
11
+ def [](key)
12
+ if nil_keys.include? key.to_s
13
+ nil
14
+ elsif false_keys.include? key.to_s
15
+ false
16
+ else
17
+ super || prototype[key]
18
+ end
19
+ end
20
+
21
+ def []=(key, value)
22
+ remove_from_falsey_lists key.to_s
23
+ add_to_falsey_lists key.to_s, value
24
+ super
25
+ end
26
+
27
+ def delete(property)
28
+ remove_from_falsey_lists property.to_s
29
+ super
30
+ end
31
+
32
+ private
33
+
34
+ attr_accessor :nil_keys, :false_keys
35
+
36
+ def remove_from_falsey_lists(key)
37
+ nil_keys.delete key
38
+ false_keys.delete key
39
+ end
40
+
41
+ def add_to_falsey_lists(key, value)
42
+ if value.nil?
43
+ nil_keys << key
44
+ elsif value == false
45
+ false_keys << key
46
+ end
47
+ end
48
+
49
+ def method_missing(method, *arguments, &block)
50
+ return super if equals_method?(method) || (block && prototype[method].nil?)
51
+ delegate_to_prototype method, arguments, block
52
+ end
53
+
54
+ def delegate_to_prototype(method_name, arguments, block)
55
+ prototypes_value = prototype[method_name]
56
+ if prototypes_value.kind_of? Proc
57
+ define_singleton_method :__proto_proc, &prototypes_value
58
+ __proto_proc *arguments, &block
59
+ else
60
+ prototypes_value
61
+ end
62
+ end
63
+ end
data/lib/js_objects.rb ADDED
@@ -0,0 +1,2 @@
1
+ require_relative './prototype'
2
+ require_relative './js_object'
data/lib/prototype.rb ADDED
@@ -0,0 +1,66 @@
1
+ class Prototype < Hash
2
+
3
+ def [](key)
4
+ super(key.to_s)
5
+ end
6
+
7
+ def []=(key, value)
8
+ define_methods(key.to_s, value)
9
+ super(key.to_s, value)
10
+ end
11
+
12
+ def delete(property)
13
+ singleton_class.send :remove_method, property
14
+ singleton_class.send :remove_method, getter_to_setter_name(property)
15
+ super
16
+ end
17
+
18
+ private
19
+
20
+ def method_missing(method, *arguments, &block)
21
+ if equals_method?(method)
22
+ self[setter_to_getter_name(method)] = arguments.first
23
+ elsif block
24
+ self[method] = block
25
+ else
26
+ self[method]
27
+ end
28
+ end
29
+
30
+ def setter_to_getter_name(setter_name)
31
+ setter_name.to_s.chop.to_sym
32
+ end
33
+
34
+ def getter_to_setter_name(getter_name)
35
+ "#{getter_name}=".to_sym
36
+ end
37
+
38
+ def equals_method?(method_name)
39
+ method_name.to_s[-1] == '=' && method_name.to_s[-2] != '='
40
+ end
41
+
42
+ def define_setter_method(method_name)
43
+ define_singleton_method method_name do |new_value|
44
+ self[setter_to_getter_name(method_name)] = new_value
45
+ end
46
+ end
47
+
48
+ def define_proc_getter_method(method_name, proc)
49
+ define_singleton_method method_name, &proc
50
+ end
51
+
52
+ def define_getter_method(method_name)
53
+ define_singleton_method method_name do
54
+ self[method_name]
55
+ end
56
+ end
57
+
58
+ def define_methods(method_name, value)
59
+ define_setter_method getter_to_setter_name(method_name) unless respond_to? method_name
60
+ if value.kind_of? Proc
61
+ define_proc_getter_method method_name, value
62
+ else
63
+ define_getter_method method_name
64
+ end
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: js_objects
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.6.0
6
+ platform: ruby
7
+ authors:
8
+ - Michael Crismali
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-17 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Provides objects with some JavaScript-like syntax and prototypal inheritance
15
+ in Ruby
16
+ email: michael.crismali@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README.md
22
+ - lib/js_object.rb
23
+ - lib/js_objects.rb
24
+ - lib/prototype.rb
25
+ homepage: https://github.com/michaelcrismali/js_objects_gem
26
+ licenses:
27
+ - MIT
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 1.8.23
47
+ signing_key:
48
+ specification_version: 3
49
+ summary: Provides objects with some JavaScript-like syntax and prototypal inheritance
50
+ in Ruby
51
+ test_files: []