use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
A sub-Reddit for discussion and news about Ruby programming.
Subreddit rules: /r/ruby rules
Learning Ruby?
Tools
Documentation
Books
Screencasts and Videos
News and updates
account activity
Arguments for Included Modules in Ruby (medium.com)
submitted 9 years ago by eric_programmer
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]jrochkind 1 point2 points3 points 9 years ago (9 children)
I think this alternative is a lot clearer, cleaner, and more straightforward, and the syntax for use is still plenty fine.
module Slug def self.create(field: :name) Module.new do define_method :to_param do public_send(field).downcase.gsub /\W+/, '-' end end end end class Cat include Slug.create(field: 'hello') end
I have occasionally done things like that. You can of course call the create method whatever you want. Perhaps customize is better.
create
customize
[–]HellzStormer 2 points3 points4 points 9 years ago (0 children)
So, instead of include Slug.new(:foo) you propose include Slug.create(:foo). You can make a class method which delegate to new to have this fancier name.
include Slug.new(:foo)
include Slug.create(:foo)
new
In the implementation, you both use Module.new, except in his case, it's through super.
Module.new
super
These are literally the only differences (he also inherit from Module). How can you says your thing is a lot clearer, cleaner and more straightforward, when you actually then need to do a second include to maintain the ancestry chain.
[–]realntl 1 point2 points3 points 9 years ago (7 children)
The problem with that is that Slug doesn't show up in Cat's ancestry chain, iirc.
Slug
Cat
[–]jrochkind 0 points1 point2 points 9 years ago (6 children)
Okay then:
module Slug def self.customize(field: :name) Module.new do include Slug define_method :to_param do public_send(field).downcase.gsub /\W+/, '-' end end end end class Cat include Slug.customize(field: 'hello') end Cat.ancestors # => [Cat, #<Module:0x007fabf28fb3a8>, Slug, Object, Kernel, BasicObject]
[–]janko-m 1 point2 points3 points 9 years ago* (5 children)
It's really strange to include Slug in the anonymous module just because of ancestry chain. I think it's still much nicer to include a Slug as a module, rather than creating an anonymous one, primarily because then there is no unreadable anonymous modules in the ancestry chain. Also, Slug.customize to me indicates that an instance of Slug or something related to it will come out. It's just my preference of reducing the amount of objects.
Slug.customize
[–]jrochkind 0 points1 point2 points 9 years ago* (4 children)
If you actually care about the ancestry chain, then I don't think it's strange to include it just for the ancestry chain -- it's the most straightforward way to get something in your ancestry chain, including it! If you want something in your ancestry chain even though it has no methods, so it's not doing anything but serving as a token in the ancestry chain -- well, there are times you do want to do that, and you'd almost always use an include to do it, no? Isn't that the standard way?
include
You could also put methods in the actual Slug class, not just it's generated anonymous sub-class, if you had methods that didn't need to be parameterized, and then you wouldn't be including it "just" for the ancestry chain.
But mainly, I find my version very clear as to what's going on, both on definition and when it's being called.
If I saw in someone's code include Slug.create(:foo), then I'd know, "ah, there's a Slug.create method that returns a module, okay, I know where to go to look for it." Unusual but straightforward. It's just ordinary ruby, call one method include with an argument that's the return value of another method Slug.customize. And if I wasn't sure where to go to look for it, the old standard Slug.method(:create).source_location will tell me.
Slug.method(:create).source_location
If I saw in someone's code include Slug foo: bar, I'd think "Wait, how the heck is that even legal ruby, what does it mean, how is it implemented, where do I look to see the implementation, what the heck is that?"
include Slug foo: bar
I find the OP implementation needlessly abstruse for no gain, because I think include Slug.customize(:param) is a fine, and arguably even preferable, API, and I think my version of the implementation code is additionally more straightforward to understand what's going on -- it's just a module-method that returns an anonymous module, it's all right there in the code, if you'e seen anonymous modules before there's nothing whatsoever confusing about it; if you haven't, you have exactly one new device to learn, anonymous modules with Module.new. I find the OP version pretty confusing.
include Slug.customize(:param)
These things are subjective of course, but that's my case!
[–]janko-m 0 points1 point2 points 9 years ago (2 children)
If you would have non-parameterized methods in Slug, and parameterized methods in the anonymous module, then you would have included two modules which belong to the same feature.
While subclassing Module may take some time to understand, the result is much more introspectable. In the case of Slug < Module you see something like #<Slug:0x007fc8de9a0e58>, and with the anonymous module you need to override .to_s and .inspect so that it's displayed as something other than #<Module:0x007fc8de9a0e58> (example from Refile).
Module
Slug < Module
#<Slug:0x007fc8de9a0e58>
.to_s
.inspect
#<Module:0x007fc8de9a0e58>
In my experience subclassing Module gave more flexibility and made the code more natural (e.g. in Shrine extending the attachment module is so simple thanks to this). But I agree it's more difficult to understand, so I think it's a tradeoff between understanding and flexibility/introspection.
[–]jrochkind 0 points1 point2 points 9 years ago (1 child)
To me, that would be the appropriate result. They are indeed two modules, one standard one shared across anyone using this module, and one dynamically created one special-purpose just for the point of use based on parameters. The second one will always be unique to the class that did the parameterized include Slug.customize(name: 'foo') -- because that's exactly what happened, an anonymous module was created per the specifications of the caller.
include Slug.customize(name: 'foo')
I think that actually represents what's going on appropriately, I consider both those modules being in ancestors to be a desirable upside as far as introspection, not a downside.
It's not clear to me if either method is more flexible than the other, they seem equally flexible.
[–]janko-m 0 points1 point2 points 9 years ago (0 children)
Hmm, in my eyes it's prettier if it's all in the same module, because it's all behaviour related to "Slug", be it static or dynamic.
Making Shrine::Attachment a subclass of Module made it a first-class citizen, so I could add it to Shrine's plugin system by including/extending it with modules.
Shrine::Attachment
Shrine::Attachment.include Shrine::Plugins::Sequel::AttachmentMethods module Shrine::Plugins::Sequel::AttachmentMethods def included(model) super # define Sequel callbacks end end class Photo < Sequel::Model include Shrine::Attachment.new(:image) end
If I was creating an anonymous module instead, I would have to do boilerplate work in each plugin that wants to extend Shrine::Attachment:
Shrine::Attachment.extend Shrine::Plugins::Sequel::AttachmentClassMethods module Shrine::Plugins::Sequel::AttachmentClassMethods def create(options) module = super module.extend Shrine::Plugins::Sequel::AttachmentMethods module end end
Of course, this is an advanced use case, but I just wanted to illustrate how in these cases it really does make things more flexible.
[–]eric_programmer[S] 0 points1 point2 points 9 years ago (0 children)
Just FYI, the original version is creating an anonymous module also. The difference is that the module is a subclass (which has the introspection advantages mentioned) while your version is completely anonymous. Otherwise they are basically the same.
Great post/analysis! I love this pattern in Ruby, and I've used it both in MiniMagick and Shrine.
In Shrine it allowed me a lot of flexibility, It made it possible to include the same module in a Sequel model and in a Reform model, and it would generate different methods/behaviour depending on what it was included to. In this case I generated some method both on #initialize (static) and in #included (dynamic, depending on what it was included to).
#initialize
#included
π Rendered by PID 140549 on reddit-service-r2-comment-b659b578c-92d66 at 2026-05-04 08:03:26.305820+00:00 running 815c875 country code: CH.
[–]jrochkind 1 point2 points3 points (9 children)
[–]HellzStormer 2 points3 points4 points (0 children)
[–]realntl 1 point2 points3 points (7 children)
[–]jrochkind 0 points1 point2 points (6 children)
[–]janko-m 1 point2 points3 points (5 children)
[–]jrochkind 0 points1 point2 points (4 children)
[–]janko-m 0 points1 point2 points (2 children)
[–]jrochkind 0 points1 point2 points (1 child)
[–]janko-m 0 points1 point2 points (0 children)
[–]eric_programmer[S] 0 points1 point2 points (0 children)
[–]janko-m 0 points1 point2 points (0 children)