Can I create something in my model to do something like:
MyModel::TYPE::ONE
MyModel::TYPE::TWO
where ONE and TWO are strings? I placed them in a constant in my model like:
class MyModel
TYPE = ['ONE', 'TWO']
end
so I can access MyModel::Type and get the array, but how do I get one more level?
You can get the syntax you desire with:
[~]$ irb
irb(main):001:0> module MyModel
irb(main):002:1> module TYPE
irb(main):003:2> ONE = 1
irb(main):004:2> TWO = 2
irb(main):005:2> end
irb(main):006:1> end
=> 2
irb(main):007:0> MyModel::TYPE::ONE
=> 1
irb(main):008:0> MyModel::TYPE::TWO
=> 2
This has the disadvantage, or maybe the advantage, of allowing extra "attributes" on the enum, not unlike what Java gives you. You can make the values of ONE and TWO be maps if you like, which is similar to Java's enum objects.
EDIT: You can also get the values like this:
irb(main):009:0> MyModel::TYPE::constants
=> [:ONE, :TWO]
Related
I'm running into an issue when trying to create an open struct with an attribute that has the same name as one of the OpenStruct instance methods. Specifically, i'd like to create an open struct that has an attribute capture. I'm using this as a stub in an rspec test, so i can't change the name of the method (it must be capture)
#=> OpenStruct.new(capture: true).capture
#=> ArgumentError: wrong number of arguments (0 for 1)
looking at the OpenStruct methods, it has a method capture and it is this method that is getting called. Is there a way to instantiate an open struct with an attribute of the same name as one of its methods?
for clarity, i specifically need the method capture, which i've confirmed breaks on rails 4.0.x but not rails 5, but this situation holds true for any method openstruct might have.
#=> OpenStruct.new(class: true).class
#=> OpenStruct
This works just fine for me in pry (running ruby 2.3, by the way)
[9] pry(main)> OpenStruct.new(capture: 1).capture
=> 1
Here's another way to do it:
[15] pry(main)> a = OpenStruct.new capture: 1
=> #<OpenStruct capture=1>
[22] pry(main)> a.singleton_class.class_exec { def capture; self[:capture] + 1; end }
=> :capture
[23] pry(main)> a.capture
=> 2
i don't know what testing library you're using, but if it's RSpec, you could use this mocking approach as well:
a = OpenStruct.new capture: 0
allow(a).to receive(:capture).and_return(a[:capture])
a.capture # => 0
I have a ruby hash that I'd like to render using RABL. The hash looks something like this:
#my_hash = {
:c => {
:d => "e"
}
}
I'm trying to render this with some RABL code:
object #my_hash => :some_object
attributes :d
node(:c) { |n| n[:d] }
but I'm receiving {"c":null}
How can I render this with RABL?
This works for arbitrary hash values.
object false
#values.keys.each do |key|
node(key){ #values[key] }
end
Worked for me using Rails 3.2.13 and Ruby 2.0.0-p195
Currently RABL doesn't play too nicely with hashes. I was able to work around this by converting my hash to an OpenStruct format (which uses a more RABL-friendly dot-notation). Using your example:
your_controller.rb
require 'ostruct'
#my_hash = OpenStruct.new
#my_hash.d = 'e'
your_view.rabl
object false
child #my_hash => :c do
attributes :d
end
results
{
"c":{
"d":"e"
}
}
Sometimes its easy to do too much imho.
How about just
render json: my_hash
And just like magic we can delete some code !
RABL deals in objects but does not require a particular ORM. Just objects that support dot notation. If you want to use rabl and all you have is a hash:
#user = { :name => "Bob", :age => 27, :year => 1976 }
then you need to first turn the hash into an object that supports dot notation:
#user = OpenStruct.new({ :name => "Bob", :age => 27, :year => 1976 })
and then within a RABL template treat the OpenStruct as any other object:
object #user
attributes :name, :age, :year
Consider that if everything you are doing in your app is just dealing in hashes and there is no objects or databases involved, you may be better off with an alternative more custom JSON builder such as json_builder or jbuilder.
Pasted from the official wiki page on RABL's github: https://github.com/nesquena/rabl/wiki/Rendering-hash-objects-in-rabl
RABL actually can render ruby hashes and arrays easily, as attributes, just not as the root object. So, for instance, if you create an OpenStruct like this for the root object:
#my_object = OpenStruct.new
#my_object.data = {:c => {:d => 'e'}}
Then you could use this RABL template:
object #my_object
attributes :data
And that would render:
{"data": {"c":{"d":"e"}} }
Alternatively, if you want :c to be a property of your root object, you can use "node" to create that node, and render the hash inside that node:
# -- rails controller or whatever --
#my_hash = {:c => {:d => :e}}
# -- RABL file --
object #my_hash
# Create a node with a block which receives #my_hash as an argument:
node { |my_hash|
# The hash returned from this unnamed node will be merged into the parent, so we
# just return the hash we want to be represented in the root of the response.
# RABL will render anything inside this hash as JSON (nested hashes, arrays, etc)
# Note: we could also return a new hash of specific keys and values if we didn't
# want the whole hash
my_hash
end
# renders:
{"c": {"d": "e"}}
Incidentally, this is exactly the same as just using render :json => #my_hash in rails, so RABL is not particularly useful in this trivial case ;) But it demonstrates the mechanics anyway.
By specifying a node like that, you are given access to the #my_hash object which you can then access attributes of. So I would just slightly change your code to be:
object #my_hash
node(:c) do |c_node|
{:d => c_node.d}
end
where c_node is essentially the #my_hash object. This should give you what you're expecting (shown here in JSON):
{
"my_hash":{
"c":{
"d":"e"
}
}
}
My answer is partially based on the below listed site:
Adapted from this site:
http://www.rubyquiz.com/quiz81.html
require "ostruct"
class Object
def to_openstruct
self
end
end
class Array
def to_openstruct
map{ |el| el.to_openstruct }
end
end
class Hash
def to_openstruct
mapped = {}
each{ |key,value| mapped[key] = value.to_openstruct }
OpenStruct.new(mapped)
end
end
Define this perhaps in an initializer and then for any hash just put to_openstruct and send that over to the rabl template and basically do what jnunn shows in the view.
I am using Ruby on Rails 3.0.7 and I would like to retrieve the class name, also if it is namespaced. For example, if I have a class named User::Profile::Manager I would retrieve the Manager string from that using some unknown to me Ruby or Ruby on Rails method and in a secure way.
BTW: What other "usefull" information that are "commonly" used can I get for the class?
Some useful simple metaprogramming calls:
user = User::Profile::Manager.new(some_params)
user.class # => User::Profile::Manager
user.class.class # => Class
user.class.name # => "User::Profile::Manager"
user.class.name.class # => String
# respond_to? lets you know if you can call a method on an object or if the method you specify is undefined
user.respond_to?(:class) # => true
user.respond_to?(:authenticate!) # => Might be true depending on your authentication solution
user.respond_to?(:herpderp) # => false (unless you're the best programmer ever)
# class.ancestors is an array of the class names of the inheritance chain for an object
# In rails 3.1 it yields this for strings:
"string".class.ancestors.each{|anc| puts anc}
String
JSON::Ext::Generator::GeneratorMethods::String
Comparable
Object
PP::ObjectMixin
JSON::Ext::Generator::GeneratorMethods::Object
ActiveSupport::Dependencies::Loadable
Kernel
BasicObject
If you want the lowest-level class from User::Profile::Manager I'd probably do the following [using a regex for this seems like overkill to me ;)]:
user = User::Profile::Manager.new
class_as_string = user.class.name.split('::').last # => "Manager"
class_as_class = class_name.constantize # => Manager
Edit:
If you actually want to look through some more metaprogramming calls, check the docs for the Object and Module classes, and check out the google results for "Ruby Metaprogramming".
Have you tried class method:
class A
class B
end
end
myobject = A::B.new
myobject.class
=> A::B
To expand on #JCorcuera's answer, some other useful information can be found with kind_of? and methods
class A
class B
def foo
end
end
end
myobject = A::B.new
p myobject.class
=> A::B
p myobject.kind_of? A::B
=> true
p myobject.methods
=> [:foo, :nil?, :===, :=~, ...
p myobject.methods.include? :foo
=> true
Factory Girl is incredibly useful for functional testing, but has one annoying property that makes it slightly harder to use in unit tests, where I don't want to rely on the test database. I often use Factory.build to create a factory that I can then pass around or assign to an ActiveRecord.find call using flexmock:
require 'test_helper'
require 'flexmock'
class SomeMixinTest < ActiveSupport::TestCase
include FlexMock::TestCase
def setup
#foo = Factory.build(:foo, :id => 123,
:bar => Factory.build(:bar, :id => 456,
:baz => Factory.build(:baz, :id => 789)
)
)
flexmock Foo, :find => #foo
end
def test_find_by_reverse_id
assert_equal #foo, Foo.find_by_reverse_id(321)
end
end
This pattern is very nice, since it cares not about the presence of the database, and runs much faster than if the objects had to actually be persisted. However, it is a bit annoying to have to build the associated objects manually. If you don't, the associated objects are actually created in the database by the build call, as if you had used create instead.
assert_equal [], Foo.all
foo = Factory.build :foo # build my associations too, please
assert_equal [], Foo.all # look Ma, no mocks!
assert_equal [], Bar.all # <=== ASSERTION FAILED
assert_equal [], Baz.all
This is non-intuitive to say the least, and causes an actual problem when I'm testing a few classes that need to play nicely with a mixin. I want to be able to do this:
KLASSES_UNDER_TEST = [Foo, Bar, Baz]
def test_find_by_reverse_id
KLASSES_UNDER_TEST.each do |klass|
objects = (123..456).map do |id|
Factory.build klass.to_s.downcase.to_sym, :id => id
end
flexmock klass, :all => objects
objects.each do |object|
assert_equal object, klass.find_by_reverse_id(object.id.to_s.reverse), "#{klass} #{object.id}"
end
end
But this has the nasty side effect of creating 333 Bars and 666 Bazes ("Baz" does sound kind of like a demon's nickname, so maybe that's fitting) in the database, making this test slower than molasses flowing uphill in the winter.
I'd like to create a helper method like this:
def setup_mocks(klass)
klass_sym = klass.to_s.downcase.to_sym
objects = (123..456).map{|id|
associated_objects = Hash[
Factory.associations(klass_sym).map do |association|
[ association, setup_mocks(association, 1) ]
end
]
Factory.build klass_sym, associated_objects
end
flexmock klass, :all => objects
objects
end
So, does anything like Factory.associations exist?
I've not tested this, but looking at the source it seems that something like this should work:
FactoryGirl.find(:factory_name).associations
irb(main):001:0> require 'active_support'
=> true
irb(main):002:0> require 'active_support/inflector/inflections'
=> true
irb(main):003:0> ActiveSupport::Inflector.pluralize('test')
=> "test"
irb(main):004:0> ActiveSupport::Inflector.singularize('tests')
=> "tests"
irb(main):005:0> ActiveSupport::Inflector.titleize('hat simulator')
=> "Hat Simulator"
<ort::Inflector.tableize("america's number one hat simulator")
=> "america's number one hat simulator"
Well, basically, that's the question. It's confusing me that methods such as titleize seem to work fine, but tableize, pluralize and singularize don't.
Have I forgotten to require something?
(On a separate note, I notice this page provides examples like "post".pluralize, which when I tried, resulted in NoMethodError: undefined method 'pluralize' for "post":String. But maybe that's something to save for another question.)
Access to #pluralize without adding new methods to the String class:
require 'active_support/inflector'
ActiveSupport::Inflector.pluralize('test')
#=> "tests"
For String class:
require 'active_support/core_ext/string'
"test".pluralize
#=> "tests"
which actually calls ActiveSupport::Inflector.pluralize underneath:
def pluralize
ActiveSupport::Inflector.pluralize(self)
end