How to store method attributes in variable - ruby-on-rails

class Dates
ATTRS = {date_from: '', date_to: ''}
def set_dates(ATTRS)
#date_from = date_from
#date_to = date_to
end
def show_dates(ATTRS)
p date_from
p date_to
end
end
Dates.new.set_dates(date_from: Time.current, date_to: Time.current)
#-:4: formal argument cannot be a constant
# def set_dates(ATTRS)
# ^
#-:9: formal argument cannot be a constant
# def show_dates(ATTRS)
SO question is: Is it possible to store method attributes in variable ?

You can use the new keyword arguments syntax in Ruby 2.0:
class Dates
def set_dates(date_from: '', date_to: '')
#date_from = date_from
#date_to = date_to
end
def show_dates
p #date_from
p #date_to
end
end
Or the hash arguments before Ruby 2.0:
class Dates
ATTRS = {date_from: '', date_to: ''}
def set_dates(attr)
attr = ATTRS.merge(attr)
#date_from = attr[:date_from]
#date_to = attr[:date_to]
end
def show_dates
p #date_from
p #date_to
end
end
For the show_dates method, I guess you meant showing the status of the Dates instance, so I made some modification to it.
Besides, in Ruby, variables started with capitalized letter are treated as constants, you cannot use it as formal arguments of the methods.

I would write your code as below using Ruby (2.0.0 > =) new keyword rest argument(**) operator :
class Dates
def set_dates(**attr)
#date_from,#date_to = attr.values_at(:date_from,:date_to)
end
def show_dates
p #date_from
p #date_to
end
end

Related

Ruby on Rails: Handling invalid dates with multiparameter dates

I've added a form to my rails app which asks for a date. Rather than use the (IMO) clunky date_select helper, or a date popup solution, I'd like to use seperate input fields for date, month and year (as specified in the GDS service manual). I've written a custom input for simple_form here:
class TextDateInput < SimpleForm::Inputs::Base
def input(wrapper_options)
input_html_options[:pattern] = '[0-9]*'
#value = #builder.object.send(attribute_name)
#merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
"#{date_field} #{month_field} #{year_field}".html_safe
end
def date_field
#builder.label(attribute_name, class: 'grouped-date date') do
output = template.content_tag(:span, 'Date')
output += #builder.text_field(attribute_name,
#merged_input_options.merge(
name: "#{#builder.object_name}[#{attribute_name}(3i)]",
maxlength: 2,
value: #value&.day
))
output
end
end
def month_field
#builder.label(attribute_name, class: 'grouped-date month') do
output = template.content_tag(:span, 'Month')
output += #builder.text_field(attribute_name,
#merged_input_options.merge(
name: "#{#builder.object_name}[#{attribute_name}(2i)]",
maxlength: 2,
value: #value&.month
))
output
end
end
def year_field
#builder.label(attribute_name, class: 'grouped-date year') do
output = template.content_tag(:span, 'Year')
output += #builder.text_field(attribute_name,
#merged_input_options.merge(
name: "#{#builder.object_name}[#{attribute_name}(1i)]",
maxlength: 4,
value: #value&.year
))
output
end
end
end
And it works perfectly in the frontend, however, if the user enters an invalid date (for example 99/99/9999), Rails raises an ActiveRecord::MultiparameterAssignmentErrors error. Is there a clean way to handle this so rather than raising an error I can apply a validation error to the database object and show an invalid date error to the user?
You can parse date in before_filter callback or introduce params object which will replace invalid date with date that can trigger AR validation.
I decided to have a stab at this myself and added the following to my base model class (I'm using Rails 5):
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def update_attributes(attrs = {})
super parse_dates(attrs)
end
def parse_dates(attrs)
# First fetch any nested attributes
attrs.clone.each do |k, v|
next unless v.is_a?(Hash)
# If this is a hash, it's a nested attribute, so check for dates
attrs = parse_nested_dates(k, attrs)
end
# Now marshal the rest of the dates
marshal_dates(self.class, attrs)
end
def parse_nested_dates(key, attrs)
klass = Object.const_get key.split('_attributes').first.classify
attrs[key] = marshal_dates(klass, attrs[key])
attrs
end
def marshal_dates(klass, attrs)
# Get all the columns in the class that have a date type
date_columns = klass.columns_hash.select { |_k, value| value.type == :date }.keys
date_columns.each { |c| attrs = parse_date(attrs, c) }
attrs
end
def parse_date(attrs, property)
# Gather up all the date parts
keys = attrs.keys.select { |k| k =~ /#{property}/ }.sort
return attrs if keys.empty?
values = []
keys.each do |k|
values << attrs[k]
attrs.delete(k)
end
# Set the date as a standard ISO8601 date
attrs[property] = values.join('-')
attrs
end
end
This seems to work perfectly for both standard attributes and nested attributes, and means it automatically works for all date columns without me having to do anything.

How do I create accessors for ActiveRecord models from an array of symbols? [duplicate]

When I run code below it raise error:
implicit argument passing of super from method defined by define_method() is not supported. Specify all arguments explicitly. (RuntimeError).
I am not sure what is the problem.
class Result
def total(*scores)
percentage_calculation(*scores)
end
private
def percentage_calculation(*scores)
puts "Calculation for #{scores.inspect}"
scores.inject {|sum, n| sum + n } * (100.0/80.0)
end
end
def mem_result(obj, method)
anon = class << obj; self; end
anon.class_eval do
mem ||= {}
define_method(method) do |*args|
if mem.has_key?(args)
mem[args]
else
mem[args] = super
end
end
end
end
r = Result.new
mem_result(r, :total)
puts r.total(5,10,10,10,10,10,10,10)
puts r.total(5,10,10,10,10,10,10,10)
puts r.total(10,10,10,10,10,10,10,10)
puts r.total(10,10,10,10,10,10,10,10)
The error message is quite descriptive. You need to explicitly pass arguments to super when you call it inside of define_method block:
mem[args] = super(*args)

OpenStruct issue with Ruby 2.3.1

In Ruby 2.1.5 and 2.2.4, creating a new Collector returns the correct result.
require 'ostruct'
module ResourceResponses
class Collector < OpenStruct
def initialize
super
#table = Hash.new {|h,k| h[k] = Response.new }
end
end
class Response
attr_reader :publish_formats, :publish_block, :blocks, :block_order
def initialize
#publish_formats = []
#blocks = {}
#block_order = []
end
end
end
> Collector.new
=> #<ResourceResponses::Collector>
Collector.new.responses
=> #<ResourceResponses::Response:0x007fb3f409ae98 #block_order=[], #blocks= {}, #publish_formats=[]>
When I upgrade to Ruby 2.3.1, it starts returning back nil instead.
> Collector.new
=> #<ResourceResponses::Collector>
> Collector.new.responses
=> nil
I've done a lot of reading around how OpenStruct is now 10x faster in 2.3 but I'm not seeing what change was made that would break the relationship between Collector and Response. Any help is very appreciated. Rails is at version 4.2.7.1.
Let's have a look at the implementation of method_missing in the current implementation:
def method_missing(mid, *args) # :nodoc:
len = args.length
if mname = mid[/.*(?==\z)/m]
if len != 1
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
end
modifiable?[new_ostruct_member!(mname)] = args[0]
elsif len == 0
if #table.key?(mid)
new_ostruct_member!(mid) unless frozen?
#table[mid]
end
else
err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
err.set_backtrace caller(1)
raise err
end
end
The interesting part is the block in the middle that runs when the method name didn't end with an = and when there are no addition arguments:
if #table.key?(mid)
new_ostruct_member!(mid) unless frozen?
#table[mid]
end
As you can see the implementation first checks if the key exists, before actually reading the value.
This breaks your implementation with the hash that returns a new Response.new when a key/value is not set. Because just calling key? doesn't trigger the setting of the default value:
hash = Hash.new { |h,k| h[k] = :bar }
hash.has_key?(:foo)
#=> false
hash
#=> {}
hash[:foo]
#=> :bar
hash
#=> { :foo => :bar }
Ruby 2.2 didn't have this optimization. It just returned #table[mid] without checking #table.key? first.

Ruby: How to make custom DSL accept variables

I have this class:
class Items
def initialize &block
(block.arity < 1 ? (instance_eval &block) : block.call(self)) if block_given?
end
def button_id button_id=nil
unless #button_id.present?
raise "button_id must be supplied" if button_id.nil?
#button_id = button_id
end
#button_id
end
end
Now, when I do this it works:
Items.new do
button_id 1
end
But when I do this, it fails because I think it is not on the same scope:
#button = Button.find(params[:button_id]
Items.new do
button_id #button.id
end
How can fix this to take arguments outside the scope?
Thanks!
Try this:
class Items
def self.dsl
new.tap do |item|
yield item
end
end
def button_id(button_id)
#button_id = button_id
end
end
#button = Button.find(params[:button_id])
item = Items.dsl do |item|
item.button_id(#button.id)
end
puts item.inspect
Turns out all I needed to do was to pass the arguments to the block like this:
Items.new do |item|
item.button_id #button.id
end
Less beautiful DSL but works.
I don't think this is the right use case of DSL, when you can simply assign the attributes by arguments.
class Item
attr_accessor: :button_id
def initialize(args)
button_id = args[:button_id]
end
end
Another problem is in your usage. The instance would be of little value if you don't assign it to a variable
item = Item.new button_id: button_id

Ruby Challenge - Method chaining and Lazy Evaluation

After reading the article http://jeffkreeftmeijer.com/2011/method-chaining-and-lazy-evaluation-in-ruby/, I started looking for a better solution for method chaining and lazy evaluation.
I think I've encapsulated the core problem with the five specs below; can anyone get them all passing?
Anything goes: subclassing, delegation, meta-programming, but discouraged for the latter.
It would be favourable to keep dependencies to a minimum:
require 'rspec'
class Foo
# Epic code here
end
describe Foo do
it 'should return an array corresponding to the reverse of the method chain' do
# Why the reverse? So that we're forced to evaluate something
Foo.bar.baz.should == ['baz', 'bar']
Foo.baz.bar.should == ['bar', 'baz']
end
it 'should be able to chain a new method after initial evaluation' do
foobar = Foo.bar
foobar.baz.should == ['baz', 'bar']
foobaz = Foo.baz
foobaz.bar.should == ['bar', 'baz']
end
it 'should not mutate instance data on method calls' do
foobar = Foo.bar
foobar.baz
foobar.baz.should == ['baz', 'bar']
end
it 'should behave as an array as much as possible' do
Foo.bar.baz.map(&:upcase).should == ['BAZ', 'BAR']
Foo.baz.bar.join.should == 'barbaz'
Foo.bar.baz.inject do |acc, str|
acc << acc << str
end.should == 'bazbazbar'
# === There will be cake! ===
# Foo.ancestors.should include Array
# Foo.new.should == []
# Foo.new.methods.should_not include 'method_missing'
end
it "should be a general solution to the problem I'm hoping to solve" do
Foo.bar.baz.quux.rab.zab.xuuq.should == ['xuuq', 'zab', 'rab', 'quux', 'baz', 'bar']
Foo.xuuq.zab.rab.quux.baz.bar.should == ['bar', 'baz', 'quux', 'rab', 'zab', 'xuuq']
foobarbaz = Foo.bar.baz
foobarbazquux = foobarbaz.quux
foobarbazquuxxuuq = foobarbazquux.xuuq
foobarbazquuxzab = foobarbazquux.zab
foobarbaz.should == ['baz', 'bar']
foobarbazquux.should == ['quux', 'baz', 'bar']
foobarbazquuxxuuq.should == ['xuuq', 'quux', 'baz', 'bar']
foobarbazquuxzab.should == ['zab', 'quux', 'baz', 'bar']
end
end
This is inspired by Amadan's answer but uses fewer lines of code:
class Foo < Array
def self.method_missing(message, *args)
new 1, message.to_s
end
def method_missing(message, *args)
dup.unshift message.to_s
end
end
Trivial, isn't it?
class Foo < Array
def self.bar
other = new
other << 'bar'
other
end
def self.baz
other = new
other << 'baz'
other
end
def bar
other = clone
other.unshift 'bar'
other
end
def baz
other = clone
other.unshift 'baz'
other
end
end
The to_s criterion fails because 1.9 has changed the way Array#to_s works. Change to this for compatibility:
Foo.baz.bar.to_s.should == ['bar', 'baz'].to_s
I want cake.
BTW - metaprogramming here would cut down the code size and increase flexibility tremendously:
class Foo < Array
def self.method_missing(message, *args)
other = new
other << message.to_s
other
end
def method_missing(message, *args)
other = clone
other.unshift message.to_s
other
end
end

Resources