What are callback classes in ActiveRecord::Base in Rails - ruby

Can some one explain 16 Callback classes in this guide http://guides.rubyonrails.org/active_record_validations_callbacks.html

Ok i think i understand your problem :
The after_destroy method in PictureFileCallbacks will be auto-magically called by rails :
When rails destroys your PictureFile object, it will instantiate a PictureFileCallbacks object and try to run an after_destroy method in it.
Everything works by convention, if you follow the naming properly everything will work out of the box.
Try it on a dummy project, and if you have some trouble making this work come back with some code to show.

everything works by convention, you can try the following example:
#generate PictrueFile model with name attribute and generate seed
rails g model PictureFile name:string
#seeds.rb
3.times do |i|
PictureFile.create(name: "name#{i}")
end
#create picture_file.rb and picture_file_callbacks.rb in model directory
#picture_file_callbacks.rb
class PictureFileCallbacks
def after_destroy(picture_file)
PictureFile.create(name: "demo")
end
end
#picture_file_callbacks.rb
class PictureFile < ApplicationRecord
after_destroy PictureFileCallbacks.new
end
execute the command in rails c
PictureFile.first.destroy
PictrueFile.pluck(:name) #=>["name1", "name2", "demo"]

Related

Sinatra, ActiveRecord, FactoryGirl, and has_many_through associations

I am building a Sinatra API. My models use ActiveRecord and have a many-to-many model relationship.
class Workout < ActiveRecord::Base
has_many :workouts_exercises, dependent: :destroy
has_many :exercises, through: :workouts_exercises
end
class Exercise < ActiveRecord::Base
has_many :workouts_exercises
has_many :workouts, through: :workouts_exercises
end
class WorkoutsExercises < ActiveRecord::Base
belongs_to :workouts
belongs_to :exercises
end
I am trying to set up FactoryGirl to use these associations. Here is what I've got from reading all the docs I have found so far.
FactoryGirl.define do
factory :workout do
name 'Default Workout Factory Name'
description 'Default Workout Factory Description'
factory :workout_with_exercises do
after(:create) do |workout|
workout.exercises << FactoryGirl.create(:exercise)
end
end
end
end
FactoryGirl.define do
factory :exercise do
name 'Default Exercise Factory Name'
description 'Default Exercise Factory Description'
end
end
FactoryGirl.define do
factory :workouts_exercises do
workout
exercise
end
end
Here is my test that I would like to run
it 'returns the associated exercises' do
workout = FactoryGirl.create(:workout_with_exercises)
associated_exercises = workout.exercises
expect(associated_exercises.count).to eq(1)
end
However, when I run the specs I receive
1) Workout returns the associated exercises
Failure/Error: workout.exercises << FactoryGirl.create(:exercise)
NameError:
uninitialized constant Workout::WorkoutsExercise
The first method of debugging I tried was to pry before workout= declaration. Workout doesn't know about the exercises attribute. According to the ActiveRecord documentation, setting up a many-to-many association should provide you with the 16 #collection methods. This would mean that #exercises should return all the associated Exercise objects for the Workout object.
I can't, for the life of me, figure out whether or not it's my AR associations in the models that are to blame - or the FactoryGirl configuration I have. I've checked the SQL tables and all of the columns seem to be appropriate. I could really use some help figuring out what the issue is.
I've tried a few other FactoryGirl after_create hooks, using the shovel operator and attempting to declare the workouts_exercises association explicitly:
workout.exercises << [
create(:exercise, name: 'Bench Press', workouts_exercises: workout),
create(:exercise, name: 'Pushups', workouts_exercises: workout),
create(:exercise, name: 'DB Flys', workouts_exercises: workout)
]
Again, failure.
My third attempt was to use the FactoryGirl callback methods from ThoughtBot - ain't no calla back girl.
factory :workout_with_exercises do
after(:create) do |workout|
FactoryGirl.create(:exercise, workout: workout)
end
end
This results in
undefined method `exercise=' for #<Workout:0x007ff6250c2768>
Which makes me believe the AR associations aren't written correctly.
Thanks in advance for any help you can lend!
I use Sinatra, ActiveRecord, and RSpec. Just recently, I added FactoryGirl to the mix.
In your test framework's initialization point (I use RSpec, which is spec_helper.rb), you'll need to require the file that contains your model classes.
Next, do your model classes live within a module?
My model classes don't exist at the top-level, because I instead put them in a module for better organization within the project.
With this pattern, I found that I needed to explicitly define the module + class path for FactoryGirl.
The following would not work:
FactoryGirl.define do
factory :vendor do
name 'Test Vendor
end
end
But things began to work after I told FactoryGirl the full class name, like below:
FactoryGirl.define do
factory :vendor, class: MyAppName::Models::Vendor do
name 'Test Vendor
end
end

Versioning model serialization

I've a model and 2 controllers as follow :
class MyModel < ActiveRecord::Base
def serializable_hash(options={})
super(only: [:id, :foo])
end
end
module V1
class MyController < ApplicationController
def show
render json: {my_model: #my_model}
end
end
end
module V2
class MyController < ApplicationController
def show
render json: {my_model: #my_model}
end
end
end
I want to be able to return a different json depending on the controller :
class MyModel < ActiveRecord::Base
def serializable_hash(options={})
# If V1
super(only: [:id, :foo])
# ElsIf V2
super(only: [:id, :bar])
# End
end
end
I would like to find a generic solution, so I don't have to send the version manually in parameters.
It would be better to decouple the serialization from your model i.e. don't put the serialization code in the model. You have a few options: Using a json builder in your views directory, or using ActiveModelSerializers. Both approaches will make it easy to version your serialization code:
JSON builders:
# app/views/v1/my_controller/my_model.json.builder
json.my_model do
json.id #my_model.id
json.foo #my_model.foo
end
With the above set up you can imagine easily adding a v2 directory with a different serializer.
In your controller you don't even need to specify a render call since rails will notice you have a builder in your controller's view directory. More info about jbuilder in this Railscast
ActiveModelSerializers:
Same goes with serializers, they are just files in app/serializers. You can put your files in app/serializers/v1/my_model_serializer.rb.
The concept with these two approaches is to use Rails modular paths to version your files. A controller in app/controller/v1 will load files from other directories with the same v1: app/serializers/v1.
Here's the railscast for active model serializers. And the asciicast.
Also, here's a gem that helps with api versioning: https://github.com/EDMC/api-versions I use it and find it nice to work with.

Issue loading classes order EDIT: works, although some odd behavior along the way

I'm working on a project to recreate some of the functionality of ActiveRecord. Here's the portion that isn't working
module Associations
def belongs_to(name, params)
self.class.send(:define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
self.class.send(:define_method, :other_table_name) do |other_class|
other_class.table_name
end
.
.
.
o_c = other_class(name, params)
#puts this and other (working) values in a query
query = <<-SQL
...
SQL
#sends it off with db.execute(query)...
I'm building towards this testing file:
require 'all_files' #holds SQLClass & others
pets_db_file_name = File.expand_path(File.join(File.dirname(__FILE__), "pets.db"))
DBConnection.open(pets_db_file_name)
#class Person
#end
class Pet < SQLClass
set_table_name("pets")
set_attrs(:id, :name, :owner_id)
belongs_to :person, :class_name => "Person", :primary_key => :id, :foreign_key => :owner_id
end
class Person < SQLClass
set_table_name("people")
set_attrs(:id, :name)
has_many :pets, :foreign_key => :owner_id
end
.
.
.
Without any changes I received
.../active_support/inflector/methods.rb:230:in `block in constantize': uninitialized constant Person (NameError)
Just to make sure that it was an issue with the order of loading the classes in the file I began the file with the empty Person class, which, as predicted gave me
undefined method `table_name' for Person:Class (NoMethodError)
Since this is a learning project I don't want to change the test to make my code work (open all the classes, set all the tables/attributes then reopen them them for belongs_to. But, I'm stuck on how else to proceed.)
EDIT SQLClass:
class SQLClass < AssignmentClass
extend SearchMod
extend Associations
def self.set_table_name(table_name)
#table_name = table_name
end
def self.table_name
#table_name
end
#some more methods for finding rows, and creating new rows in existing tables
And the relevant part of AssignmentClass uses send on attr_accessor to give functionality to set_attrs and makes sure that before you initialize a new instance of a class all the names match what was set using set_attrs.
This highlights an important difference between dynamic, interpreted Ruby (et al) and static, compiled languages like Java/C#/C++. In Java, the compiler runs over all your source files, finds all the class/method definitions, and matches them up with usages. Ruby doesn't work like this -- a class "comes into existence" after executing its class block. Before that, the Ruby interpreter doesn't know anything about it.
In your test file, you define Pet first. Within the definition of Pet, you have belongs_to :person. belongs_to does :person.constantize, attempting to get the class object for Person. But Person doesn't exist yet! Its definition comes later in the test file.
There are a couple ways I can think that you could try to resolve this:
One would be to do what Rails does: define each class in its own file, and make the file names conform to some convention. Override constant_missing, and make it automatically load the file which defines the missing class. This will make load order problems resolve themselves automatically.
Another solution would be to make belongs_to lazy. Rather than looking up the Person class object immediately, it could just record the fact that there is an association between Pet and Person. When someone tries to call pet.person, use a missing_method hook to actually define the method. (Presumably, by that time all the class definitions will have been executed.)
Another way would be do something like:
define_method(belongs_to) do
belongs_to_class = belongs_to.constantize
self.class.send(:define_method, belongs_to) do
# put actual definition here
end
self.send(belongs_to)
end
This code is not tested, it's just to give you an idea! Though it's a pretty mind-bending idea, perhaps. Basically, you define a method which redefines itself the first time it is called. Just like using method_missing, this allows you to delay the class lookup until the first time the method is actually used.
If I can say one more thing: though you say you don't want to "overload" method_missing, I don't think that's as much of a problem as you think. It's just a matter of extracting code into helper methods to keep the definition of method_missing manageable. Maybe something like:
def method_missing(name,*a,&b)
if has_belongs_to_association?(name)
invoke_belongs_to_association(name,a,b)
elsif has_has_many_association?(name)
invoke_has_many_association(name,a,b)
# more...
else
super
end
end
Progress! Inspired by Alex D's suggestion to use method_missing to delay the creation I instead used define_methodto create a method for the name, like so:
define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
define_method(:other_table_name) do |other_class|
other_class.table_name
end
#etc
define_method(name) do #|params| turns out I didn't need to pass in `params` at all but:
#p "---#{params} (This is line 31: when testing this out I got the strangest error
#.rb:31:in `block in belongs_to': wrong number of arguments (0 for 1) (ArgumentError)
#if anyone can explain this I would be grateful.
#I had declared an #params class instance variable and a getter for it,
#but nothing that should make params require an argument
f_k = foreign_key(name, params)
p f_k
o_c = other_class(name, params)
o_t_n = other_table_name(o_c)
p_k = primary_key(params)
query = <<-SQL
SELECT *
FROM #{o_t_n}
WHERE #{p_k} = ?
SQL
row = DBConnection.execute(query, self.send(f_k))
o_c.parse_all(row)
end

Rails select Empty method?

I have the following select in a helper for my Rails app:
def unit_select
Unit.all.map{|unit| unit.calls.empty? ? [unit.unit_name, unit.id] : ["#{unit.unit_name} (on call)", unit.id] }
end
What this does is look for a unit that has a call and if that unit has a call append (on call) next to the unit in a form. The problem I'm seeing with this is when it goes to look for unit.calls.empty? it's taking into account call records with a status of "closed" which should not be taken into account.
Is there another method I can use (or write) that will allow me to look at unit.calls with passing whether or not the call is in call_status open?
In your Unit model you can override the empty? method on the calls association:
class Unit < ActiveRecord::Base
has_many :calls do
def empty?
self.where(:call_status => :open).any?
end
end
end

Implementing an ActiveRecord before_find

I am building a search with the keywords cached in a table. Before a user-inputted keyword is looked up in the table, it is normalized. For example, some punctuation like '-' is removed and the casing is standardized. The normalized keyword is then used to find fetch the search results.
I am currently handling the normalization in the controller with a before_filter. I was wondering if there was a way to do this in the model instead. Something conceptually like a "before_find" callback would work although that wouldn't make sense on for an instance level.
You should be using named scopes:
class Whatever < ActiveRecord::Base
named_scope :search, lambda {|*keywords|
{:conditions => {:keyword => normalize_keywords(keywords)}}}
def self.normalize_keywords(keywords)
# Work your magic here
end
end
Using named scopes will allow you to chain with other scopes, and is really the way to go using Rails 3.
You probably don't want to implement this by overriding find. Overriding something like find will probably be a headache down the line.
You could create a class method that does what you need however, something like:
class MyTable < ActiveRecord::Base
def self.find_using_dirty_keywords(*args)
#Cleanup input
#Call to actual find
end
end
If you really want to overload find you can do it this way:
As an example:
class MyTable < ActiveRecord::Base
def self.find(*args)
#work your magic here
super(args,you,want,to,pass)
end
end
For more info on subclassing checkout this link: Ruby Tips
much like the above, you can also use an alias_method_chain.
class YourModel < ActiveRecord::Base
class << self
def find_with_condition_cleansing(*args)
#modify your args
find_without_condition_cleansing(*args)
end
alias_method_chain :find, :condition_cleansing
end
end

Resources