js_objects 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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: []