Store gem configuration in the gem's module - ruby

My gem exposes a module that can be included on Mongoid mapper classes to keep track of changes.
I want the user to be able to flag specific fields on their classes so that they will be handled differrently.
My idea is to create an appendable flag. Please see an outline of the relevant sections of the module below:
module PendingChanges
included do
attr_accessor :__appendable_fields
private def self.appendable(field)
self.__appendable_fields << field
end
end
end
So when the user want to flag a field on his document as "appendable" they would do something like:
class Person
include PendingChanges
field :name, type: String
field :emails, type: Array
appendable :emails <- this is what they would do
end
I don't seem to be able to make it work, as __appendable_fields is always nil. Also, I'm not confident this is the "Ruby way" of approaching this?

Apparently, I had to use mattr_accessor instead.
I'm able to access it then with self.__appendable_fields on the appendable method.

Related

What if objects can print themselves in Ruby? [Object#print]

I was thinking wouldn't it be cool to have a print method defined in the Ruby Object class? Consider the following:
class Object
def print
puts self.to_s
end
end
23.times &:print
Is there any issue in having something like this? Seems like a good feature to have. It also appears easy to read.
There's already Object#inspect defined. Plus, there's already Kernel#print defined as private method in Object class and every class that inherits from it.
This method already exists in the Ruby standard library. However, it has a different name: display.
23.times &:display
# 012345678910111213141516171819202122
As you can see, it does not write a newline after the object's string representation; it is ill-suited for object inspection.
The main issue with adding methods to Object is that they become universal and may clash with similarly named methods in other libraries or in your project.
There are already multiple simple ways to output data or convert to string form in Ruby core, so the risk of a clash (on a very useful method name) likely outweighs any benefits from nicer syntax even in your own code.
If you have a smaller set of classes in your own project, where you feel this would be a useful feature to have, then this is an ideal use case for mix-ins.
Define a module:
module CanPrintSelf
def print
puts self.to_s
end
end
And include it in any class you want to have the feature:
class MyClass
include CanPrintSelf
end
my_object = MyClass.new
my_object.print
So you can have this feature if you like it, and you don't need to modify Object.

Allowing users to create plugins for my class, how to extend it correctly?

I have a class which I want other developers to be able to create modules to extend my class and give it more instance variables.
How can I automatically include modules in my class based on files in a particular folder? i.e. load all files and include them as modules for my class
How can I do this and somehow avoid method naming collisions?
I was thinking I could pass the name of the plugin in the initialize method, and do this:
class MyClass
def initialize(plugin_name=nil)
end
def method_missing(method_name)
"#{plugin_name}".send(method_name)
end
end
Would something like this work in theory, please help out in the method_missing as I have never written one before. I'm trying to basically specify a particular plugin name to avoid any name collisions.
Or should I, when including the plugins, output an error if there is a method collision?
Allowing external code to be melted in your class sounds dangerous and drives into names collisions as you guessed. It cans also drive into unexpected behavior, since you allow external code to mess with your class internals.
The best way to handle a plugin would be to encaspulate the behavior that would be replaced.
Let's take an example :
Given you're building a Logger, we can imagine that you can create plugins to specify where the logs will be wrote.
You can then create the following class :
class Logger
def initialize(store)
#store = store
end
def info(message)
#store.write(:info, message)
end
end
class StandardOutputStore
def write(level, message)
puts "#{level}: #{message}"
end
end
class FileStore
def initalize(filename)
#filename = filename
end
def write(level, message)
File.open(#filename, "a") do |file|
file.write("#{level}: #{message}")
end
end
end
Then you can you instanciate the right plugin this way :
logger = Logger.new(StandardOutputStore.new)
logger.warn "hello"
logger = Logger.new(FileStore.new("/tmp/log"))
logger.warn "hello"
This way of doing things provides modularity, more flexibility and is more solid than overloading things in your class with external code.
Loading modules as they are found in a directory is a matter of toying with Dir.glob and require. To explore what had been found under your plugin directory, you can force your users to write plugins in a module, like Foo::Plugins::FileStore and inspect which constants are present in the Foo::Plugins module after requiring the files. See http://www.ruby-doc.org/core-1.9.3/Module.html#method-i-constants for more informations.
Edit: As you don't have any interface to map to since you provides the methods to an ERB template, you could do the following :
Plugins are in the following form :
module Plugins
module Emoticons
def smile
":-)"
end
end
end
You can then load them with the following :
Dir["/plugins/*.rb"].each {|file| require file }
And include them, given TemplateMethods is a module included in the context of your ERB template.
Plugins.constants.each do |constant|
mod = Plugins.const_get constant
TemplateMethods.send(:include, mod)
end
Using send isn't very elegant, I'd suggest building a class method on TemplateMethods that encapsulate the whole thing, like TemplateMethods.load_plugins_method! The bang here would warn about the intrusive action done by the dynamic modules inclusion.
That should do the job :)

Is this define_method use case too complex?

I've been bashing my head against this for about three days now. I've created a class that models html pages and tells cucumber step definitions where to populate form data:
class FlightSearchPage
def initialize(browser, page, brand)
#browser = browser
#start_url = page
#Get reference to config file
config_file = File.join(File.dirname(__FILE__), '..', 'config', 'site_config.yml')
#Store hash of config values in local variable
config = YAML.load_file config_file
#brand = brand #brand is specified by the customer in the features file
#Define instance variables from the hash keys
config.each do |k,v|
instance_variable_set("##{k}",v)
end
end
def method_missing(sym, *args, &block)
#browser.send sym, *args, &block
end
def page_title
#Returns contents of <title> tag in current page.
#browser.title
end
def visit
#browser.goto(#start_url)
end
def set_origin(origin)
self.text_field(#route[:attribute] => #route[:origin]).set origin
end
def set_destination(destination)
self.text_field(#route[:attribute] => #route[:destination]).set destination
end
def set_departure_date(outbound)
self.text_field(#route[:attribute] => #date[:outgoing_date]).set outbound
end
# [...snip]
end
As you can see, I've used instance_variable_set to create the variables that hold the references on the fly, and the variable names and values are supplied by the config file (which is designed to be editable by people who aren't necessarily familiar with Ruby).
Unfortunately, this is a big, hairy class and I'm going to have to edit the source code every time I want to add a new field, which is obviously bad design so I've been trying to go a stage further and create the methods that set the variable names dynamically with define_method and this is what's kept me awake until 4am for the last few nights.
This is what I've done:
require File.expand_path(File.dirname(__FILE__) + '/flight_search_page')
class SetFieldsByType < FlightSearchPage
def text_field(config_hash)
define_method(config_hash) do |data|
self.text_field(config_hash[:attribute] => config_hash[:origin]).set data
end
end
end
The idea is that all you need to do to add a new field is add a new entry to the YAML file and define_method will create the method to allow cucumber to populate it.
At the moment, I'm having problems with scope - Ruby thinks that define_method is a member of #browser. But what I want to know is: is this even feasible? Have I totally misunderstood define_method?
Do you mean that you'd expect to see the requires and file loads outside the class definition?
No, inside the class definition. Ruby class declarations are just code that execute in the order it's seen. Things like attr_accessor are just class methods that happen to do things to the class being defined, as it's being defined. This seems like what you want to do.
In your case you'd read the YAML file instead, and run your own logic to create accessors, build any backing data required, etc. I don't totally grok the usecase, but it doesn't sound unusual or difficult--yet.
That said, how much "convenience" do you get by putting these definitions in a YAML file? Consider something like I did once to create page instances I used to drive Watir:
class SomePage < HeavyWatir
has_text :fname # Assumed default CSS accessor pattern
has_text :whatever, accessor: 'some accessor mechanism', option: 'some other option'
end
The has_xxx were class methods that created instance variable accessors (just like attr_accessor does), built up some other data structures I used to make sure all the things that were supposed to be on the page actually were, and so on. For example, very roughly:
page = SomePage.new
page.visit
if page.missing_fields?
# Do something saying the page isn't complete
end
It sounds like you want something vaguely similar: you have a bunch of "things" you want to give to the class (or a sub-class, or you could mix it in to an arbitrary class, etc.)
Those "things" have additional functionality, that functionality works in multiple ways, like:
Things-that-happen-during-definition
E.g., has_text adds the name to a class instance hash ofthe page's metadata, like field names.
Things-that-happen-during-usage
E.g., when fname= is called set a flag in the instance's metadata saying the setter was called.
This is an appropriate case for metaprogramming, but it looks like you're going about it the wrong way.
First of all, is there going to be a different config file for each instance of FlightSearchPage or just one config file that controls all pages? It looks like you're loading the same config file regardless of the arguments to initialize so I'm guessing your case is the former.
If that is so, you need to move all of your metaprogramming code into the class (outside method definitions). I.e. when the class is defined, you want it to load the config file and then each instance is created based on that config. Right now you have it reloading the config file every time you create an instance, which seems incorrect. For example, define_method belongs to Module so it should appear in class scope, rather than in an instance method.
On the other hand, if you do want a different config for each instance, you need to move all of your metaprogramming code into the singleton class e.g. define_singleton_method rather than define_method.

Rails equivalent of ApplicationController for models

I understand that application_controller.rb is the place to put all the methods, etc that you would like made available in all your controllers since they all inherit from this class. Great.
But what is the equivalent for Models? In other words, I want a place where I can create a couple of super classes that my models will inherit from.
For example, I have a method that searches different tables for entries in all CAPS via REGEXP in Mysql. I'd like to be able to create the method only once and call it for different tables/models.
What is the Rails way of doing this?
I thought I could create a class that would inherit from ActiveRecord::Base (as all models do) , put the methods in there and then inherit all my models from that class. But thought there would surely be a better way to do it.
Thanks.
Edit
Per Semyon's answer I'm editing the post to show the routes I am using. It works now:
# models/dvd.rb
require 'ModelFunctions'
class Dvd < ActiveRecord::Base
extend ModelFunctions
...
end
# lib/ModelFunctions.rb
module ModelFunctions
def detect_uppercase(object)
case object
...
where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]]{5,}' ").not_locked.reorder("LENGTH(#{field}), #{table}.#{field} ASC")
end
end
In config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
Take a look at mixins, for example here:
http://ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
In a Rails app you could create a module in the lib directory that defines your methods and then include it in your models.
EDIT: To be specific for your example, you're trying to define a class method. You can do this in a mixin like this:
module Common
module ClassMethods
def detect_uppercase(object)
case object
when 'dvd'
field = 'title'
...
end
where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]] {5,}').not_locked.reorder('LENGTH(title), title ASC')"
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
Now when you include Common in your model, that model's class will be extended to include the new class methods, and you should be able to call Dvd.detect_uppercase.
Put the reusable method in some module next to your Dvd class. You can move it in a separate file later.
# app/models/dvd.rb
module CaseInsensitiveSearch
def case_insensitive_search(field, value)
# searching field for value goes here
end
end
class Dvd
end
After extending a class with the module you can use case_insensitive_search on the class. Including the module will make case_insensitive_search an instance method which is not what you want.
class Dvd
extend CaseInsensitiveSearch
end
Dvd.case_insensitive_search("title", "foo")
And of course you can use it inside Dvd class.
class Dvd
def self.search(query)
case_insensitive_search("title", query)
end
end
Dvd.search("foo")
Now when you made sure it works, you will probably want to move it in a separate file and use it across multiple classes. Place it in lib/case_insensitive_search.rb and make sure you have this line in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Now you can require it anywhere you want to use it:
require 'case_insensitive_search'
class Dvd
extend CaseInsensitiveSearch
end
The last thing I'd like to suggest. Create multiple modules with meaningful names. So instead of CommonModel have CaseInsensitiveSearch and so on.

Ruby precedence of methods in objects extended with multiple modules

Given the following:
class User; attr_accessor :roles; end
module RegisteredUser
def default_context
Submission
end
end
module Admin
def default_context
Review
end
end
current_user = User.new
current_user.roles = ["registered_user", "admin"]
current_user.roles.each do |role|
role_module = role.gsub(/ /, '_').camelize
if module_exists?(role_module)
current_user.extend role_module.constantize
end
end
context = self.extend current_user.default_context
Is there a way to set the priority of User#default_context? That is, can I say that the Admin#default_context always takes priority over RegisteredUser#default_context regardless of the order in which current_user is extended?
method_added is in Module.
I actually meant included, not extended, but both are also in Module.
The mechanism would revolve around doing something like this:
module Foo
def self.included(base)
base.extend(FooMethods)
end
module FooMethods
def bar
# Whatever
end
end
end
Inside Foo.included you can determine, based on arbitrary criteria, whether or not the methods in question should be added to base (the entity including the module).
In your case, you could check to see if a "higher priority" module was already included, or see if the module is the "higher priority" module. Based on that you'd decide whether or not to add the methods.
Since an Admin is also a RegisteredUser, I would do
module Admin
include RegisteredUser
...
end
and then only
current_user.extend Admin
I am not sure if this is the correct way. If Admin and RegisteredUser were classes, it would make sense making Admin inherit from RegisteredUser. In case of modules, dunno.
You cannot; in Ruby the order of module inclusion is the order in which modules are searched (after the current class, before parent classes). The only way to change the 'priority' is to include the modules in the order you want, or to move them to a parent class.
Although not pure Ruby, you can use the Remix library by banisterfiend to change module ordering (or unmixin a module, or...other things).

Resources