I'm trying to make a function that takes 2 arguments from the user_controller and does a few queries and calculations and return the result in an array. Is this something I should set up in a helper file? And how would I return the results (I'm very new to this).
Thanks for all and any help :)
Unless those calculations are entirely related to your templates (html, js) you should NEVER put that stuff in helpers of any kind. It will make your app hard to test.
Your options:
1) Create a model without ActiveRecord (MyCalculations.rb in ˜/models or ~/lib)
2) Create an extension and include in existing models
Examples (sample code, not realistic)
calculator.rb in ~/models
class Calculator
attr_accessor :amount, :parcs, :interest, :change
#... lots of code
def initialize(amount, parcs, interest)
# do stuff
calculate
end
def self.calculate!(amount, parcs, interest)
Calculator.new(amount, parcs, interest)
end
end
Extensions:
~/lib/models/import/csv_ext.rb
module Models
module Import
module CsvExt
extend ActiveSupport::Concern
included do
end
module ClassMethods
#static
def load_from_csv(csv)
# code comes here
end
end
end
end
end
Then, add it to your models:
include Models::Import::CsvExt
Related
I am just getting my hands on Concerns in Rails and try to implement a simple logging for ActiveRecord classes. In there I want to define the field that should go into the log and have the log written automatically after save.
What I have is this:
#logable.rb (the concern)
module Logable
extend ActiveSupport::Concern
#field = nil
module ClassMethods
def set_log_field(field)
#feild = field
end
end
def print_log
p "LOGGING: #{self[#index.to_s]}"
end
end
#houses.rb (the model using the concern)
class House < ActiveRecord::Base
include Logable
after_save :print_log
set_log_field :id
end
Unfortunately the call to set_log_field does not have an effect - or rather the given value does not make it to print_log.
What am I doing wrong?
Thanks for your help!
You probably mean this (btw, why not Loggable?):
# logable.rb
module Logable
extend ActiveSupport::Concern
# Here we define class-level methods.
# Note, that #field, defined here cannot be referenced as #field from
# instance (it's class level!).
# Note also, in Ruby there is no need to declare #field in the body of a class/module.
class_methods do
def set_log_field(field)
#field = field
end
def log_field
#field
end
end
# Here we define instance methods.
# In order to access class level method (log_field), we use self.class.
included do
def print_log
p "LOGGING: #{self.class.log_field}"
end
end
end
Update You also asked about what's the difference between methods in included block and those within method body.
To make a short resume there is seemingly no difference. In very good approximation you can consider them the same. The only minor difference is in dependency management. Great illustration of it is given in the end of ActiveSupport::Concern documentation. It worth reading, take a look!
This extension create cache_find method for all models of app (I've create this using this post).
config/active_record_extension.rb
require 'active_support/concern'
module ActiveRecordExtension
extend ActiveSupport::Concern
# add your instance methods here
def flush_find
Rails.cache.delete([self.class.name, :cached_find, id])
end
included do
after_commit :flush_find
end
module ClassMethods
def cached_find id
Rails.cache.fetch([self.name, :cached_find, id]) { self.find(id) }
end
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
I turned this code into a gem and added to this repo.
So I want to add this methods dynamically, something like this:
class User << ActiveRecord::Base
# id, name, email, age...
cached :find, :find_by_name, :find_by_email
end
and the above code should generate cached_find, flush_find, cached_find_by_name, flush_find_by_name... You get it.
I need help to:
Test Rails.cache methods in model_caching gem.
Create code to dynamically add methods to app models based on cached method arguments.
Some links that helped me but do not meet all:
https://github.com/radar/guides/blob/master/extending-active-record.md
http://railscasts.com/episodes/245-new-gem-with-bundler
http://guides.rubyonrails.org/plugins.html
Fell free to clone and improve gem code.
You don't have to hack ActiveRecord::Base. You can add what Marc-Alexandre said right into your concern, like so:
module ActiveRecordExtension
extend ActiveSupport::Concern
...
module ClassMethods
def cached(*args)
define_method "cached_#{arg.to_s}" do
# do whatever you want to do inside cached_xx
end
define_method "flush_#{arg.to_s}" do
# do whatever you want to to inside flush_xx
end
end
end
end
Also, I would not auto include the extension directly in ActiveRecord, I think it's better to explicitly include it in the models you are going to use it.
To add code dynamically you need to hack the ActiveRecord::Base class. In another file (you usually put in lib/core_ext) you could do as follow :
ActiveRecord::Base.class_eval do
def self.cached(*args)
args.each do |arg|
define_method "cached_#{arg.to_s}" do
# do whatever you want to do inside cached_xx
end
define_method "flush_#{arg.to_s}" do
# do whatever you want to to inside flush_xx
end
end
end
end
What it does basically is takes all your arguments for cached (:find, :find_by_name, etc) and define the two methods (cache_find, cache_find_by_name) and flush_find, .. etc)
Hope this helps !
I have two models which need an identical function. I'd like to learn how to make this DRY the right rails way...
For both models I have:
before_save :assign_uuid
Which in each model has:
def assign_uuid
if self.uuid.nil?
self.uuid = ActiveSupport::SecureRandom.hex(32)
end
end
Since, assign_uuid lives in both models, Where is the one place I should place this func? Also, in the models, where it say's before_save. How do I call the assign_uuid in the location it is located?
Thanks
I'm no Ruby expert, so I'm not sure if this is frowned upon or not, but if I were you, I'd chuck it in a file in lib/ and whack it straight on ActiveRecord::Base with class_eval.
ActiveRecord::Base.class_eval do
def assign_uuid
if self.uuid.nil?
self.uuid = ActiveSupport::SecureRandom.hex(32)
end
end
end
That way, it's available for all your models. Either that, or create a miscellaneous model helpers file and include the module into the models you'd like.
# lib/misc_model_helpers.rb
module MiscModelHelpers
def assign_uuid
if self.uuid.nil?
self.uuid = ActiveSupport::SecureRandom.hex(32)
end
end
end
# models/person.rb
class Person < ActiveRecord::Base
include MiscModelHelpers
before_save :assign_uuid
end
Again, I'm really not 100% on the most rubyish way of doing this. It's probably something completely different. These are just two ways that work for me!
In your lib folder, add a file uuid_lib.rb, and write
module UuidLib
def assign_uuid
if self.uuid.nil?
self.uuid = ActiveSupport::SecureRandom.hex(32)
end
end
end
and inside your model write
include UuidLib
before_save :assign_uuid
An example article explaining modules and mixins in more detail can be found here.
You should add this as a module and mix it into your models, that is the Ruby way to do what you are after.
I have this method (50+ lines) in one of my models, I prefer not having to scroll so much and not losing my cursor sometimes because of its taking up so much space. I wonder if I can put it away in a separate file and sort of include it in the model instead.
Thanks!
You can put it into a module and include it (mix it in) to your model class. For example:
app/models/my_long_method.rb
module MyLongMethod
def my_long_method
....
end
end
app/models/my_class.rb
class MyClass < ActiveRecord::Base
include MyLongMethod
end
If your method really is that long though you might want to consider breaking it down into smaller sections as methods in that module too.
module MyLongMethod
def my_long_method
first_part
second_part
end
private
def first_part
...
end
def second_part
...
end
end
In an effort to reduce code duplication in my little Rails app, I've been working on getting common code between my models into it's own separate module, so far so good.
The model stuff is fairly easy, I just have to include the module at the beginning, e.g.:
class Iso < Sale
include Shared::TracksSerialNumberExtension
include Shared::OrderLines
extend Shared::Filtered
include Sendable::Model
validates_presence_of :customer
validates_associated :lines
owned_by :customer
def initialize( params = nil )
super
self.created_at ||= Time.now.to_date
end
def after_initialize
end
order_lines :despatched
# tracks_serial_numbers :items
sendable :customer
def created_at=( date )
write_attribute( :created_at, Chronic.parse( date ) )
end
end
This is working fine, now however, I'm going to have some controller and view code that's going to be common between these models as well, so far I have this for my sendable stuff:
# This is a module that is used for pages/forms that are can be "sent"
# either via fax, email, or printed.
module Sendable
module Model
def self.included( klass )
klass.extend ClassMethods
end
module ClassMethods
def sendable( class_to_send_to )
attr_accessor :fax_number,
:email_address,
:to_be_faxed,
:to_be_emailed,
:to_be_printed
#_class_sending_to ||= class_to_send_to
include InstanceMethods
end
def class_sending_to
#_class_sending_to
end
end # ClassMethods
module InstanceMethods
def after_initialize( )
super
self.to_be_faxed = false
self.to_be_emailed = false
self.to_be_printed = false
target_class = self.send( self.class.class_sending_to )
if !target_class.nil?
self.fax_number = target_class.send( :fax_number )
self.email_address = target_class.send( :email_address )
end
end
end
end # Module Model
end # Module Sendable
Basically I'm planning on just doing an include Sendable::Controller, and Sendable::View (or the equivalent) for the controller and the view, but, is there a cleaner way to do this? I 'm after a neat way to have a bunch of common code between my model, controller, and view.
Edit: Just to clarify, this just has to be shared across 2 or 3 models.
You could pluginize it (use script/generate plugin).
Then in your init.rb just do something like:
ActiveRecord::Base.send(:include, PluginName::Sendable)
ActionController::Base.send(:include, PluginName::SendableController)
And along with your self.included that should work just fine.
Check out some of the acts_* plugins, it's a pretty common pattern (http://github.com/technoweenie/acts_as_paranoid/tree/master/init.rb, check line 30)
If that code needs to get added to all models and all controllers, you could always do the following:
# maybe put this in environment.rb or in your module declaration
class ActiveRecord::Base
include Iso
end
# application.rb
class ApplicationController
include Iso
end
If you needed functions from this module available to the views, you could expose them individually with helper_method declarations in application.rb.
If you do go the plugin route, do check out Rails-Engines, which are intended to extend plugin semantics to Controllers and Views in a clear way.