Let's say I have the following method:
def run
#users.each do |u|
..
..
end
end
I have a lot of code in run so I am trying to refactor it and splitting it up into smaller methods. One of these methods is the following:
def finish_mutation
..
..
Rails.logger.info "Succesfully added #{u.name}"
end
This breaks because finish_mutation doesn't have access to the u variable. How can I create new methods that can access the u variable that I created in run?
You can simply create method taking parameter:
def finish_mutation(user)
# code
Rails.logger.info "Successfully added #{user.name}"
end
and call it, passing User instance:
finish_mutation(u)
it's easy to do you just add a parameter to your finish_mutation method like this :
def finish_mutation(param)
# .......
end
then you call your function like this :
def run
#users.each do |u|
..
..
finish_mutation(u) # <----- for example here you call your method
end
end
Sometimes passing your loop variable (as shown in the other answers) is the best answer. Sometimes you can DRY things up better by adding a method to whatever class 'u' is an instance of. So you might do
class User
def finish_mutation
# put your operation here
end
end
And then in your loop
u.finish_mutation
Obviously you need to think about which is the best way for a specific case.
Related
I try to find how to best way implement chain of methods in Ruby( Rails application)
like this
class Foo
attr_accessor :errors
attr_accessor :shared_params
def initialize(..)
..
end
def call
check_params
calc_smthing
write_in_db
end
prviate
def check_params
..
end
def calc_smthing
..
end
def write_in_db
..
end
end
main ideas:
if something fail( or not fail but return false) in some step others steps doesnt call,
of course I dont want to add multiple ifs to check state and think about how implement it in one place
I need to save errors
It would be great to find way to share params between methods.
I can write in simple( ugly) way with ifs
but I try to find more elegant way or pattern because problem not so specific I think.
Thanks in advance.
Save errors and shared attributes to variables (how you do it now). Unite methods to chain of methods:
def call
check_params && calc_smthing && write_in_db
end
def valid?
errors.blank?
end
Been told to use memoization in my code not to call the function over and over. Is my implementation best way to use it? It seems redundant. Please advise how could I get rid of the initialize function.
class OrderService
def initialize
#current_orders = current_orders
end
def orders_acceptance
#current_orders.
with_statuses(:acceptance).
select do |order|
order.acceptance? if order.shopper_notified_at?
end
end
def orders_start
#current_orders.
with_statuses(:start).
select do |order|
order.start?
end
end
private
def current_orders
#current_orders ||= begin
Order.includes(:timestamps).
with_statuses(
[
:acceptance,
:start
]
)
end
end
end
2 tips:
Don't call current_orders directly in constructor. Orders should be loaded for the first time when you're calling either orders_start or orders_acceptance. There is always a risk someone initializes this service early when request processing starts but because of some business rules neither of those methods is run. In that case - you called db but never consumed the result.
In both orders_acceptance and orders_start you're using #current_orders instance variable. It's ok but it's perfectly fine if you just call current_orders method multiple times - result is the same.
You don't need to do anything in the initializer.
The first time current_orders is called, the value will be memoized, which is "standard" memoiziation.
Also, as Mohammad pointed out, your method is returning an AR relation. The method should be:
#current_orders ||= Order.includes(:timestamps)
.with_statuses([:acceptance,:start])
.to_a
end
Also, why would you want to memoize this? current_orders should be current, not stale.
I am rather new to Rails, and would greatly appreciate any bit of help. I have created the following method:
def name_fix
name = self.split
mod_name = []
name.each do |n|
n.split("")
if n[0]
n.upcase
else
n.downcase
end
mod_name.push(n)
end
mod_name.join
end
I would like to use this method in my Controller as such:
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name].name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
How can I go about accomplishing this? Will this method reside within my Model or Controller? Previously, I've run into an undefined method error.
Note: I'm sure that there is a way to better write my code. I am grateful for help with that as well.
#app/models/patient.rb
class Patient < ActiveRecord::Base
protected
def name=(value)
mod_name = []
value.split.each do |n|
n.split("")
type = n[0] ? "up" : "down"
n.send("#{type}case")
mod_name.push(n)
end
#name = mod_name.join
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient = Patient.new patient_params
#patient.save ? redirect_to(patients_path) : render(:new)
end
private
def patient_params
params.require(:patient).permit(:name)
end
end
What you're doing is trying to override the setter method, which can be done using the above code. Much more efficient and out of the way.
I have created the following method
Since you're new, let me explain something else.
It is important to note where you're using this method.
You've currently put it in the model, which means you'll have to call it to manipulate some attribute / functionality of any object created with said model.
--
Models - in Rails - build the objects which populate your app. Ruby is an object orientated language, which means that every element of your program should revolve around data objects in some degree.
As you can see above, the method of building objects in your system is really about invoking classes. These classes contain methods which can be called, either at class level (IE invoking the class through the method), or at instance level (IE calling a method on an already invoked object).
This is where you get "class" methods (Model.method) and "instance" methods (#model.method) from:
#app/models/patient.rb
class Patient < ActiveRecord::Base
def explode
#this is an instance method
puts "Instance Explode"
end
def self.explode
#this is a class method
puts "Exploded"
end
end
Thus you can call the following:
#patient = Patient.find params[:id]
#patient.explode #-> "Instance explode"
Patient.explode #-> "Exploded"
--
This is important because it gives you a strict framework of where you should, and shouldn't use methods in your models.
It explains why you have controllers & helpers, and allows you to formulate the best way to structure your application as to get the most out of the least code.
For example...
Your use of #patient.name = params[:params][:name].name_fix is incorrect.
It's wrong because you're calling the instance method .name_fix on a piece of data totally unrelated to your model. If you wanted to use .name_fix in a general sense like this, you'd probably use a helper:
#app/helpers/patients_helper.rb
class PatientsHelper
def name_fix value
# stuff here
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient.name = name_fix params[:patient][:name]
end
end
Since you're using the method to populate the .name attribute of your model, it makes sense to override the name= setter. This will not only provide added functionality, but is much smoother and efficient than any other way.
Methods that are called directly are best put in the Controller (or in ApplicationController if you think more than one controller might want to use it).
These are methods like
# app/controllers/my_controller.rb
def foo(bar)
# do something here
end
def create
id = params[:id]
value = foo(id)
end
If you want a chained method that acts as a property method of whatever you're calling it on. Those are characteristic of how Models work - you have your main model and you call attributes or methods on the instance of that model.
# app/models/my_model.rb
def full_name
first_name + " " + last_name
end
# app/controller/my_controller.rb
def create
id = params[:id]
model = MyModel.find(id)
full_name = model.full_name
end
In your case, you want to call name_fix ON whatever is returned by params[:params][:name], which is (I'm guessing) a String.
You have two options
Modify the String class to define a method named name_fix. I highly recommend against this. It's call "monkeypatching" and shouldn't be done without good reason. Just letting you know you can do it in some cases.
Use a direct method in your controller or ApplicationController like the first example above.
#patient.name = name_fix(params[:params][:name])
Edit: As for your request about a better way to write your code... that's difficult to teach or convey in one answer. I'd say read some open source projects out there to see how people write Ruby and some common idioms used to clean up the code. To get you started, here's how I'd re-write your code
def create
#patient = Patient.new(params[:patient])
# 1. Be descriptive with your method names. `name_fix` is vague
# 2. Why is `:name` nested under another `[:params]` hash?
#patient.name = capitalize_name(params[:name])
if #patient.save
# 1. I think `patient_path` has to be singular
# 2. It needs a `Patient` object to know how to construct the URL
# e.g. `/patients/:id`
redirect_to patient_path(#patient)
else
render :new
end
end
def capitalize_name(full_name)
# Example: julio jones
#
# 1. `split` produces an array => ["julio", "jones"]
# 2. `map` applies a function (`capitalize`) to each element
# => ["Julio", "Jones"]
# 3. `join(" ")` rejoins it => "Julio Jones"
full_name.split.map(&:capitalize).join(" ")
end
Assuming your goal with the name_fix method is just to capitalize the first letter of each name, you could just pass name as an argument and store it as a private method on the Controller:
# app/controllers/patient_controller.rb
private
def name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do
#patient.name = name_fix(params[:params][:name])
in the create method.
OR, you could store this method in the model:
# app/models/patient.rb
def self.name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do this instead, in the controller:
#patient.name = Patient.name_fix(params[:params][:name])
I would also suggest renaming your name_fix method to something like capitalize_name.
update your create method as below
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name]
#patient = #patient.name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
It should work.
I'm trying total up all "amount" columns with a definition in the model like so:
def self.total
self.all.collect(&:amount).sum
end
With that, "Recipe.total" works as expected. However, I'm using a plugin that passes "Recipe.find(:all)", and I can't seem to pass that to the method to find the total. That is:
Recipe.find(:all).total # doesn't work
Is there a way to define the method in my model differently to make Recipe.find(:all).total work like Recipe.total?
You can write your method as:
def self.total
self.sum(:amount)
end
And then you can use it also with named scopes:
Recipe.total # without any scopes
Recipe.my_custom_named_scope.total # with your custom named scope
Another variant is to override find method for that model:
def self.find(*args)
result = super
if args[0] && args[0] == :all
def result.total
self.sum(&:amount)
end
end
result
end
Then you get exactly what you want, you'll be able to write Recipe.find(:all).total.
Check out the Calculation Module
It has methods for: sum,average,count, etc ...
Its baked into ActiveRecord.
So you would want to write:
Recipe.sum(:total)
Have fun!
I have a Rails app that repeatedly talks to another Web server through a wrapper, and I'd like to stick the wrapper in a Singleton class so it's not recreated for every request. Easy enough, I thought:
class AppWrapper < Wrapper
include Singleton
end
...
wrapper = AppWrapper.instance "url"
Only it doesn't work:
wrong number of arguments (0 for 1)
/usr/lib/ruby/1.8/singleton.rb:94:in `initialize'
/usr/lib/ruby/1.8/singleton.rb:94:in `new'
/usr/lib/ruby/1.8/singleton.rb:94:in `instance'
Wrapper.initialize needs an argument, and apparently it's not getting passed through, since line 94 in question says
#__instance__ = new # look Ma, no argument
How do I work around this? Redefining initialize in AppWrapper doesn't seem to help, and
mucking around with Wrapper to separate "set URL" from "initialize" seems suboptimal.
Passing argument to singleton
class Parameterized_Singleton
def initialize(a)
#pdf = a
puts #pdf
end
def self.instance(p)
begin
##instance =Parameterized_Singleton.new(p)
private_class_method :new
rescue NoMethodError
# return ##instance # or you can return previous object
puts "Object Already Created"
exit
end
return ##instance
end
def scanwith(b)
puts "scan"
end
def show_frequence_distribution
puts "fd"
end
def show_object_number(a)
puts "no"
end
end
Parameterized_Singleton.instance(20).show_object_number(10)
Parameterized_Singleton.instance(10).show_object_number(20)
Are you sure you need a singleton and not a factory . Refer this
I asked this question while I was still getting my head around Ruby, and it seems so naive now. The easy solution is to just store the Wrapper object in a member variable and use ||= to initialize it only if it hasn't been set yet:
class WrapperUserClass
def initialize
#wrapper = nil # Strictly speaking unnecessary, but it's a bit clearer this way
end
def wrapper
#wrapper ||= Wrapper.new(foobar)
end
def do_something
wrapper.booyakasha
end
end
Since you mention something about editing Wrapper as a solution, can't you just use Wrapper directly and do this?
class Wrapper; include Singleton; end
If not, you could use something like this, which will just make sure AppWrapper.new isn't called more than once:
class AppWrapper
def self.new(*args)
class << app_wrapper = Wrapper.new(*args)
include Singleton
end
app_wrapper
end
end
If you need the singleton "Klass.instance" method, you'll have to take either take out the parameter in Wrapper#initialize, or just redefine Singleton#instance to take arguments optionally and passes them to the call to new on line 94.