Ruby mixin best practice - ruby-on-rails

New to Ruby\Rails, shame on me :(
I'm developing an engine for personal use (simple admin panel). What I want, is to be able to config my main app's models, like this:
class User < ActiveRecord::Base
include Entropy::Configurable
entropy_config do
form_caption 'Editing user'
end
end
And then in engine's templates do this:
<h1><%= #object.entropy_config :form_caption %></h1>
Engine's module:
module Entropy
module Configurable
def self.included(base)
## to call entropy_config in model class
base.send :extend, ClassMethods
end
def entropy_config(arg)
## ... I'm missing this part
end
module ClassMethods
##config = { ... }
def entropy_config (&block)
class_eval &block
end
def form_caption(arg)
// skipping class identification
##config[:user][:form_caption] = arg
end
end
end
end
The problem is that I can not get access to ##config from Configurable module, actually when I call entropy_config on #object. What I'm doing wrong?

First of all you've doing it wrong. Rails is on of the frameworks that pushed a lot on the MVC architecture. Having your model know about form captions is wrong. For that I would use the rails i18n gem. For the sake of the argument here's some untested code that will probably answer your question:
module Entropy
module Configurable
def self.included(base)
## to call entropy_config in model class
base.send :extend, ClassMethods
end
def entropy_config(key)
self.class.config[:user][key]
end
module ClassMethods
cattr_accessor :config
def entropy_config (&block)
self.config ||= {}
class_eval &block
end
def form_caption(arg)
// skipping class identification
self.config[:user][:form_caption] = arg
end
end
end
end
see http://apidock.com/rails/Class/cattr_accessor for more info

Related

How to prevent a method from being accessed/ overwritten from outside a concern?

Given the following model and concerns
def MyModel
include ConcernA
include ConcernB
end
module ConcernA
extend ActiveSupport::Concern
def print_a
formatted_text
end
protected
def formatted_text
"ConcernA"
end
end
module ConcernB
extend ActiveSupport::Concern
def print_b
formatted_text
end
protected
def formatted_text
"ConcernB"
end
end
The formatted_text method gets overwritten
=> MyModel.print_a
= "ConcernB"
=> MyModel.print_b
= "ConcernB"
Is there a way to actually protect the formatted_text method so that it is only accessible within the concern and therefore not overwritten?
There's no hard protection. If someone wants to change something, they most likely will be able to. But you can devise some safeguards against accidental name clash.
For example, extract the method into its own namespace:
module ConcernA
extend ActiveSupport::Concern
def print_a
Impl.new.formatted_text
end
class Impl
def formatted_text
"ConcernA"
end
end
end
module ConcernB
extend ActiveSupport::Concern
def print_b
Impl.new.formatted_text
end
class Impl
def formatted_text
"ConcernB"
end
end
end
class MyModel
include ConcernA
include ConcernB
end
MyModel.new.print_a # => "ConcernA"
MyModel.new.print_b # => "ConcernB"
This way, ConcernA::Impl and ConcernB::Impl are two unrelated classes and don't affect each other.

how to find out in which class i currently include my module?

how do we find out in which class we are currently including a module? (coming from rails-background where we do the has_* style modules)
class Foo
has_likes
end
module HasLikes
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def has_likes(options = {})
end
end
module ContentMethods
def self.included(base)
base.extend ClassMethods
end
# ?????
# how do we get Foo here?
end
end
First of all, as #Jordan said in comments, it smells as a design flaw.
Secondary, inside ContentMethods declaration, it’s definitely impossible, since it is always included after been declared.
In general, one might do it afterwards with ObjectSpace#each_object:
module HasLikes
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def has_likes(options = {})
end
end
module ContentMethods
def self.included(base)
base.extend ClassMethods
end
end
end
class Foo
include ::HasLikes::ContentMethods
has_likes
end
ObjectSpace.each_object(Class) do |x|
p(x) if x <= ::HasLikes::ContentMethods
end
#⇒ Foo
But please do not do that at home or school.

Creating a module for raising class-specific errors

In my rails projects, I often use this sort of behavior in my classes and models:
class Whatever
class WhateverError < StandardError; end
def initialize(params={})
raise WhateverError.new("Bad params: #{params}") if condition
# actual class code to follow
end
end
The trouble is, this is both hugely repetitive and fairly verbose. I'd love it if I could just do this whenever I need to raise a class-specific error:
class ErrorRaiser
include ClassErrors
def initialize(params={})
error("Bad params: #{params}") if condition
error if other_condition # has default message
# actual class code to follow
end
def self.class_method
error if third_condition # class method, behaves identically
end
end
I'm having major trouble creating such a module. My sad early attempts have tended to look something like the below, but I'm pretty confused about what's available within the scope of the module, how to dynamically create classes (within methods?) or whether I have straightforward access to the "calling" class at all.
My basic requirements are that error be both a class method and an instance method, that it be "namespaced" to the class calling it, and that it have a default message. Any thoughts/help? Is this even possible?
module ClassErrorable
# This and the "extend" bit (theoretically) allow error to be a class method as well
module ClassMethods
def self.error(string=nil)
ClassErrorable.new(string).error
end
end
def self.included(base)
set_error_class(base)
base.extend ClassMethods
end
def self.set_error_class(base)
# I'm shaky on the scoping. Do I refer to this with # in a class method
# but ## in an instance method? Should I define it here with # then?
##error_class = "##{base.class}Error".constantize
end
def self.actual_error
# This obviously doesn't work, and in fact,
# it raises a syntax error. How can I make my
# constant a class inheriting from StandardError?
##actual_error = ##error_class < StandardError; end
end
def initialize(string)
#string = string || "There's been an error!"
end
def error(string=nil)
raise ##actual_error.new(string)
end
end
How about something like this (written in pure Ruby; it could be refactored to use some Rails-specific features like .constantize):
module ClassErrorable
module ClassMethods
def error(message = nil)
klass = Object::const_get(exception_class_name)
raise klass.new(message || "There's been an error!")
end
def exception_class_name
name + 'Error'
end
end
def self.included(base)
base.extend ClassMethods
Object::const_set(base.exception_class_name, Class.new(Exception))
end
def error(message = nil)
self.class.error(message)
end
end

Accessing other methods in a Ruby module

I am writing my first Rails gem, which adds a method to ActiveRecord. I can't seem to figure out a simple way to call other methods from within the method I am adding to ActiveRecord. Is there a pattern for this I should be using?
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
# This doesn't work
some_utility_method
end
end
def some_utility_method
# Do something useful
end
end
ActiveRecord::Base.send(:include, MyModule)
Once you've included MyModule, ActiveRecord::Base will have my_class_method as a class method (equivalently, an instance method of the Class object ActiveRecord::Base), and some_utility_method as an instance method.
So, inside my_class_method, self is the Class ActiveRecord::Base, not an instance of that class; it does not have some_utility_method as an available method
Edit:
If you want a utility method private to the Module, you could do it like this:
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
# This doesn't work
MyModule::some_utility_method
end
end
def self.some_utility_method
# Do something useful
end
end
ActiveRecord::Base.send(:include, MyModule)

including Modules in controller

I have done a module in lib directory in ruby on rails application
its like
module Select
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def select_for(object_name, options={})
#does some operation
self.send(:include, Selector::InstanceMethods)
end
end
I called this in a controller like
include Selector
select_for :organization, :submenu => :general
but I want to call this in a function
i.e
def select
#Call the module here
end
Let's clarify: You have a method defined in a module, and you want that method to be used in an instance method.
class MyController < ApplicationController
include Select
# You used to call this in the class scope, we're going to move it to
# An instance scope.
#
# select_for :organization, :submenu => :general
def show # Or any action
# Now we're using this inside an instance method.
#
select_for :organization, :submenu => :general
end
end
I'm going to change your module slightly. This uses include instead of extend. extend is for adding class methods, and include it for adding instance methods:
module Select
def self.included(base)
base.class_eval do
include InstanceMethods
end
end
module InstanceMethods
def select_for(object_name, options={})
# Does some operation
self.send(:include, Selector::InstanceMethods)
end
end
end
That will give you an instance method. If you want both instance and class methods, you just add the ClassMethods module, and use extend instead of include:
module Select
def self.included(base)
base.class_eval do
include InstanceMethods
extend ClassMethods
end
end
module InstanceMethods
def select_for(object_name, options={})
# Does some operation
self.send(:include, Selector::InstanceMethods)
end
end
module ClassMethods
def a_class_method
end
end
end
Does that clear things up? In your example you defined a module as Select but included Selector in your controller...I just used Select in my code.

Resources