Default reader for ActiveRecord Rails 4 - ruby

Is there any default reader method for ActiveRecord such that whenever I fetch a value from the DB this reader method will be called?(Property.last.name or Student.first.course). I want to override this method in such a way that if the data type is a string then convert it to uppercase. I don't want to add separate getters in each model for each column.
For ex:
def reader(column)
column.upcase if column.is_a? String
end

There is a method ActiveRecord::AttributeMethods::Read#read_attribute that returns the value after it has been typecasted. So monkey-patching (or refining) this method might give you what you want - all strings values will be transformed.
But I'd seriously reconsider this approach in general: all the data transformations should be explicit and visible if you don't want to shoot own leg.
It doesn't mean you cannot avoid redefining getters manually. For example, you can do smth. like this (disclaimer: quick and dirty example):
module TransformValues
def transform(*attributes_to_transform, transformer: nil, &block)
attributes_to_transform.each do |attribute_name|
define_method attribute_name do
case transformer = (block || transformer)
when Proc
transformer.call(self[attribute_name])
when Symbol
self[attribute_name].__send__(transformer)
else
self[attribute_name]
end
end
end
end
end
# and then
class User < ActiveRecord::Base
extend TransformValues
transform :first_name, :last_name, transformer: :upcase
transform :whatever do |val|
do_smth_with(val)
end
# etc
end
In this case all your transformations stay more or less explicit without tons of manually written boilerplate. If all you need is just upcased strings, all this can be seriously reduced to just smth. like
module UpcaseAttributes
def always_upcase(*attributes_to_transform)
attributes_to_transform.each do |attribute_name|
define_method attribute_name do
case value = self[attribute_name]
when String
value.upcase
else
value
end
end
end
end
end
class User < ActiveRecord::Base
extend UpcaseAttributes
always_upcase :first_name, :last_name
end

Related

How to print attribute accessor value when attribute accessor has same name as method

I have a class Book with an instance variable title and a method title
class Book
attr_accessor :title
def title(title)
#title = title.capitalize
end
end
I create a book object
b = Book.new
And then I set the title
b.title("inferno")
Now when I try to print the title field variable value
p b.title
Ruby thinks I'm trying to call the method
"title": wrong number of arguments
Any idea how to print the field variable value?
Thanks
All attr_accessor :title does is create wrapper methods, identical to def title() and def title=(value). It's just a syntactic sugar method generation. You can then set the value with:
b.title = "Whatever"
and access with:
b.title
If you just wanted a reader or a writer, you could use attr_reader :title or attr_writer :title separately.
If you want to make your own reader/writer methods manually, all the above do is create:
def title
#title
end
def title=(value)
#title = value
end
You can't have two methods with the same name and different arity in Ruby, so when you define your own method (as in your question), you're overwriting the reader method with your writer. That leaves you with two ways to set and no way to read.
You could use attr_reader and a custom writer:
def title=(value)
#title = value.capitalize
end
You can always name the method anything you want, like def capitalize_and_set_title(value). It might be more clear than the magic of attr_accessor and operator overloading.
I think that what you want in order to set the title is:
def title=(str)
super str.try(:capitalize)
end
If you want to be able to set the value with title(some_value) and read it with title (without arguments), you could do e.g.:
class Book
def title(title = :no_value_given)
if title == :no_value_given
#title
else
#title = title.capitalize
end
end
end
This relies on a default argument value for when you call it without arguments.
The reason I did not use nil as a default argument value is so that you can set the value to nil.

Create hash of instance methods references

I have a class that can parse different types of messages and what I want to do is to create a hash that will use the msg type id as the keys and different instance methods as the values.
Something like this:
class Parser
def initialize(msg_id)
#my_methods = {1 => method_1, 2 => method_2, 3 => method_3}
#my_methods[msg_id]()
end
def method_1
end
def method_2
end
def method_3
end end
I know it's possible, but I am not sure how to do it. I tried using the self.method(:method_1) as a value but I got an error saying that method_1 is not defined.
Thank you
The simplest possible changes to fix your code are like this:
class Parser
def initialize(msg_id)
#my_methods = { 1 => method(:method_1), 2 => method(:method_2), 3 => method(:method_3) }
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
I.e. use the Object#method method to get a Method object, and use the Method#call method to execute it.
However, there are a few improvements we could make. For one, your Hash associates Integers with values. But there is a better data structure which already does that: an Array. (Note: if your message IDs are not assigned sequentially, then a Hash is probably the right choice, but from the looks of your example, they are just Integers counting up from 1.)
And secondly, hardcoding the methods inside the Parser#initialize method is probably not a good idea. There should be a declarative description of the protocol, i.e. the message IDs and their corresponding method names somewhere.
class Parser
# this will make your message IDs start at 0, though
PROTOCOL_MAPPING = [:method_1, :method_2, :method_3].freeze
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
Another possibility would be something like this:
class Parser
PROTOCOL_MAPPING = []
private_class_method def self.parser(name)
PROTOCOL_MAPPING << name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
parser def method_1; end
parser def method_2; end
parser def method_3; end
end
Or maybe this:
class Parser
PROTOCOL_MAPPING = {}
private_class_method def self.parser(msg_id, name)
PROTOCOL_MAPPING[msg_id] = name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map {|msg_id, name| [msg_id, method(name)] }.to_h.freeze
#my_methods[msg_id].()
end
parser 1, def method_1; end
parser 2, def method_2; end
parser 3, def method_3; end
end
While provided answer would work fine, there are few "minor" issues with it:
If there'd be tons of methods, hardcoding such hash would take time, and since it is not dynamic (because you have to update the hash manually each time new method is added to the class body) it is very error prone.
Even though you are within the class, and technically have access to all methods defined with any visibility scope with implicit receiver (including private and protected), it is still a good practice to only rely on public interface, thus, I'd recommend to use Object#public_send.
So here is what I would suggest (despite the fact I do not see how the idea of having such map would work in real life):
class Parser
def initialize(msg_id)
# generate a dynamic hash with keys starting with 1
# and ending with the size of the methods count
methods_map = Hash[(1..instance_methods.size).zip(instance_methods)]
# Use public_send to ensure, only public methods are accessed
public_send(methods_map[msg_id])
end
# create a method, which holds a list of all instance methods defined in the class
def instance_methods
self.class.instance_methods(false)
end
end
After a quick thought I refactored it a bit, so that we hide the implementation of the mapping to private methods:
class Parser
def initialize(msg_id)
public_send(methods_map[msg_id])
end
# methods omitted
private
def methods_map # not methods_hash, because what we do is mapping
Hash[(1..instance_methods.size).zip(instance_methods)]
# or
# Hash[instance_methods.each.with_index(1).map(&:reverse)]
end
def instance_methods
self.class.instance_methods(false)
end
end
The method you're looking for is send.
Note that the values in your hash need to be symbols to be passed to send.
class Parser
def initialize(msg_id)
#my_methods = {1 => :method_1, 2 => :method_2, 3 => :method_3}
send(#my_methods[msg_id])
end
def method_1
end
def method_2
end
def method_3
end
end
Documentation here

How do I stub a Class object in Rspec?

I am trying hard to enforce encapsulation (but am probably not doing very well), and want to test the code in Rspec. The Customer class will take a class Object (as klass) when it is instantiated in a factory class. Through an as yet non-existent UI, the Customer will create an Order.
My current test is as follows. I just want to confirm that the order is the Order class.
describe 'Customer' do
let(:customer){Customer.new}
let(:customer_with_instantiation){Customer.new(:klass=>order, :name=>'Oscar Wilde', :number=>'0234567')}
let(:order){double :order, :name=>:order}
it 'klass object to be the order class when customer is instantiated with a klass attribute' do
expect(customer_with_instantiation.klass).to be_a(order)
end
end
Class code as follows:
class Customer
attr_accessor :name, :number, :klass
DEFAULT_CUSTOMER_ORDER = {:order_detail => [{ :dish=>"",
:item_count=>0 }],
:order_total_cost=>0 }
def initialize(options={})
#name=options.fetch(:name, "")
#number=options.fetch(:number, "")
#klass=options.fetch(:klass, Object)
#customer_order=DEFAULT_CUSTOMER_ORDER
end
def place_order(menu)
#requires user input
customer_order=klass.new({:order_detail => [{:dish => :pizza, :item_count => 3},
{:dish => :burger, :item_count => 3}],
:order_total_cost => 210})
klass.test_customer_order(customer_order, self)
end
end
class Order
attr_reader :order_detail, :order_total_cost
attr_accessor :total_check
def initialize(options={})
#order_detail=options.fetch(:order_detail, Object)
#order_total_cost=options.fetch(:order_total_cost, Object)
end
def self.test_customer_order(customer_order, customer, menu, assistant)
customer_order.total_check = 0
customer_order.order_detail.each do |order_item|
menu.dishes.each do |dish|
if order_item[:dish]==dish.name
customer_order.total_check += dish.price*order_item[:item_count]
end
end
end
assistant.take_order(customer_order, customer, customer_order.total_check)
end
end
Any help gratefully appreciated!
By using be_a, you're testing that klass is an instance of klass, which is probably not what you want.
It seems to me that when testing the initialize method and the getter for klass (which is what you're doing, in effect), you should only be interested in confirming that whatever you send into Customer.new can be read afterwards.
So maybe something like this:
class Foo
attr_reader :klass
def initialize(args)
#klass = args.fetch(:klass)
end
end
describe Foo do
describe "#initialize" do
let(:klass) { double }
let(:instance) { Foo.new(klass: klass)}
it "sets klass" do
expect(instance.klass).to eq(klass)
end
end
end
Some general points:
If you want to test whether the order is an instance of klass, you should probably rewrite your code to make that easier to test
klass isn't a very useful name in this case. It isn't clear why a Customer would need a klass.
You want to decouple the order from the customer, but the customer is clearly making some assumptions about the interface of the order. Did you really achieve anything?
I'd recommend not putting test methods in the classes themselves, but rather in the test files.
Using Object as a default in fetch is probably not what you want. To begin with, you probably want them to be instances of some class, not class objects.
Is it really the job of an instance of the Customer class to create orders? If the point is to make sure that any kind of abstract order can be instantiated based on user input, maybe a separate OrderCreator class would be more appropriate? This class could accept user data and an order class and the affected customer.

How can I create instances of a ruby class from a hash array?

I have a module FDParser that reads a csv file and returns a nice array of hashes that each look like this:
{
:name_of_investment => "Zenith Birla",
:type => "half-yearly interest",
:folio_no => "52357",
:principal_amount => "150000",
:date_of_commencement => "14/05/2010",
:period => "3 years",
:rate_of_interest => "11.25"
}
Now I have an Investment class that accepts the above hash as input and transforms each attribute according to what I need.
class Investment
attr_reader :name_of_investment, :type, :folio_no,
:principal_amount, :date_of_commencement,
:period, :rate_of_interest
def initialize(hash_data)
#name = hash_data[:name_of_investment]
#type = hash_data[:type]
#folio_no = hash_data[:folio_no]
#initial_deposit = hash_data[:principal_amount]
#started_on =hash_data[:date_of_commencement]
#term = hash_data[:period]
#rate_of_interest = hash_data[:rate_of_interest]
end
def type
#-- custom transformation here
end
end
I also have a Porfolio class with which I wish to manage a collection of investment objects. Here is what the Portfolio class looks like:
class Portfolio
include Enumerable
attr_reader :investments
def initialize(investments)
#investments = investments
end
def each &block
#investments.each do |investment|
if block_given?
block.call investment
else
yield investment
end
end
end
end
Now what I want is to loop over the investment_data yielded by the module and dynamically create instances of the investment class and then send those instances as input to the Portfolio class.
So far I tried:
FDParser.investment_data.each_with_index do |data, index|
"inv#{index+1}" = Investment.new(data)
end
But obviously this doesn't work because I get a string instead of an object instance. What is the right way to send a collection of instances to a enumerable collection class that can then manage them?
I'm not sure what "send as input to the Portfolio class" means; classes themselves don't accept "input". But if you're just trying to add Investment objects to the #investments instance variable inside an instance of Portfolio, try this:
portfolio = Portfolio.new([])
FDParser.investment_data.each do |data|
portfolio.investments << Investment.new(data)
end
Note that the array literal [] and the return value of portfolio.investments point to the self-same Array object here. This means you could equivalently do this, which arguably is a little clearer:
investments = []
FDParser.investment_data.each do |data|
investments << Investment.new(data)
end
Portfolio.new(investments)
And if you want to play a little code golf, it shrinks further if you use map.
investments = FDParser.investment_data.map {|data| Investment.new(data) }
Portfolio.new(investments)
I think this is a little harder to read than the previous option, though.

Variable scope definitions and inheritance

I'm working on a extended search feature for my webpage.
I looked at ransack, however it's lacking some functionalities I need, makes the url-query string very long and has some bugs (reported).
Thus I started to implement my own hack.
First I want to present my idea, afterwards I want to ask kindly how to fix my issue and in the end if there are other ways to improve this.
The idea:
A model defines something like this (additionally, the model is inside an engine):
module EngineName
class Post < ActiveRecord::Base
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
end
:name is to define the query-param to use e.g. this would be ?q[name]=something
I know that this is not fully generic like ransack, but well...
:as is to build up the correct form-tag. :string would be for text_field, :integer for number_field and so on. I want to extend it further to implement auto-generating of collections for associations etc.
Now the block is a simple scope to use.
I run into several shortcomings with ransack when building up complex queries (like with count() etc.). Now I can specify my own optimized query in squeel.
I extended ActiveRecord::Base to set up the logic (the global one, not inside the engine. I want to use it everywhere).
I defined a scope :search so I can use Model.search(param[q]) like in ransack.
Also I tried to keep a list of keys which are "searchable" defined by the search_for calls.
class ActiveRecord::Base
##searchable_attributes = Hash.new({})
def self.search_for(name, *opts, &search_scope)
return unless search_scope
##searchable_attributes[name] = {
:type => opts[:as],
:condition => search_scope
}
unless ##searchable_attributes.has_key? :nil
##searchable_attributes[:nil] = Proc.new { scoped }
end
end
scope :search, lambda {|q|
next unless q.kind_of?(Hash)
base = ##searchable_attributes[:nil].call
q.each do |key, search|
next unless base.class.searchable_attributes.has_key?(key)
base = ##searchable_attributes[key][:condition].call(base, search)
end
base
}
end
Now the issues:
It has mostly to do with inheritance of the classes. But even after reading and trying 3, 4 it does not worked.
Please take a look at the second line in the scope :search.
There I'm calling the simple Proc I definied above which only includes "scoped"
This is to get arround the issue that self returns "ActiveRecord::Base" and not the model itself like "Post" or "Comment".
It's because the scope is called on the Base class on inheritance, however I did not find anything to fix this.
As search_for is called on the model itself (e.g. Post) the scope-model returned there is "the right one".
Does anyone know how to circumvent this?
The next question would be, how to store the list of "searchable" scopes. I used ##variables. But as they are shared within every subclass, this would be a no-go.
However, it needs to be static as the search_for is called without initialize a instance (isn't it?)
Last but not least, it is somekind horrible to always specify the base-model to use on every scope so that I can chain them together.
Is there any other possibilities to improve this?
Ok, it seems I got it finally myself my putting several other answers from other questions together.
Model:
module EngineName
class Post < ActiveRecord::Base
searchable
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
end
My "Plugin" currently as an initializer:
class ActiveRecord::Base
def self.searchable
include Searchable
end
end
module Searchable
def self.included(base)
base.class_eval {
##searchable_attributes = Hash.new({})
def self.search_for(name, opts)
return unless block_given?
##searchable_attributes[name] = {
:type => opts[:as],
:condition => Proc.new
}
end
# Named scopes
scope :search, lambda {|q|
next unless q.kind_of?(Hash)
base = self.scoped
q.each do |key, search|
key = key.to_sym
next unless ##searchable_attributes.has_key?(key)
base = ##searchable_attributes[key][:condition].call(base, search)
end
base
}
}
end
end
Hope it'll help some others working on the same problem.
Rails provides a helper for class_attribute. This provides inheritable class attributes, but allows subclassess to "change their own value and it will not impact parent class". However a hash which is mutated using []= for example would effect the parent, so you can ensure that a new copy is made when subclassing using rubys inherited method
Therefore you could declare and initialise on the base class like so:
module Searchable
extend ActiveSupport::Concern
included do
class_attribute :searchable_attributes
end
module ClassMethods
def inherited(subclass)
subclass.searchable_attributes = Hash.new({})
end
def search_for(name,opts)
return unless block_given?
searchable_attributes[name] = {
:type => opts[:as],
:condition => Proc.new
}
end
end
end
Note that I used ActiveSupport::Concern to gain the neater syntax for defining stuff directly on the class and also mixing in class methods. Then you can simply add this to active record base:
ActiveRecord::Base.send(:include, Searchable)
now any classes get their own attributes hash:
class Post < ActiveRecord::Base
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end

Resources