Refactoring a switched block statement for group_by in Ruby on Rails - ruby-on-rails

How can I rewrite this code so it's completely dynamic, and I don't have to use the case clause to manually list all possible values of #group?
# Grouping
#group = params[:group] if !params[:group].blank?
case #group
when 'category_id'
#ideas_grouped = #ideas.group_by { |i| i.category_id }
when 'status_id'
#ideas_grouped = #ideas.group_by { |i| i.status_id }
when 'personal_bias'
#ideas_grouped = #ideas.group_by { |i| i.personal_bias }
when 'business_value'
#ideas_grouped = #ideas.group_by { |i| i.business_value }
end

You can use some sort of meta programming
Above code can be refactored in one of the way is
if params[:group].present? && ["category_id","status_id","personal_bias","business_value"].include?(params[:group])
#ideas_grouped = #ideas.group_by { |i| i.send(params[:group]) }
end

If you need no white-listing:
#ideas_grouped = if (group = params[:group]).present?
#ideas.group_by(&group.to_sym)
end
If you need white-listing you may call include? first (see Amar's answer), but to add something new, let me push it with a declarative approach (Object#whitelist is left as an exercise for the reader, maybe comes from Ick):
#ideas_grouped = params[:group].whitelist(IdeaGroupers).maybe do |group|
#ideas.group_by(&group.to_sym)
end

Try out this:
#ideas_grouped = #ideas.group_by { |i| i.send(:"#{#group}")} if (#group = params[:group])

what about :
#group = params[:group] if !params[:group].blank?
#ideas_grouped = ideas_hash.fetch(#group)
def ideas_hash
{
'category_id' => ideas_by_category_id,
'status_id' => ideas_by_status_id,
'personal_bias' => ideas_by_personal_bias
'business_value' => ideas_by_business_value
}
end
def ideas_by_category_id
#ideas.group_by { |i| i.category_id }
end
def ideas_by_status_id
#ideas.group_by { |i| i.status_id }
end
def ideas_by_personal_bias
#ideas.group_by { |i| i.personal_bias }
end
def ideas_by_business_value
#ideas.group_by { |i| i.business_value }
end
I would also make ideas_hash and all of the others method private.

Okay, the last time I touched Ruby is too far back so I cannot give you an example. As far as I understand your problem you are doing a mapping (group -> accessor method) there. So either you use a map object or you build a mapping function using a lambda.

Related

Merge a hash inside array

I am new to Ruby and Rails, I have stuck in a situation that I need to create an array of hashes. Please see below the code:
def self.v_and_c items
result = []
items.try(:each) do |item|
result << item
if item.is_parent_variation
check_ancestor item
result << { :item_variation => #variations }
result << { :options => #options }
elsif item.is_parent_customization
check_ancestor item
result << { :customizations => #customizations }
result << { :ingredients => #ingredients }
end
end
result
end
Here is the output of the function:
{"items":[{"id":1,"name":"Cake"},{"item_variation":null},{"options":null}]}
But I wanted to do like this.
{"items":[{"id":1,"name":"Cake","item_variation":null, "options":null} ]}
You could try something like this:
def self.v_and_c items
result = []
items.try(:each) do |item|
item_hash = {}.merge(item)
if item.is_parent_variation
check_ancestor item
item_hash.merge!({ item_variation: #variations }).merge!({ options: #options})
elsif item.is_parent_customization
check_ancestor item
item_hash.merge!({ customizations: #customizations }).merge!({ ingredients: #ingredients})
end
result.push(item_hash)
end
result
end
Explanation:
For each iteration of loop, create an item_hash and merge all the requisite hashes in it and then push the resulting hash into the result array.
Few pointers:
Take care of new Ruby hash syntax
If check ancestor is needed in both if and else why not do it outside?
It should be simple like this, use .merge method
def self.v_and_c items
result = []
items.try(:each) do |item|
result << item
if item.is_parent_variation
check_ancestor item
result = result.merge { :item_variation => #variations }
result = result.merge { :options => #options }
elsif item.is_parent_customization
check_ancestor item
result = result.merge { :customizations => #customizations }
result = result.merge { :ingredients => #ingredients }
end
end
result
end
def self.v_and_c items
[].tap do |result|
items.try(:each) do |item|
result_hash = item.dup
if item.is_parent_variation
check_ancestor item
result_hash.merge!({ item_variation: #variations, options: #options })
elsif item.is_parent_customization
check_ancestor item
result_hash.merge!({ customizations: #customizations, ingredients: #ingredients })
end
result << result_hash
end
end
end

Change Json output, return `icd3_code_id` as `:id`

At the time my controller looks like this:
def search
#icd4 = Icd4Code.search_full(params[:search]).first(20)
render json: { icd: #icd4.as_json(:only => [:bezeichnung, :nummer, :id])}
end
What i would like to change is that my code does not return #icd4.id as :id but instead #icd4.icd3_code_id as :id
So render json: { icd: #icd4 } would look like this:
{"icd":[{"id":6,"nummer":"A00.1","bezeichnung":"Cholera","icd3_code_id":3,"created_at":"2014-02-28T19:38:20.530Z","updated_at":"2014-02-28T19:38:20.530Z"},{"id":7,"nummer":"A00.1","bezeichnung":"El-Tor-Cholera","icd3_code_id":3,"created_at":"2014-02-28T19:38:20.533Z","updated_at":"2014-02-28T19:38:20.533Z"}]}
My actual code render json: { icd: #icd4.as_json(:only => [:bezeichnung, :nummer, :id])} returns this:
{"icd":[{"id":6,"nummer":"A00.1","bezeichnung":"Cholera"},{"id":7,"nummer":"A00.1","bezeichnung":"El-Tor-Cholera"}]}
And i would like this output:
{"icd":[{"id":3,"nummer":"A00.1","bezeichnung":"Cholera"},{"id":7,"nummer":"A00.1","bezeichnung":"El-Tor-Cholera"}]}
How can i achieve this? Thanks
Without a serializer you can iterate through the items and their keys and rename the key when you find yours.
#icd4 = Icd4Code.search_full(params[:search]).first(20)
data = #icd4.as_json(:only => [:bezeichnung, :nummer, :icd3_code_id]).tap do |icd4_json|
icd4_json.each do |icd4_item|
icd4_item.each do |key|
icd4_item[ 'id' ] = icd4_item.delete( key ) if key == 'icd3_code_id'
end
end
end
render json: { icd4: data }
You should definitely take a look at draper and active_model_serializers gems.
http://railscasts.com/episodes/409-active-model-serializers
http://railscasts.com/episodes/286-draper
although I use Draper in a bit different way then Ryan Bates does, usually I do something like this:
render json: item.decorate.as_json
for example as a simplest solution you could have this class:
class Icd4CodeDecorator < Draper::Decorator
decorates :icd4_code
delegate_all
def as_json(options={})
{
id: icd3_code_id,
bezeichnung: bezeichnung,
nummer: nummer
}
end
end
and then in your controller you could just do:
render json: #icd4.decorate.as_json
Although I think it would be better to keep things correct in as_json method and have id value returned for id property and create a decorator class inherited from Draper::CollectionDecorator and define there your custom method, something like:
class Icd4CodesDecorator < Draper::CollectionDecorator
def as_search_json
object.map do |o|
{
id: icd3_code_id,
bezeichnung: bezeichnung,
nummer: nummer
}
end
end
end
and then in your controller you could do:
render json: Icd4CodesDecorator.new(#icd4).as_search_json
This way you can easily create and maintain any number of versions of json output for your models.
The simplest way is to cleverly change the value something like this
def search
#icd4 = Icd4Code.search_full(params[:search]).first(20)
r = icd: #icd4.as_json(:only => [:bezeichnung, :nummer, :icd3_code_id])
final_value = []
r["icd"].each do |h|
f = {}
h.map do |k,v|
if k == 'icd3_code_id'
f["id"] = v
else
f[k] = v
end
end
final_value << f
end
render json: final_value
end

How do I stub a method of an instance only if a specific instance variable has a value?

I have an object MyObject:
class MyObject
def initialize(options = {})
#stat_to_load = options[:stat_to_load] || 'test'
end
def results
[]
end
end
I want to stub the results method only if stat_to_load = "times". How can I do that? I tried:
MyObject.any_instance.stubs(:initialize).with({
:stat_to_load => "times"
}).stubs(:results).returns(["klala"])
but it does not work. Any idea?
So, I think there is probably a simpler way to test what you're trying to test, but without more context I don't know what to recommend. However, here is some proof-of-concept code to show that what you want to do can be done:
describe "test" do
class TestClass
attr_accessor :opts
def initialize(opts={})
#opts = opts
end
def bar
[]
end
end
let!(:stubbed) do
TestClass.new(args).tap{|obj| obj.stub(:bar).and_return("bar")}
end
let!(:unstubbed) { TestClass.new(args) }
before :each do
TestClass.stub(:new) do |args|
case args
when { :foo => "foo" }
stubbed
else
unstubbed
end
end
end
subject { TestClass.new(args) }
context "special arguments" do
let(:args) { { :foo => "foo" } }
its(:bar) { should eq "bar" }
its(:opts) { should eq({ :foo => "foo" }) }
end
context "no special arguments" do
let(:args) { { :baz => "baz" } }
its(:bar) { should eq [] }
its(:opts) { should eq({ :baz => "baz" }) }
end
end
test
special arguments
bar
should == bar
opts
should == {:foo=>"foo"}
no special arguments
bar
should == []
opts
should == {:baz=>"baz"}
Finished in 0.01117 seconds
4 examples, 0 failures
However I'm making a lot of use of special subject/let context blocks here. See http://benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks/ for more on that subject.
Try out below, this should work as expected:
Here, Basically we are actually stubbing new instance getting created and also stubbing results method of the instance which is getting returned.
options = {:stat_to_load => "times"}
MyObject.stubs(:new).with(options)
.returns(MyObject.new(options).stubs(:results).return(["klala"]))
You could use plain old Ruby inside your test to achieve this.
MyObject.class_eval do
alias_method :original_results, :results
define_method(:results?) do
if stats_to_load == "times"
["klala"]
else
original_results
end
end
end

How can I refactor these common controller methods?

I have a few controller methods that are extremely similar and I was wondering what the best way to refactor them would be. First thing that comes to mind would be somehow passing in two blocks to a helper method, but I'm not sure how to do that either.
def action_a
if #last_updated.nil?
#variable_a = #stuff_a
else
#variable_a = (#stuff_a.select{ |item| item.updated_at > #last_updated }
end
end
def action_b
if #last_updated.nil?
#variable_b = #stuff_b.some_method
else
#variable_b = #stuff_b.some_method.select{ |stuff| item.updated_at > #last_updated }
end
end
It just seems like I'm constantly checking if #last_updated is nil (I set the #last_updated instance variable in a before_filter. If I could somehow pass the stuff inside the if as a block and the stuff in the else as another block, then I could remove the if #last_updated.nil? duplication?
What is the best way of accomplishing this for many methods?
Update
Where I specify #stuff_a and #stuff_b, they are always returning an array (since I use .select).
Take a look at this. It's DRYer and should yield identical results.
def action_a
do_the_processing :"#variable_a", #stuff_a
end
def action_b
do_the_processing :"#variable_b", #stuff_b.some_method
end
private
def do_the_processing var_name, collection
if #last_updated.nil?
instance_variable_set var_name, collection
else
instance_variable_set var_name, collection.select{ |item| item.updated_at > #last_updated }
end
end
Update
And here's the two blocks approach (just for fun) (uses 1.9's stabby lambda syntax)
def action_a
check_last_updated is_nil: -> { #variable_a = #stuff_a },
is_not_nil: -> { #variable_a = (#stuff_a.select{ |item| item.updated_at > #last_updated } }
end
def action_b
check_last_updated is_nil: -> { #variable_b = #stuff_b.some_method },
is_not_nil: -> { #variable_b = #stuff_b.some_method.select{ |stuff| item.updated_at > #last_updated } }
end
private
def check_last_updated blocks = {}
if #last_updated.nil?
blocks[:is_nil].try(:call)
else
blocks[:is_not_nil].try(:call)
end
end
You need to extract your condition in a separate def block and use it later on:
def select_updates a
#last_updated.nil? ? a : a.select{ |item| item.updated_at > #last_updated }
end
def action_a; #variable_a = select_updates(#stuff_a) end
def action_b; #variable_b = select_updates(#stuff_b.some_method) end
AS I can see, you could do the followings
have two scope for each
Ex:
class Stuff < ActiveRecord::Base
scope :updated_at, lambda {|updated_date|
{:conditions => "updated_at > #{updated_date}"}
}
end
class Item < ActiveRecord::Base
scope :updated_at, lambda {|updated_date|
{:conditions => "updated_at > #{updated_date}"}
}
end
in your controller do this
def action_a
#variable_a = update_method(#stuff_a)
end
def action_b
#variable_b = update_method(#stuff_b)
end
private
def update_method(obj)
result = nil
if #last_updated.nil?
result = obj.some_method
else
result = obj.some_method.updated_at(#last_updated)
end
result
end
HTH

Active record select with array passed in

How would I show all tickets with status.name equal to the items in the array?
A single item with select seems to work like so
#project_tickets = #project.tickets.select { |m| m.status.name == "new"] }
but not with an array of items
params[:array_here] = ["new","implemented"]
#project = Project.find_by_id(params[:project_id])
#project_tickets = #project.tickets.select { |m| m.status.name == [params[:array_here]] }
The way your asking for is like this:
#project_tickets = #project.tickets.select { |m| params[:array_here].include? m.status.name }
But you should really be delegating this to the database.
#project_tickets = Ticket.includes(:status).where(:project_id => #project.id, :status => { :name => params[:array_here] })
Does not where work?
#project_tickets = #project.tickets.where("name = ?", params[:array_here])
#new_tickets=#project.tickets.select { |m| m.status.name == params[:array_here][0] }
#implemented_tickets=#project.tickets.select { |m| m.status.name == params[:array_here][1] }
params[:array_here] = ["new","implemented"]
#project = Project.find_by_id(params[:project_id])
#project_tickets = #project.tickets.select { |m| params[:array_here].include?(m.status.name) }

Resources