ActiveSupport::Concern and class methods - ruby-on-rails

I'm using ActiveSupport::Concern to dry up some code that is included on my AR classes. I have a module for calculating the lower wilson bound on data:
module CalculateWilsonBound
extend ActiveSupport::Concern
included do
class_attribute :wilson_ratings
class_attribute :wilson_positive_ratings
end
def calculate_wilson_lower_bound
number_of_ratings = self.class.wilson_ratings.call(self)
...
end
end
After I've included it onto an object, I want to provide two class level methods (wilson_ratings, wilson_positive_ratings) which define blocks which will return respective counts.
From the AR objects point of view:
class Influence < ActiveRecord::Base
include CalculateWilsonBound
wilson_ratings { |model| model.votes }
wilson_positive_ratings { |model| model.positive_votes }
This does not cause any runtime errors, but when I got to access the class attribute:
number_of_ratings = self.class.wilson_ratings.call(self)
It's nil.
First of all, am I organising the code in a that makes sense, secondaly, why is the class attribute nil?

I believe you will need to do:
class Influence < ActiveRecord::Base
include CalculateWilsonBound
self.wilson_ratings = Proc.new { |model| model.votes }
self.wilson_positive_ratings = Proc.new { |model| model.positive_votes }
end
At the moment you have 2 problems.
When trying to assign a class attribute in the context of a class definition Rails will not realise that you are referring to the class attribute unless you use self.
You need to assign the class attribute, rather than pass a block into it. As your code reads now it looks like you are calling a method called wilson_ratings and passing a block into it.
As to whether your code is sensible, it is starting to smell a little funny to me. I much prefer the service class pattern where possible (see http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/) - it prevents you from having to mess around with class_attributes and other potentially hairy concepts.

If you want to make class attribute callable, you can assign it a proc
self.wilson_ratings = proc{ |model| model.votes }

Related

Add method to a class which can only be accessed inside specific class

I have a class in initializers in which I use Hash class and I would like to add 2 methods to Hash class. I know how to add methods to the class but I don't want to make the Hash class "dirty".
Is there a way that I can extend the Hash class with those two methods but only inside the class where I use them?
You could use refinements for this:
Due to Ruby's open classes you can redefine or add functionality to existing classes. This is called a “monkey patch”. Unfortunately the scope of such changes is global. All users of the monkey-patched class see the same changes. This can cause unintended side-effects or breakage of programs.
Refinements are designed to reduce the impact of monkey patching on other users of the monkey-patched class. Refinements provide a way to extend a class locally. Refinements can modify both classes and modules.
Something like this:
module HashPatches
refine Hash do
def new_hash_method
# ...
end
end
end
and then:
class YourClass
using HashPatches
def m
{}.new_hash_method
end
end
That would let you call YourClass.new.m (which would use new_hash_method) but it wouldn't pollute Hash globally so outside YourClass, some_hash.new_hash_method would be a NoMethodError.
Reading:
Official Refinements docs
Refinements spec
A less hacky way could be to use SimpleDelegator.
class Foo
class SuperHash < SimpleDelegator
def new_method
# do something with hash
# you can use __getobj__() or super
end
end
private_constant :SuperHash
def initialize
#hash = SuperHash.new({})
end
end
https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html

Ruby class which could take an array of rules

I'm in the process of learning Ruby/Rails. I'm currently learning to create a model/classes.
I have an existing class that has these methods
def duration
(start_on..end_on).count
end
def items
space.available_items
end
def available_items
duration >= 365 ? items.not_including_insurance : items
end
In the class above, I have a method called available_items that checks if the duration is more than 365 days, then item will not be included in the array.
Rather than coupling the duration logic to the existing class, I think it's better to encapsulate it in another class which could take an array of rules to apply to the items.
So instead, in the above class, in the available_items method I can do something like:
policy.apply(items)
which will return all of the items which satisfy the rules.
And in the future, I can append more rules and keeps it flexible.
After includeing you module you still can define available_items method with custom rules (and have a generic one upstream), this is the easy way.
As for "passing" parameters to a strategy - it can be accomplished in a Concern like:
module SomePolicy
extend ActiveSupport::Concern
module ClassMethods
def my_policiable(param)
has_many :prices, class_name: 'Pricing::SimplePrice', as: :priceable
# ...
define_method(:available_items) {
if param == :foo
# ...
end
}
end
end
end
include SomePolicy
my_policiable(:foo)
trick is that the method is run in class context, there based on params you can define methods differently (but note that in example above if param.. will run for each resulting method invocation)

rails - instance model include module on variable condition

I need to know if I can include a module to an instantiated model.
What works today :
in the controller
#m = MyModel.create(params)
in the model
class Doc < ActiveRecord::Base
after_save :set_include
def set_include
if bool
self.class.send(:include, Module1)
else
self.class.send(:include, Module2)
end
end
end
and this works, but I'm afraid that self.class actually include the module for the class model an not the instantiated model
In this case, this will work.
The module methods are call after the object is saved.
But in many case, the controller will call some modules methods.
I thought of called the method set_include (up there) in a before_action of the controller.
But I really thinks that is not a good idea...
Any idea how I can really do that with in a good way ?
thanks !
Answer to your direct question is no. Your code only appears to be working and is actually not modifying instance of a class, but the class itself. So all instances of it will be getting this "benefit". Probably not what you wanted. Let me demonstrate with simple ruby example: https://repl.it/BnLO
What you can do instead is use extend with instance like: https://repl.it/BnLO/2
Applied to your code it would be:
class Doc < ActiveRecord::Base
after_save :set_include
def set_include
if bool
extend(Module1)
else
extend(Module2)
end
end
end
Also, self is not necessary. https://repl.it/BnLO/3
You need to use instance class (a.k.a eigenklass):
def set_include
singleton_class.instance_eval do
include bool ? Module1 : Module2
end
end
However the fact that you want to do this is suspicious and might lead to a disaster. So the question is: what are you really trying to achieve here - there surely is the better way of doing so.

What is the proper way to access class variables in Ruby 1.9?

I'm trying to set some class variables to store paths in a Rails application (but I think this more a ruby question)
Basically my class looks like this
class Image < ActiveRecord::Base
##path_to_folder = "app/assets"
##images_folder = "upimages"
##path_to_images = File.join(##path_to_folder, ##images_folder)
end
But when I try to access ##path_to_images from my controller by doing Image.path_to_images, I get a NoMethodError
When I try with Image.class_eval( ##path_to_images ), I get uninitialized class variable ##path_to_images in ImagesController
I've searched around and all I've seen says those would work, so I'm very confused about this
What's more, I tried defining simple classes with the ruby console like so
class Bidule
##foo = "foo"
Bar = "bar"
end
And so I tried, I think, all the ways possible (previous 2 included) to access them but no way I always get an exception raised
Rails provides class level attribute accessor for this functionality
Try
class Image < ActiveRecord::Base
cattr_accessor :path_to_folder
##path_to_folder = "app/assets"
end
Then to access path_to_folder class variable just use
Image.path_to_folder
But people always suggest to avoid class variables due to its behavior in inheritance.So you can use constants like
class Image < ActiveRecord::Base
PATH_TO_FOLDER = "app/assets"
end
Then you can access the constant like
Image::PATH_TO_FOLDER
Although I wouldn't in general recommend it, you can access class variables by passing a string to class_eval as in:
Image.class_eval('##path_to_folder')
at least in later versions of Ruby.
Note, however, that class variables are associated with the uppermost class in a subclassed hierarchy. In the case of ActiveRecord subclasses like this one, this means that these class variables really exist in the namespace of ActiveRecord::Base.
If you can't or don't want to extend class use:
Image.class_variable_get(:##path_to_images)
Best way is to set and get the value using methods. Below is a sample code
class Planet
##planets_count = 0
def initialize(name)
#name = name
##planets_count += 1
end
def self.planets_count
##planets_count
end
def self.add_planet
##planets_count += 1
end
def add_planet_from_obj
##planets_count += 1
end
end
Planet.new("uranus")
Plant.add_planet
obj = Planet.new("earth")
obj.add_planet_from_obj
Class variables are rarely used in Ruby applications because they have a lot of limitations and also tend to run against the grain of proper Object-Oriented design.
In nearly every case a class variable can be replaced with a proper constant, a class method, or both.
Your example is probably better described as:
class Image < ActiveRecord::Base
PATH_TO_FOLDER = "app/assets"
IMAGES_FOLDER = "upimages"
PATH_TO_IMAGES = File.join(PATH_TO_FOLDER, IMAGES_FOLDER)
end
Class variables are private to the class in question and don't trickle down to sub-classes and are difficult to access from an external context. Using constants allows the use of things like:
image_path = Image::PATH_TO_FOLDER
There are some circumstances under which a class variable is more reasonable than the alternative, but these are usually very rare.
You can do that by wrapping it in a class method, like this:
def self.path_to_images
##path_to_images
end
but I should mention that you should try to avoid using class variables in rails
I would modify it like this:
class Image < ActiveRecord::Base
##path_to_folder = "app/assets"
##images_folder = "upimages"
##path_to_images = File.join(##path_to_folder, ##images_folder)
def initialize
end
...
def self.path_to_folder
##path_to_folder
end
end
What you've done here is make the class variable into a method, so you can now access is using a .method name call. Since this is a class variable though, you can only call this on the class itself, not on the instance of a class.
You are getting a 'NoMethodError' because you're calling the class variable using a method which has not exist. The code above defines this method on the lines where you say:
def self.path_to_folder
##path_to_folder
end
This will now work:
Image.path_to_folder

Wrapping an object with methods from another class

Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.

Resources