I'm writing a Redmine plugin and added some fields to issues form via hooks (the fields are also added to Issue table), so far so good. Now I want to make those fields mandatory, but can't figure out how to 'override' validates_presence_of behavior for Issue model.
I've created a hook for Issue save method, in order to check presence of my new fields before saving, but not sure if this is the best way to go. Is it possible to just extend Issue model so that it validates for presence of my new fields?
You can add validations on new fields in you plugin. Example is here
# load plugin file(s)
Rails.configuration.to_prepare do
TimeEntry.send(:include, TimeLimitTimeEntryPatch)
end
# in patch file
module TimeLimitTimeEntryPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
validates_presence_of :comments
validate :validate_time_limit_allowed_ip
end
end
module InstanceMethods
def validate_time_limit_allowed_ip
# add error if permission is not set and IP is not allowed
if !self.class.have_permissions?(user, project) && !time_limit_allowed_ip
errors.add(:hours, I18n.t(:not_allowed_ip))
end
end
end
end
Alternatively:
1) Create extension somewhere in your lib directory (make sure that it is required):
module IssueExtensions
extend ActiveSupport::Concern
included do
validates_presence_of :new_attr
end
end
2) Send it to Issue model. Good place for this could be config/initializers/extensions.rb (must be initialized after Redmine obviously):
Issue.send(:include, IssueExtensions)
Related
I need some help with my plugin. I want to extend ActiveRecord::Base with a method that initializes another method that can be called in the controller.
It will look like this:
class Article < ActiveRecord::Base
robot_catch :title, :text
...
end
My attempt at extending the ActiveRecord::Base class with robot_catch method looks like following. The function will initialize the specified attributes (in this case :title and :text) in a variable and use class_eval to make the robot? function available for the user to call it in the controller:
module Plugin
module Base
extend ActiveSupport::Concern
module ClassMethods
def robot_catch(*attr)
##robot_params = attr
self.class_eval do
def robot?(params_hash)
# Input is the params hash, and this function
# will check if the some hashed attributes in this hash
# correspond to the attribute values as expected,
# and return true or false.
end
end
end
end
end
end
ActiveRecord::Base.send :include, Plugin::Base
So, in the controller, this could be done:
class ArticlesController < ApplicationController
...
def create
#article = Article.new(params[:article])
if #article.robot? params
# Do not save this in database, but render
# the page as if it would have succeeded
...
end
end
end
My question is whether if I am right that robot_catch is class method. This function is to be called inside a model, as shown above. I wonder if I am extending the ActiveRecord::Base the right way. The robot? function is an instance method without any doubt.
I am using Rails 3.2.22 and I installed this plugin as a gem in another project where I want to use this functionality.
Right now, it only works if I specifically require the gem in the model. However, I want it the functionality to be included as a part of ActiveRecord::Base without requiring it, otherwise I'd have to require it in every model I want to use it, not particularly DRY. Shouldn't the gem be automatically loaded into the project on Rails start-up?
EDIT: Maybe callbacks (http://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html) would be a solution to this problem, but I do not know how to use it. It seems a bit obscure.
First, I would suggest you make sure that none of the many many built in Rails validators meet your needs.
Then if that's the case, what you actually want is a custom validator.
Building a custom validator is not as simple as it might seem, the basic class you'll build will have this structure:
class SpecialValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# Fill this with your validation logic
# Add to record.errors if validation fails
end
end
Then in your model:
class Article < ActiveRecord::Base
validates :title, :text, special: true
end
I would strongly suggest making sure what you want is not already built, chances are it is. Then use resources like this or ruby guides resources to continue going down the custom validator route.
Answer
I found out the solution myself. Bundler will not autoload dependencies from a gemspec that my project uses, so I had to require all third party gems in an engine.rb file in the lib/ directory of my app in order to load the gems. Now everything is working as it should.
Second: the robot_catch method is a class method.
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 need to extend a model in a Rails 2.3.11 app without touching the original source file. I need to add a :has_many association in it. I've tried the approach mentioned in Extend model in plugin with "has_many" using a module without success. The class I need to extend is called UbiquoUser. Here the code I have in lib/extensions.rb:
module Sindicada
module Extensions
autoload :UbiquoUser, 'extensions/ubiquo_user'
end
end
UbiquoUser.send(:extend, Sindicada::Extensions::UbiquoUser)
Here's what I have in lib/extensions/ubiquo_user.rb:
module Sindicada
module Extensions
module UbiquoUser
module ClassMethods
def has_audio_favorites
has_many :audios, :through => :audios_favorite
end
end #ClassMethods
def self.included(base)
base.extend(ClassMethods).has_audio_favorites
end
end #UbiquoUser
end #Extensions
end #Sindicada
However, when I try to access the property audios of UbiquoUser on the app I get the error undefined method audios for class blablabla.
I also have the require 'extensions' in the environment.rb file and have checked that the files are being loaded.
The problem you have now is that you are extending your class, not including a module into it, so the Sicada::Extensions::UbiquoUser#included method never gets called.
To fix this, change this line:
UbiquoUser.send(:extend, Sindicada::Extensions::UbiquoUser)
to
UbiquoUser.send(:include, Sindicada::Extensions::UbiquoUser)
In my Rails app there are several models where users are posting data to the database. Lots of this data has trailing and leading whitespaces. Is there a way I can globally strip all input's leading and trailing whitespaces?
I'd like to avoid doing this for every field in every model, seems like there could be a global way to handle this during a before_save.
Any used techniques out there?
Thanks
One more gem to do this job: https://github.com/holli/auto_strip_attributes
Also in some cases you want to squish the data user has inputted to get rid of multiple spaces inside the variable. E.g. with names or nicks.
gem "auto_strip_attributes", "~> 1.0"
class User < ActiveRecord::Base
auto_strip_attributes :name, :nick, :nullify => false, :squish => true
end
All the gems and other approaches work a bit the same way by using before_save callback. (Code example is in Jeremys example.) So there might be some issues with custom setters. You can choose to do it with
attributes.each do before_validation do ...
record.send("#{attr_name}=", record.send(attr_name).to_s.strip)
or with
attributes.each do before_validation do ...
record[attribute] = record.send(attr_name).to_s.strip)
First approach will call setter twice (once when setting, once in before_validation). The second will call setter only once but will alter the data after the call to setter.
Here is one simple way to do it on selected attributes:
module ActiveRecord
module Acts
module AttributeAutoStripper
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_attribute_auto_stripper (*names)
class_eval <<-EOV
include ActiveRecord::Acts::AttributeAutoStripper::InstanceMethods
before_validation :auto_strip_selected_attributes
def auto_strip_attributes
#{names.inspect}
end
EOV
end
end
module InstanceMethods
def auto_strip_selected_attributes
if auto_strip_attributes
auto_strip_attributes.each do |attr_name|
self.send("#{attr_name}=", self.send(attr_name).to_s.strip) unless self.send(attr_name).blank?
end
end
end
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord::Acts::AttributeAutoStripper
and then in your model:
class User < ActiveRecord::Base
acts_as_attribute_auto_stripper :name, :email
end
If users are posting data to the DB through a form, you could create a before filter method that'll strip the parameters. Put that in the Application controller.
I hope this helps :)
This fork of the StripAttributes plugin may do the trick for you:
https://github.com/fragility/strip_attributes
You could create an ActiveRecord subclass with a before_save filter that strips all attributes. Then, make all of your models a subclass of this new class.
I'm working with an external framework (redmine) which has one Project model that has_many EnabledModules.
Projects can have EnabledModules "attached" or "removed" via the module names, like this:
class Project < ActiveRecord::Base
...
has_many :enabled_modules, :dependent => :delete_all
...
def enabled_module_names=(module_names)
enabled_modules.clear
module_names = [] unless module_names && module_names.is_a?(Array)
module_names.each do |name|
enabled_modules << EnabledModule.new(:name => name.to_s)
end
end
end
I'd like to detect when new modules are attached/removed via callbacks on EnabledModule, and not modify the "original source code" if possible.
I can detect "attachments" like this:
class EnabledModule < ActiveRecord::Base
belongs_to :project
after_create :module_created
def module_created
logger.log("Module attached to project #{self.project_id}")
end
end
I thought that a before_destroy would work for detecting removals, but it will not.
This happens because the enabled_modules.clear call on Project.enabled_module_names=, doesn't invoke 'destroy' on the modules. It just sets their project_id to nil. So I figured I should use a after_update or before_update.
If I use after_update, how can I get the 'previous' project_id?
If I use before_update, how can I differentiate between modules that are 'just updated' and modules whose project_id is going to be reset to nil?
Should I use a totally different approach here?
EDIT: I just found out that I could get the old values with '_was' (i.e. self.project_was). However, collection.clear doesn't seem to trigger update callbacks. Any other solutions?
EDIT 2: Changed title
It looks like revision 2473 onwards of Redmine should solve your problem. See the diffs here:
http://www.redmine.org/projects/redmine/repository/diff/trunk/app/models/project.rb?rev=2473&rev_to=2319
Basically the code has been modified such that removed modules are destroyed rather than deleted, the difference being that model callbacks are not fired for deletes.
There's another related fix in revision 3036 that seems important (see http://www.redmine.org/issues/4200) so you might want to pick up at least that version.
I ended up reimplementing the enabled_module_names= method of projects, including a file in vendor/plugins/my_plugin/lib/my_plugin/patches/project_patch.rb and alias.
module MyPlugin
module Patches
module ProjectPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
# This replaces the existing version of enabled_module_names with a new one
# It is needed because we need the "destroy" callbacks to be fired,
# and only on the erased modules (not all of them - the default
# implementation starts by wiping them out in v0.8'ish)
alias_method :enabled_module_names=, :sympa_enabled_module_names=
end
end
module ClassMethods
end
module InstanceMethods
# Redefine enabled_module_names so it invokes
# mod.destroy on disconnected modules
def sympa_enabled_module_names=(module_names)
module_names = [] unless module_names and module_names.is_a?(Array)
module_names = module_names.collect(&:to_s)
# remove disabled modules
enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
# detect the modules that are new, and create those only
module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name) }
end
end
end
end
end
I had to include some code on my vendor/plugins/my_plugin/init.rb file, too:
require 'redmine'
require 'dispatcher'
# you can add additional lines here for patching users, memberships, etc...
Dispatcher.to_prepare :redmine_sympa do
require_dependency 'project'
require_dependency 'enabled_module'
Project.send(:include, RedmineSympa::Patches::ProjectPatch)
EnabledModule.send(:include, RedmineSympa::Patches::EnabledModulePatch)
end
Redmine::Plugin.register :redmine_sympa do
# ... usual redmine plugin init stuff
end
After this, I was able to detect deletions on enabled modules (via before_delete) on my patcher.
Regarding:
If I use after_update, how can I get the 'previous' project_id
Maybe try project_id_was, it's provided by ActiveRecord::Dirty