Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Ruby 1.8.7 compatibility #43

Merged
merged 17 commits into from
Jan 16, 2014
Merged

Add Ruby 1.8.7 compatibility #43

merged 17 commits into from
Jan 16, 2014

Conversation

sferik
Copy link
Collaborator

@sferik sferik commented Dec 31, 2013

I’d like to use Naught to replace Twitter::NullObject in the twitter gem, however, the current version of the gem still supports Ruby 1.8.7 and I don’t want to bump the major version just to add Naught.

I also took this opportunity to add other Ruby versions and implementations to the Travis CI configuration (JRuby, Rubinius, etc.).

Do you have any objection to supporting Ruby 1.8.7, at least through June 2014?

@avdi
Copy link
Owner

avdi commented Dec 31, 2013

Cool! I'm actually kinda surprised it was that easy... does instance_exec work in 1.8.7?

@sferik
Copy link
Collaborator Author

sferik commented Dec 31, 2013

does instance_exec work in 1.8.7?

I can’t find any instances of instance_exec in the codebase.

@sferik
Copy link
Collaborator Author

sferik commented Dec 31, 2013

I'm actually kinda surprised it was that easy

It’s actually not as easy as I initially thought. Working on making the specs pass on various Ruby implementations now.

@sferik
Copy link
Collaborator Author

sferik commented Dec 31, 2013

Hmmm, it looks like this will require removing BasicObject as the parent class for null objects. That seems like a pretty major change, so I wanted to check with you before I proceed with this patch.

@avdi
Copy link
Owner

avdi commented Jan 1, 2014

Thoughts on adding a dependency on the backports gem instead? Or what about
defining our own ::Naught::BasicObject (like the one in ActiveSupport)
which is an alias for BasicObject if it's available?

On Tue, Dec 31, 2013 at 4:23 PM, Erik Michaels-Ober <
notifications@github.com> wrote:

Hmmm, it looks like this will require replacing BasicObject with Objectas the parent class for null objects. That seems like a pretty major
change, so I wanted to check with you before I proceed with this patch.


Reply to this email directly or view it on GitHubhttps://github.com//pull/43#issuecomment-31411823
.

Avdi Grimm
http://avdi.org

I only check email twice a day. to reach me sooner, go to
http://awayfind.com/avdi

@sferik
Copy link
Collaborator Author

sferik commented Jan 1, 2014

Backports is cool project but it can make debugging more complicated. I prefer the idea of ::Naught::BasicObject. I’ll take that approach and continue working on this.

@sferik
Copy link
Collaborator Author

sferik commented Jan 2, 2014

Okay, I’ve got the specs passing on MRI 1.9.2 through head, rbx, and jruby-head but there are still 5 spec failures on MRI 1.8.7. There seems to be something very strange happening on 1.8.7 when calling any of the conversion methods (e.g. Actual) with a block. Even though the method is passed a block, for some reason, the block parameter is nil and block_given? is false. This has me thoroughly confused. Would you mind looking into it? I’m obviously missing something.

@avdi
Copy link
Owner

avdi commented Jan 3, 2014

Working on it!

@avdi
Copy link
Owner

avdi commented Jan 3, 2014

While I'm waiting for 1.8.7 to install... a little buzzing at the back of my head is saying something about parsing of blocks. First thing I want to try will involve switching between curly braces and do/end.

@avdi
Copy link
Owner

avdi commented Jan 3, 2014

This is super weird. I can't reproduce it with methods I define in IRB, but it's definitely happening under test.

@avdi
Copy link
Owner

avdi commented Jan 3, 2014

OK, so this is pretty nuts. If I load up naught in IRB 1.8.7 and generate a null class, then include the conversions module, I can reproduce this behavior. But if I copy-and-paste the source of Actual into the IRB session and then try it, it works.

@avdi
Copy link
Owner

avdi commented Jan 3, 2014

All I can think is that this is a bizarre bug in 1.8.7 that only occurs when a method is included from a module. I don't know how to work around it other than to put a conditional around the tests for the conversion functions. But that doesn't help anyone to actually use the functions in 1.8.7.

This is painful, but you might move the definitions of the conversion functions into a proc, and provide a way to instance_eval that proc in the current context rather than including the generated Conversions module. That might work in 1.8.

@sferik
Copy link
Collaborator Author

sferik commented Jan 3, 2014

I agree with your conclusion that this is being caused by some bizarre bug in Ruby 1.8.7. Frankly, I’m surprised that I’ve never encountered it before and I have no recollection of reading about it around the time 1.9 was released.

For kicks, I tried requiring backports to see if that somehow magically fixed things. It did not.

This is painful, but you might move the definitions of the conversion functions into a proc, and provide a way to instance_eval that proc in the current context rather than including the generated Conversions module.

I had to read that sentence five times before I actually understood what you’re proposing and now my head hurts. I will give that a try, just as soon as my head stops hurting.

Thanks for taking the time to look into this. I was completely stumped.

@avdi
Copy link
Owner

avdi commented Jan 6, 2014

I had one other thought about this, which is to have optional lower-case versions of the conversion functions. I haven't tested to see if this gets around the bug tho.

@sferik
Copy link
Collaborator Author

sferik commented Jan 6, 2014

I just tested with lower-case method names and I get the same errors.

@avdi
Copy link
Owner

avdi commented Jan 7, 2014

This is such a weird-ass bug.
On Jan 6, 2014 5:55 PM, "Erik Michaels-Ober" notifications@github.com
wrote:

I just tested with lower-case method names and I get the same errors.


Reply to this email directly or view it on GitHubhttps://github.com//pull/43#issuecomment-31696497
.

@sferik
Copy link
Collaborator Author

sferik commented Jan 8, 2014

💥 I think I figured it out. This is the issue: You can’t pass a block that takes block arguments to define_method in Ruby 1.8, so it’s doing nothing here.

@avdi
Copy link
Owner

avdi commented Jan 8, 2014

Argh, I really should have remembered that, it was a big deal to me when
1.9.3 changed that rule.

On Tue, Jan 7, 2014 at 11:40 PM, Erik Michaels-Ober <
notifications@github.com> wrote:

[image: 💥] I think I figured it out. Thishttp://coderrr.wordpress.com/2008/10/29/using-define_method-with-blocks-in-ruby-18/is the issue: You can’t pass a block that takes block arguments to
define_method in Ruby 1.8, so it’s doing nothing herehttps://github.com/avdi/naught/blob/3ad13c6196bf0c546d6ed7baf1fa204c417edd4d/lib/naught/null_class_builder/conversions_module.rb#L12
.


Reply to this email directly or view it on GitHubhttps://github.com//pull/43#issuecomment-31805470
.

Avdi Grimm
http://avdi.org

I only check email twice a day. to reach me sooner, go to
http://awayfind.com/avdi

@sferik
Copy link
Collaborator Author

sferik commented Jan 8, 2014

This isn’t pretty but it solves the issue with blocks on Ruby 1.8. Unfortunately, it breaks tracing. Two steps forward, one step back. 🐸

@avdi
Copy link
Owner

avdi commented Jan 8, 2014

Can you fix tracing by supplying the file and line args to eval?

On Wed, Jan 8, 2014 at 2:52 PM, Erik Michaels-Ober <notifications@github.com

wrote:

Thishttps://github.com/sferik/naught/commit/be09edbaa8938f12ecfd46616fd25f8cb36c44e7isn’t pretty but it solves the issue with blocks on Ruby 1.8.
Unfortunately, it breaks tracing. Two steps forward one step back. [image:
🐸]


Reply to this email directly or view it on GitHubhttps://github.com//pull/43#issuecomment-31871192
.

Avdi Grimm
http://avdi.org

I only check email twice a day. to reach me sooner, go to
http://awayfind.com/avdi

@sferik
Copy link
Collaborator Author

sferik commented Jan 8, 2014

Can you fix tracing by supplying the file and line args to eval?

The first thing I tried (which requires the method_source gem on Ruby 1.8) was:

meth = mod.method(method_name)
define_method("__real__#{method_name}", &meth)
file, line = meth.source_location
class_eval <<-EOM, file, line
  def #{method_name}(*args, &block)
    __real__#{method_name}(block, *args)
  end
EOM

Unfortunately, this did not fix it. What am I doing wrong?

@avdi
Copy link
Owner

avdi commented Jan 8, 2014

I was just thinking FILE and LINE, but I'm probably missing
something. I don't want to hide the eval'd code from traces.
On Jan 8, 2014 3:01 PM, "Erik Michaels-Ober" notifications@github.com
wrote:

Can you fix tracing by supplying the file and line args to eval?

The first thing I tried was (which requires the method_source gem on Ruby
1.8):

meth = mod.method(method_name)define_method("real#{method_name}", &meth)file, line = meth.source_locationclass_eval <<-EOM, file, line + 1 def #{method_name}(*args, &block) real#{method_name}(block, *args) endEOM

Unfortunately, this did not fix it but maybe I’m doing it wrong?


Reply to this email directly or view it on GitHubhttps://github.com//pull/43#issuecomment-31872170
.

@sferik
Copy link
Collaborator Author

sferik commented Jan 8, 2014

That will set the source file and line to lib/naught/null_class_builder/conversions_module.rb:16 (i.e., where the code was evaluated). Not sure how to get access to the line/file of the caller.

@sferik
Copy link
Collaborator Author

sferik commented Jan 9, 2014

There may be a more elegant way to do this but I was pretty happy just to get it working across all major Ruby versions/implementations.

Very curious to get your feedback.

@@ -8,6 +8,4 @@ def self.build(&customization_block)
builder.customize(&customization_block)
builder.generate_class
end
module NullObjectTag
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: What happens when someone uses config.base = Object? Can we still identify a null object as a null object?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried doing the same thing with alias_method/undef_method but it
caused errors on JRuby.
This is required to support Ruby 1.8 and earlier, which doesn't allow
define_method to take a block that takes a block argument.

See http://coderrr.wordpress.com/2008/10/29/using-define_method-with-blocks-in-ruby-18/
ConversionsModule was previously a class that needed to be initialized
with two arguments: null_class and null_equivs.

Instead, these arguments are passed via Module#included by setting a
constant on the null class, including the module in the null class, and
then removing the constant and undefining the included methods.
@sferik
Copy link
Collaborator Author

sferik commented Jan 10, 2014

I’ve restored NullObjectTag and tests are still passing. Any other questions/objections?

@avdi
Copy link
Owner

avdi commented Jan 10, 2014

Nope, I think I'm good with it!
On Jan 10, 2014 6:09 PM, "Erik Michaels-Ober" notifications@github.com
wrote:

I’ve restored NullObjectTag and tests are still passing. Any other
questions/objections?


Reply to this email directly or view it on GitHubhttps://github.com//pull/43#issuecomment-32076248
.

@sferik
Copy link
Collaborator Author

sferik commented Jan 10, 2014

🐣 🐥 🐤 🐔 🥚

I’ve already committed to a branch in the twitter gem that replaces my custom Twitter::NullObject object with Naught. I can merge that as soon as you merge this and push a new gem. I think this will have a significant impact on the total number of users of Naught.

According to (RubyGems.org)[http://rubygems.org/gems/naught], the current version of Naught has about 1,200 downloads since November 8 or about 600 per month. The latest version of the twitter gem has about 2,400 downloads since January 6 or about 600 per day, so this should increase the number of (indirect) Naught users by a factor of 30 or so. :shipit:

@sferik
Copy link
Collaborator Author

sferik commented Jan 15, 2014

It’s been a few day since you said this looked good but I can’t help but noticing that it’s still not merged.

If you’re willing to make me a collaborator on the project, I’d be happy to merge it myself and help maintain the project going forward.

@avdi
Copy link
Owner

avdi commented Jan 16, 2014

I'm merging this. Thanks for all your hard work! And yeah, I'd be happy to make you a collaborator.

I kind of want to think about where we go from here... one of the reasons I built this library was as a way to demonstrate a number of metaprogrammer techniques, and the 1.8.7 compat necessarily means switching to... sub-optimal techniques. I'd like to get your thoughts on how to maybe restore some of the more "ideal" metaprogramming demonstrations.

I guess the simplest thing would be to just wait until you officially sunset 1.8 support in the Twitter gem, and then do a rewrite.

Another thought I had was to isolate some of the worst kludges into their own files and maintain two versions of those files one for 1.8 and one for >1.8. Then conditionally load the correct files based on RUBY_VERSION.

Thoughts?

avdi pushed a commit that referenced this pull request Jan 16, 2014
Add Ruby 1.8.7 compatibility
@avdi avdi merged commit 1ff4858 into avdi:master Jan 16, 2014
@avdi
Copy link
Owner

avdi commented Jan 16, 2014

I'm also thinking of stamping this as the 1.0 release. Being used in a major gem like twitter seems like a good time to lock down the API.

@sferik
Copy link
Collaborator Author

sferik commented Jan 16, 2014

The only issue with releasing this as version 1.0.0 is that locks you into supporting Ruby 1.8.7 until you release version 2.0.0. If you’re planning to remove Ruby 1.8.7 support in the relatively near future, (say, 6.months.from_now), I’d recommend releasing this as 0.1.0 or possibly 0.9.0, to signify that 1.0.0 is imminent.

On the other hand, the whole point of this patch is so that I can depend on this gem in production, and according to Semantic Versioning:

If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you're worrying a lot about backwards compatibility, you should probably already be 1.0.0.

Maybe releasing version 2.0.0 in 6 months isn’t so bad after all? Firefox and Chrome have taught us that there’s no shame in being at version 26.0 or 32.0.1700.77.✡ It’s not like there’s any shortage of positive integers. In fact, quite the opposite.

✡ Chrome’s version number has been increasing by (on average) 0.01630988786 per day. If they continue at the current pace for the next 50 years, when I am 80 years old, Chrome’s version number will be 297.852803088.

Update: Replaced asterisk with Star of David, since GitHub-Flavored Markdown converts asterisks to bullet points. See also.

@sferik
Copy link
Collaborator Author

sferik commented Jan 22, 2014

@avdi Any additional thoughts on shipping a new version and what number it might have?

@avdi
Copy link
Owner

avdi commented Jan 25, 2014

As also tweeted: let's go with 1.0.0. I've added you as a gem owner since I keep getting sick and having snowstorms and assorted other excuses for not getting around to it :-)

@sferik
Copy link
Collaborator Author

sferik commented Jan 26, 2014

Okay. I’ve reviewed all open issues and pull requests, updated the CHANGELOG, and bumped the version to 1.0.0. Here goes nuthin’… 🚢

@avdi
Copy link
Owner

avdi commented Jan 26, 2014

Erik, Thanks again for all your work and patience on this. I never imagined this library would be 1.8-compatible!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants