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.
Related
I would like to setup a before_create for all of my modules
what i have been trying is:
module ActiveRecord
module UserMonitor
require 'securerandom'
before_create :attach_uuid
def attach_uuid
self.uuid = SecureRandom.uuid.gsub("-","")
end
end
end
This does not seem to be working.
if i go into each module and add it in there it works, but i want to do it on a global scale.
Any thoughts or ideas on how i can achieve this in this manner? i know i could do it in triggers and such but i don't want to go that route and i would like to avoid hitting every module/class in case i need to change something.
Currently using Ruby 1.9.3 Can not currently upgrade my app until i make future code changes.
Thanks!
An other solution - I use, is to put the logic for UUID in an own module, that you include. I already have some (class-) methods I add to my AR, like set_default_if, so it was a good place for me.
module MyRecordExt
def self.included base
base.extend ClassMethods # in my case some other stuff
base.before_create :attach_uuid # now add the UUID
end
def attach_uuid
begin
self.uuid = SecureRandom.uuid
rescue
# do the "why dont we have a UUID filed?" here
end
end
# some other things not needed for add_uuid
module ClassMethods
include MySpecialBase # just an eg.
def default_for_if(...)
...
end
end
end
and then
class Articel < ActiveRecord::Base
include MyRecordExt
...
end
In general I avoid doing something for ALL models modifying AR base - I made the first bad experience with adding the UUID to all, and crashed with devise GEMs models ...
If you define attach_uuid in the ActiveRecord module, can't you just call the before_create :attach_uuid at the top of each controller? This is DRY.
Is there a UserMonitor controller that you could add it to?
class UserMonitor < ActiveRecord::Base
before_create :attach_uuid
end
i have the following function that i make use of in a lot of my models. i use MongoID for MongoDB wrapper
def make_slug
self.slug = self.name.downcase.gsub(/[^a-z1-9]+/, '').chomp('')
end
Which is the best place to place it than copy and paste it in all my models.
Also any recommendation for a good Slug Gem for Rails4?
All models are Inherited from ActiveRecord, you can open the eigenclass to add a singleton method there and use in all models.
The method I'll choose would be putting it under lib directory and require it in each model I need it.
I guess you could do a mixin/module, which you include in your models where you need the functionality. Like this:
Example of the module:
module SlugMaker
def make_slug
# Do your magic here
end
end
And then include it in your model:
class SuperAwesomeModel
include SlugMaker
def some_action
make_slug
end
end
I have a model that requires a ton of dependencies... it looks something like this:
after_create :create_dependencies
def create_dependencies
create_foo
create_bar
create_baz
# ...
end
This is really polluting my model class with a bunch of this nonsense. Is there another way I can go about this? I would love to use a form object or something like that, but I want to ensure all of these objects come with their dependent relationships no matter what, even when created through command line, in a test suite, etc.
My first reaction was to create a Form object like you mentioned (as described by Ryan Bates). However you're right that if you save a model directly none of the dependencies will be created.
One approach you could take is to refactor the dependency creation out into a separate class:
# lib/my_model_dependency_creator.rb
class MyModelDependencyCreator
def initialize(my_model)
#my_model = my_model
end
def create_dependencies
create_foo
create_bar
# etc
end
private
def create_foo
# create dependency associated with #my_model
end
def create_bar
end
end
Then in your model class:
...
after_create :create_dependencies
def create_dependencies
MyModelDependencyCreator.new(self).create_dependencies
end
First, any thought about observers?
Second, I guess it's not that hard to extract the code.
#include this module in your model
module AutoSaveDependency
def auto_save_dependencies *deps
##auto_save_dependencies = deps
end
def auto_save
##auto_save_dependencies.each {|dep| send "create_#{dep}" }
end
def self.included(model)
model.send :after_create, :auto_save
end
end
So in your model, you just include this module, and write auto_save_dependencies :foo, :bar, ...
It could be more complicated but I think it's doable
I have a very big function in my model and I want to store it somewhere else in order to keep my model dry. I read that storing methods in ApplicationHelper and then calling them from a model is a bad idea. What is a good idea then?
I want to have a separate file with my big methods and call them from a model.
You can create a "plain old ruby object (PORO)" to do your work for you. let's say you had a method that calculates the amount overdue for a user.
So, you can create app/services/calculates_overages.rb
class CalculatesOverages
def initialize(user)
#user = user
end
def calculate
# your method goes here
end
end
Then, you can:
class User < ActiveRecord::Base
def overage_amount
CaluclatesOverage.new(self).calculate
end
end
Or, in a controller you could:
def show
#amount = CaluclatesOverage.new(current_user).calculate
end
The app/services directory could also be app/models, or the lib directory. There's no set convention for this (yet).
Use a Concern. https://gist.github.com/1014971
It's simple. In app/models/concerns create a file your_functionality.rb as follows:
module YourFunctionality
extend ActiveSupport::Concern
def your_fat_method
# insert...
end
end
And in your model simply:
include YourFunctionality
I've found a way to make this work, but am curious about a better way / the Rails 3 way. (I'm using 2.3.5 still, but hope to migrate around New Year's.)
The situation: I've got two layers of module inheritance, the second layer gets mixed into a Rails model. Both modules define validation methods and I'd like both of them to attach the validations to the base class, but because of the two levels of inheritance, the following doesn't work:
def self.included(base)
base.validate :yadda_yadda
end
When that module is included by another module, the interpreter grinds to a screeching halt because Modules don't know about ActiveRecord::Validations. Including the validations module begs the question of "where is save?" thanks to alias_method.
The following works, as long as you remember to call super whenever you override validate(). I don't trust myself or future maintainers to remember that, so I'd like to use the validate :yadda_yadda idiom instead, if possible.
module Grandpa
def validate
must_be_ok
end
def must_be_ok
errors.add_to_base("#{self} wasn't ok")
end
end
module Dad
include Grandpa
def validate
super
must_be_ok_too
end
def must_be_ok_too
errors.add_to_base("#{self} wasn't ok either")
end
end
class Kid < ActiveRecord::Base
include Dad
validate :must_be_ok_three
def must_be_ok_three
errors.add_to_base("#{self} wasn't ok furthermore")
end
end
Suggestions? Tips for Rails 3 approach? I don't think the validations API has changed that much.
I solved it (when I ran into the same problem, but with something other than validation).
Short answer: you can call send(:included, base) on the module you want to bring in. Within the higher-up included() definition, you need to check whether the base is a Class or a Module.
Why would you ever want to do this? Well, I've got some modules that extract some common functionality out of my models. For instance, the module HasAllocable sets up a polymorphic belongs_to relationship, and a getter/setter pair for a virtual attribute. Now I have another module that needs to pull in HasAllocable, to spare the base classes from having to remember it.
I'd be interested to know whether this smells funny to anyone. I haven't seen anything like it on the web, so I wonder if multiple layers of model inheritance is more of an antipattern.
module Grandpa
def self.included(base)
if base.kind_of?(Class)
base.validate :must_be_ok
end
end
end
module Dad
include Grandpa
def self.included(base)
if base.kind_of?(Class)
# you can do this
#base.send(:include, Grandpa)
# you can also do this
Grandpa.send(:included, base)
# this does not invoke Grandpa.included(Kid)
#super(base)
base.validate :must_be_ok_too
end
end
end
class Kid < ActiveRecord::Base
include Dad
validate :must_be_ok_three
end