Access object attribute inside module - ruby-on-rails

I have a class with the attribute .weekday saved as an integer value.
I'm trying to create a method in a module that converts that numeric value to the corresponding weekday.
This is how I like to work:
MyClass.weekday
=> 2
MyClass.weekday.my_module_method
=> "Tuesday"
Is it possible to do this conversion with a module method or am I thinking wrong here?
I can access the object from within the module mehtod, by self, but I don't seem to be able to do self.weekday.

What you are trying to do is certainly possible. You are correct when you point to ActiveRecord::Inflector as something that works similarly. This approach modifies the Fixnum class itself to add new methods and although I don't generally recommend ad-hoc patching of core classes, you can see it in action in active_support/core_ext/integer/inflections.rb :
require 'active_support/inflector'
class Integer
# Ordinalize turns a number into an ordinal string used to denote the
# position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
#
# 1.ordinalize # => "1st"
# 2.ordinalize # => "2nd"
# 1002.ordinalize # => "1002nd"
# 1003.ordinalize # => "1003rd"
# -11.ordinalize # => "-11th"
# -1001.ordinalize # => "-1001st"
#
def ordinalize
ActiveSupport::Inflector.ordinalize(self)
end
end
In your case I might do something like:
module WeekdayInflector
def weekday
Date::DAYNAMES[self]
end
end
class Fixnum
include WeekdayInflector
end
which will at least help others track down the methods you added by looking at the module. Please note that this will affect ALL instances of Fixnum and could lead to conflicts if you include a Gem that tries to do the same thing. It is worth asking whether this tradeoff is worth it or if defining a simple view helper is the better way to go.

Related

Overwriting default accessors with options

I am using Ruby on Rails 4 and I would like to know what could be the pitfalls when I overwrite default accessors. The Rails' Official Documentation says (in the initial lines):
The mapping that binds a given Active Record class to a certain
database table will happen automatically in most common cases, but can
be overwritten for the uncommon ones.
More, in that documentation there is the "Overwriting default accessors" section which makes me think that I can do it without any problem. What do you think about?
In my case I would like to overwrite attribute accessors in order to provide some options, something like this:
# Given my Article model has :title and :content attributes
# Then I would like to overwrite accessors providing options this way:
class Article < ActiveRecord::Base
def title(options = {})
# Some logic...
end
def content(options = {})
# Some logic...
end
end
# So that I can run
#article.title # => "Sample Title"
#article.title(:parse => true) # => "Sample *Title*"
#article.content # => "Sample description very long text"
#article.content(:length => :short) # => "Sample description..."
Maybe this is more Ruby than Rails, but will be the #article.title calling the title(options => {}) method or it will call the Rails attribute accessor that access the related database table column value?
Update (after commenting)
Since it seems that in the above code default accessors are not overwritten, is there a way to provide options for those accessors so to reach what I am looking for? If so, how?
#article.title #=> will call your method
#article.title(:parse => true) #=> will call your method
There is no method overloading in ruby if that is what you are looking for.
Looking closer at the official documentation I see where your code diverges.
You forgot "=" when defining your method.
class Foo < ActiveRecord::Base
def self.bar=(value)
#foo = value
return 'OK'
end
end
Foo.bar = 3 #=> 3
WARNING: Never rely on anything that happens inside an assignment method,
(eg. in conditional statements like in the example above)

Elegant way to stringify keys with Rails and Mongo?

If you supply Mongo with a hash that uses symbols as keys and save the document, it will 'stringify' it, meaning the keys will be converted to strings. To summarize:
condition: hash keys will be:
---------- ------------------
after initializing a document symbols or strings
after saving a document strings
after fetching a document strings
This 'asymmetry' has led to some ugliness in my tests. I would like to be able to 'rely on' the keys always being strings - and not worry about if the document has just been initialized or not.
What are one or more elegant ways to avoid this?
Note: In my case, I'm using Mongoid, but I don't think this question is necessarily Mongoid specific. It probably applies to any Rails project that uses MongoDB.
Something along these lines could work. Basically this code redefines Mongoid's field macro (its setter).
require 'mongoid'
module Stringifier
def field name, args = {}
super # call mongoid implementation
define_method "#{name}=" do |val|
val.stringify_keys! if val && val.respond_to?(:stringify_keys!)
super(val)
end
end
end
class Foo
include Mongoid::Document
extend Stringifier
field :subhash, type: Hash
end
f = Foo.new
f.subhash = {a: 1, b: 2}
puts f.subhash
# >> {"a"=>1, "b"=>2}
This may not be the cleanest implementation, but you get the idea.
My current solution is to override each field setter to call stringify_keys!. For example:
def field_name=(x)
x.stringify_keys! if x
super(x)
end
This it the best I've found so far. I considered other alternatives:
Using a before_validation callback. However, I don't like this approach. I didn't like having to call valid? in order to trigger stringification.
Using after_initialize. However, this doesn't handle the case of calling a setter after initialization.

Can't understand Ruby's magic

In railscasts project you can see this code:
before(:each) do
login_as Factory(:user, :admin => true)
end
The corresponding definition for the function is:
Factory.define :user do |f|
f.sequence(:github_username) { |n| "foo#{n}" }
end
I can't understand how the admin parameter is passing to function, while in the function there's no word about admin parameter. Thanks
Factory.define is not a function definition, it is a method that takes a symbol or string (in this case user) and a block that defines the factory you are making. Factory(:user, :admin => true) makes a User object, with admin attributes. It is not calling the code in your second snippet, it is calling Factory() which initializes a factory, and selects one (in this case the one defined in second snippet). Then it passes options in hash form to Factory as well.
Factory selects the :user factory which is very generic. The option :admin=>true just tells Factory to set the admin instance variable on User to true.
This is actually what it is calling in factory.rb in factory girl
def initialize(name, options = {}) #:nodoc:
assert_valid_options(options)
#name = factory_name_for(name)
#options = options
#attributes = []
end
So Factory(name,options) is equivalent to Factory.new(name,options) in this code.
http://www.ruby-doc.org/core/classes/Kernel.html Notice Array and String etc have similar constructs. I am trying to figure out how they did that now.
This is all confusing even for decent Ruby programmers. I recommend strongly the book "Metaprogramming Ruby" It is probably the best book I have read in ruby and it tells you a lot about this magic stuff.
Michael Papile's response is essentially correct. However, I'd like to elaborate upon it a bit as there are some technical nuances that you might wish to be aware of. I looked over the code for railscasts and factory_girl and I believe there are a few extra pieces to the puzzle that explain how the :admin => true arg ends up creating the admin attribute of the user factory. The attribute addition does not actually happen by way of Factory's initialize() method, although as Michael pointed out that method is indeed being called in service of building the new user factory object.
I'm going to include in this explanation all the steps I took in case you'd like to see how to go about investigating similar questions you might have.
Since your original post is dated Feb. 17th I looked at the version of railscasts that closely matches that date.
I looked in its Gemfile:
https://github.com/ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/Gemfile
Line 18:
gem "factory_girl_rails"
I then checked out the commit of factory_girl_rails that most closely matched the Feb 17th date.
https://github.com/thoughtbot/factory_girl_rails/blob/544868740c3e26d8a5e8337940f9de4990b1cd0b/factory_girl_rails.gemspec
Line 16:
s.add_runtime_dependency('factory_girl', '~> 2.0.0.beta')
factory_girl version 2.0.0.beta was actually not so easy to find. There are no github tags with that name so I just checked out the closest in terms of commit date.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/vintage.rb
Lines 122-128:
# Shortcut for Factory.default_strategy.
#
# Example:
# Factory(:user, :name => 'Joe')
def Factory(name, attrs = {})
Factory.default_strategy(name, attrs)
end
So the Factory invocation in railscasts is actually calling a convenience method which invokes the "default strategy", which is located in the same file:
Lines 39-52:
# Executes the default strategy for the given factory. This is usually create,
# but it can be overridden for each factory.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# The result of the default strategy.
def self.default_strategy(name, overrides = {})
self.send(FactoryGirl.find(name).default_strategy, name, overrides)
end
Note that FactoryGirl.find is invoked to get the object on which to invoke default_strategy. The find method resolves to here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/registry.rb
Lines 12-14:
def find(name)
#items[name.to_sym] or raise ArgumentError.new("Not registered: #{name.to_s}")
end
Here the name is :user. Thus we wish to invoke default_strategy on the user factory. As Michael Papile pointed out, this user factory was defined and registered by the railscasts code that you originally thought was the class definition for Factory.
https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb
Lines 23-25:
Factory.define :user do |f|
f.sequence(:github_username) { |n| "foo#{n}" }
end
So in investigating what the default strategy is for the user factory, I looked around in the railscasts project and found this:
https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb
Lines 43-45:
def default_strategy #:nodoc:
#options[:default_strategy] || :create
end
:create is the default strategy. We go back to factory_girl to find the def for create.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/methods.rb
Lines 37-55:
# Generates, saves, and returns an instance from this factory. Attributes can
# be individually overridden by passing in a Hash of attribute => value
# pairs.
#
# Instances are saved using the +save!+ method, so ActiveRecord models will
# raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# A saved instance of the class this factory generates, with generated
# attributes assigned.
def create(name, overrides = {})
FactoryGirl.find(name).run(Proxy::Create, overrides)
end
The create strategy calls the run method defined here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/factory.rb
Lines 86-97:
def run(proxy_class, overrides) #:nodoc:
proxy = proxy_class.new(build_class)
overrides = symbolize_keys(overrides)
overrides.each {|attr, val| proxy.set(attr, val) }
passed_keys = overrides.keys.collect {|k| FactoryGirl.aliases_for(k) }.flatten
#attributes.each do |attribute|
unless passed_keys.include?(attribute.name)
attribute.add_to(proxy)
end
end
proxy.result(#to_create_block)
end
A translation/summarization of what this code is doing:
First, the proxy object is built by calling new on the proxy_class, which in this case is Proxy::Create, which is defined here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/create.rb
Basically all you need to know is that proxy is building a new user factory object and invoking callbacks before and after the factory object is created.
Going back to the run method, we see that all the extra args that were originally passed into the Factory convenience method (in this case, :admin => true) are now being labelled as overrides. The proxy object then invokes a set method, passing in each attribute-name/value pair as args.
The set() method is part of the Build class, the parent class of Proxy.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/build.rb
Lines 12-14:
def set(attribute, value)
#instance.send(:"#{attribute}=", value)
end
Here #instance refers to the proxied object, the user factory object.
This then, is how :admin => true is set as an attribute on the user factory that the railscasts spec code creates.
If you want, you can google "programming design patterns" and read about the following patterns: Factory, Proxy, Builder, Strategy.
Michael Papile wrote:
http://www.ruby-doc.org/core/classes/Kernel.html Notice Array and
String etc have similar constructs. I am trying to figure out how they
did that now.
If you are still curious, the Array and String you see in the Kernel doc are actually just factory methods used to create new objects of those types. That's why no new method invocation is needed. They are not actually constructor calls per se, but they do allocate and initialize Array and String objects, and hence under the hood they are doing the equivalent of calling initialize() on objects of those types. (In C though, of course, not Ruby)
I don't think that second snippet is the definition for the function. Function definitions have def and end. I think that second snippet looks like a function or method being called with an argument of :user and a block that takes an f parameter.
Of course with metaprogramming you can never really be sure what the hell is going on.

ruby convert class name in string to actual class

How do I call a class from a string containing that class name inside of it? (I guess I could do case/when but that seems ugly.)
The reason I ask is because I'm using the acts_as_commentable plugin, among others, and these store the commentable_type as a column. I want to be able to call whatever particular commentable class to do a find(commentable_id) on it.
Thanks.
I think what you want is constantize
That's an RoR construct. I don't know if there's one for ruby core
"Object".constantize # => Object
It depends on the string...
If it already has the proper shape (casing, pluralization, etc), and would otherwise map directly to an object, then:
Rails:
'User'.constantize # => User
Ruby:
Module.const_get 'User' # => User
But otherwise (note the difference in casing):
'user'.constantize # => NameError: wrong constant name user
Module.const_get 'user' # => NameError: wrong constant name user
Therefore, you must ask... is the source string singular or plural (does it reference a table or not?), is it multi-word and AlreadyCamelCased or is_it_underscored?
With Rails you have these tools at your disposal:
Use camelize to convert strings to UpperCamelCase strings, even handling underscores and forward slashes:
'object'.constantize # => NameError: wrong constant name object
'object'.camelize # => "Object"
'object'.camelize.constantize # => Object
'active_model/errors'.camelize # => "ActiveModel::Errors"
'active_model/errors'.camelize.constantize # => ActiveModel::Errors
Use classify to convert a string, which may even be plural (i.e. perhaps it's a table reference), to create a class name (still a string), then call constantize to try to find and return the class name constant (note that in Ruby class names are constants):
'users'.classify => "User" # a string
'users'.classify.constantize # => User
'user'.classify => "User" # a string
'user'.classify.constantize # => User
'ham_and_eggs'.classify # => "HamAndEgg"
In POR (Plain Old Ruby), you have capitalize, but it only works for the first word:
Module.const_get 'user'.capitalize => User
...otherwise you must use fundamental tools like strip, split, map, join, etc. to achieve the appropriate manipulation:
class HamAndEgg end # => nil
Module.const_get ' ham and eggs '.strip.gsub(/s$/,'').split(' ').map{|w| w.capitalize}.join # => HamAndEgg
I know this is an old question but I just want to leave this note, it may be helpful for others.
In plain Ruby, Module.const_get can find nested constants. For instance, having the following structure:
module MyModule
module MySubmodule
class MyModel
end
end
end
You can use it as follows:
Module.const_get("MyModule::MySubmodule::MyModel")
MyModule.const_get("MySubmodule")
MyModule::MySubmodule.const_get("MyModel")
When ActiveSupport is available (e.g. in Rails): String#constantize or String#safe_constantize, that is "ClassName".constantize.
In pure Ruby: Module#const_get, typically Object.const_get("ClassName").
In recent rubies, both work with constants nested in modules, like in Object.const_get("Outer::Inner").
If you want to convert string to actuall class name to access model or any other class
str = "group class"
> str.camelize.constantize 'or'
> str.classify.constantize 'or'
> str.titleize.constantize
Example :
def call_me(str)
str.titleize.gsub(" ","").constantize.all
end
Call method : call_me("group class")
Result:
GroupClass Load (0.7ms) SELECT `group_classes`.* FROM `group_classes`

Redefining Rails' methods

The Rails method Array#to_sentence allows for the following:
['a', 'b', 'c'].to_sentence # gives: "a, b, and c"
I would like to extend this method to allow it to take a block, so that you can do something like the following (where people is an array of Person objects, which have the name attribute):
people.to_sentence { |person| person.name }
# => "Bill, John, and Mark"
I don't have a problem with writing the extension method. But I can't work out where to put it. The Rails core extensions get loaded somewhere down in the depths of ActiveSupport.
My need is for a place where user-defined code is always loaded, and is pre-loaded (before any application code).
Create config/initializers/super_to_sentence.rb. All files in this directory are loaded after Rails has been loaded, so you'll have a chance to override Rails' definition of Array#to_sentence.
For code you want to load before Rails gets loaded, add it to config/environment.rb.
I like to do this:
# config/initializers/app.rb
Dir[File.join(Rails.root, "lib", "core_ext", "*.rb")].each {|l| require l }
# lib/core_ext/array.rb
class Array
def to_sentence_with_block(*args, &block)
if block_given?
# do something...
# to_sentence_without_block(*args) perhaps?
else
to_sentence_without_block(*args)
end
end
alias_method_chain :to_sentence, :block
end
I think this is an ugly idea. Why dont you just write
people.collect { |person| person.name }.to_sentence
This looks almost the same and will not confuse other people reading your code (like yourself in 2 years)
just searching around the web, seems like good practice is to add it in lib/
so if you wanna extend some ruby class (in my case, DateTime), just put the code in .rb and then in config/environment.rb:
config.after_initialize do
require "lib/super_datetime.rb"
end
my super_datetime.rb looks like this (code from http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/140184):
class DateTime
def days_in_month
self::class.civil(year, month, -1).day
end
alias dim days_in_month
def weekdays
(1..dim).to_a
end
end
restart your server and you'll have access to the new method for all DateTime objects.

Resources