To prevent removal of related records, I am applying the before_destroy callbacks approach on each model
I defined several related-records validation methods in a module, so that they can be shared to different models' before_destroy callbacks:
class Teacher < ActiveRecord::Base
include RelatedModels
before_destroy :has_courses
...
end
class Level < ActiveRecord::Base
include RelatedModels
before_destroy :has_courses
...
end
module RelatedModels
def has_courses
if self.courses.any?
self.errors[:base] << "You cannot delete this while associated courses exists"
return false
end
end
def has_reports
if self.reports.any?
self.errors[:base] << "You cannot delete this while associated reports exists"
return false
end
end
def has_students
if self.students.any?
self.errors[:base] << "You cannot delete this while associated students exists"
return false
end
end
...
end
But it doesn't looks very DRY
Any idea how to do it in a single method?
Meta-programming it's not among my skills
Thanks in advance
You might want to try an observer class
These are depreciated in Rails 4 (you have to use the rails-observers gem), but seem to be still part of the Rails 3 core
Observer Classes
"Listen" to specified actions in your model, and provide extended functionality. As stated by their documentation, they are particularly good for de-cluttering your models, allowing you to combine a lot of functionality into one central set of functions
Here's what you might want to do (please bare in mind I have only used these in Rails 4):
class RelatedObserver < ActiveRecord::Observer
observe :teachers, :school
def before_destroy(record)
associations = %w(teachers schools reports)
associations.each do |association|
if record.send(association).any?
self.errors[:base] << "You cannot delete this while associated #{association.pluralize} exists"
return false
end
end
end
end
Here's a good resource for you
Related
I have a Rails 3.2.21 app where I'm adding some basic timeclock functionality. I need to build a scope called current_clock_event that will look for the last record for a user where clock_out: nil so in essence, the record that will be fetched is the last ClockEvent record for the user that does not have a value in clock_out. This is what I want to pass to my controller/view.
class ClockEvent < ActiveRecord::Base
attr_accessible :clock_in, :clock_out, :total_hours, :user_id
scope :current_clock_event, where(clock_out: NIL).last
end
As you can see I wrote a very simple scope to pull a record where the clock_out: NIL so in theory that should pull the last incomplete record. I think this is working ok but I need to figure out how to access this in the controller and have some sort of conditional to either pull the current_clock_event or instantiate a new clock event if the last record is completed (both clock_in and clock_out are populated)
So I'm stubbing out my controller but am hitting a wall as to how to do this.
class ClockEventsController < ApplicationController
def index
#clock_event = current_user.current_clock_event # need to figure out this part to fetch the record or if the record is complete instantiate a ClockEvent.new for the user.
respond_to do |format|
format.html # index.html.erb
format.js
end
end
end
I wrote code 2 years ago that did all of this but lost the repo by accident so I have nothing to reference and am sort of brain-fogging on how to pull this off.
Any help would be appreciated. If you need more examples or further explanation, please let me know.
You might want to try something like this:
class ClockEvent
belongs_to :user
# you might want to add an order here...
scope :last_clock_event, -> { where("clock_out NULL").last }
def completed?
clock_in.present? && clock_out.present?
end
end
class User
has_many :clock_events
def current_clock_event
ce = clock_events.last_clock_event
ce.completed? ? ClockEvent.new : ce
end
end
class ClockEventsController < ApplicationController
def index
#clock_event = current_user.current_clock_event
render :index
end
end
The completed? method defined on the ClockEvent instance allows you to tell if your instance is considered completed or not.
The current_clock_event method defined at the User level allows you to define the logic to return either the last clock event record or a new one if completed.
The index method is pretty straight forward.
I played around with some code and was able to get some refactoring help to make things cleaner. In the User model I refactored current_clock_event to just clock_event and seem to have been able to make the code a bit cleaner, although it's not tested, just stubbed out for now. Let me know what you think.
class ClockEvent
belongs_to :user
scope :incomplete, -> { where(clock_out: nil) }
scope :complete, -> { where.not(clock_out: nil) }
def completed?
clock_in.present? && clock_out.present?
end
end
class User
has_many :clock_events
def clock_event
#clock_event ||= clock_events.incomplete.last || clock_events.new
end
end
class ClockEventsController < ApplicationController
def index
#clock_event = current_user.clock_event
render :index
end
end
There is my Observer code:
class ActivityObserver < ActiveRecord::Observer
observe PublicActivity::Activity
def after_create(activity)
if activity.trackable
Something.create(trackable: trackable)
end
end
end
My spec is
require 'spec_helper'
describe ActivityObserver do
describe '#after_create' do
it "should be triggered after an activity is created'" do
activity = create(:activity)
ActivityObserver.instance.should_receive(:after_create).with(activity)
end
end
end
But it did not pass
move and edit this line
ActivityObserver.instance.should_receive(:after_create)
above
activity = create(:activity)
you are calling create before telling Rspec to look for the after_create call, that's probably why its failing
As an example:
def create
resource = build_resource
resource.assign_attributes(params[resource_instance_name], as: :admin)
create! do |format|
format.js {...}
end
end
The problem with above is that attributes are not being assigned with as: :admin, they are being assigned without any check and so this method is not having any effect. Is it the create! method? Attributes are being assigned to this resource elsewhere and I can't find out where it is. Appreciate any insight.
Found my answer here: https://github.com/josevalim/inherited_resources/pull/153. Had to override as_role and role_given? --
def as_role
{ as: current_user.highest_role }
end
def role_given?
true
end
This will then always apply roles to attributes for either the controller it is defined in, or all resources if you inherit from a master resources controller like I do.
I have 2 models like the following
Class Post
has_many :comments, :dependent => :destroy
end
Class Comment
validates_presence_of :post
validates_presence_of :comment
belongs_to :post
end
In Comments controller,
def create
comment = #post.comments.build(params[:comment])
if comment.save
// some code
else
// some code
end
end
When the comment is invalid as per the validation, the comment is not saved. But when the #post object is accessed in the view, it contains a comment object with nil id. This did not happen in Rails 2.3.11. We are upgraded to Rails 3.1 and then now to Rails 3.2. This comment object with nil id disappears when I do #post.reload. We are using REE.
I tried to interchange build and new methods. It had the same result as build. Similar behavior is found across our application. Is it the expected behavior or am I doing something wrong?
This seems like expected behaviour to me.
via http://guides.rubyonrails.org/association_basics.html#belongs_to-association-reference
4.1.1.3 build_association(attributes = {})
The build_association method returns a new object of the associated
type. This object will be instantiated from the passed attributes, and
the link through this object’s foreign key will be set, but the
associated object will not yet be saved.
When you call #post.comments.build(...), Rails:
Creates a new Comment object
sets comment.post_id to #post.id.
Inserts it into the comments array (in memory).
When the validation fails, it doesn't delete the comment and the comment persists in the in-memory comments array. When #post gets to your view, #post.comments still includes that badly validated comment.
As for how to deal with it, I'm not sure. Maybe you could do something like (in your controller)... (Feels pretty ugly though.)
def create
comment = #post.comments.build(params[:comment])
if comment.save
// some code
else
#bad_comment = #post.comments.pop
end
end
I had a similar problem while using rails 3.2
Firstly, you need to create two separate methods in your controller. They will be as follows:
The 'new' method that is used to build your comments using 'build_association'
def new
#post = Post.new
comment = #post.build_comments
end
The 'create' method to actually create your comments using 'create_association'
def create
#post = Post.new(params[:post])
comment = #post.create_comments(params[:post][:comment_attributes])
if comment.save
// some code
else
#bad_comment = #post.comments.pop
end
end
Note: I suggest passing 'comment' attribute as a nested attribute of 'post' through your form using 'fields_for'.
Please refer:
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I want to use an ActiveModel callback to be called after an object has been voted on, the issue is that the gem I'm using (voteable_mongo) to make the model votable doesnt provide like a vote model or callback in my app, so how can I create a callback for it?
set_callback(:vote, :before) do |object|
object.do_something
end
Obviously that vote action I made up, but the gem I'm using has this method, how would you properly extend this method to trigger a callback?
Taking the plugin example as source here's what you could do:
class Post
include Mongoid::Document
include Mongo::Voteable
extend ActiveModel::Callbacks
define_model_callbacks :vote
# set points for each vote
voteable self, :up => +1, :down => -1
def vote(options, value = nil)
_run_vote_callbacks do
super( options, value )
end
end
end
I did not run this code so I am not sure if this is going to work correctly or not, but in the worst case you could alias the vote method using alias_method_chain or just copy and paste the source to inside the _run_vote_callbacks block (really, really ugly, but it's a solution anyway).
EDIT
This could also be done using alias_method_chain, if the code above does not work:
class Post
include Mongoid::Document
include Mongo::Voteable
extend ActiveModel::Callbacks
define_model_callbacks :vote
# set points for each vote
voteable self, :up => +1, :down => -1
alias_method_chain :vote, :callback
def vote_with_callback(options, value = nil)
_run_vote_callbacks do
vote_without_callbacks( options, value )
end
end
end