My solution throws NoMethodError. This is what I have done below:
Order.rb
class Order < ActiveRecord::Base
belongs_to :member
end
Member.rb
class Order < ActiveRecord::Base
has_many :orders
def liquidity_manager?
#is_liquidity_manager ||= self.class.liquidity_managers.include?(self.email)
end
def liquidity_managers
ENV['LM_ACCOUNTS'].split(',')
end
end
rake task logic
The logic in lib/update_order_tags.rake
num_counts = (Order.count/10).ceil
num_counts.times do |i|
Order.all.offset(i*10).limit(10).find_each do |g|
if g.tags.blank? # am saving on only the blank fields
if g.liquidity_manager? # The error is here
g.tags = 'LM'
g.save!
else
g.tags = 'Customer'
g.save!
end
end
end
end
How do I reference the liquidity_manager? method to be useful for me in lib?
You defined Order#liquidity_manager? in Member.rb, which is not automatically loaded by Rails. You have to either explicitly load the file Member.rb, or rename that file to /app/models/order.rb.
So from the advise given to me by #sawa in the answer section, here is what I have done to make it work.
num_counts = (Order.count/10).ceil
num_counts.times do |i|
Order.all.offset(i*10).limit(10).find_each do |g|
if g.tags.blank?
if g.member.liquidity_manager? # So this easily gets to Member.rb to reference liquidity_manager? method.
g.tags = 'LM'
g.save!
else
g.tags = 'Customer'
g.save!
end
end
end
end
Related
class Book < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
belongs_to :publisher
has_many :books
end
class Publisher < ActiveRecord::Base
before_destroy :remove_empty_author
has_many :authors, dependent: destroy_all
def remove_empty_author
books_present = authors.map(&:book).all? { |book| book.present? }
authors.destroy_all unless books_present
end
end
class PublishersController < ApplicationController
def destroy
#publisher = Publisher.find(params[:id].to_i)
#publisher.destroy # for simplicity I only show the destroy call
end
end
Scenario
This is so close to working!
I have a controller test.
I want to test the following without creating a database entry or querying the database:
1. The remove_empty_author call back is called & if I delete it & the before_destroy the test complains.
2. Destroy is called on author.
Note:
I do not want to test the details of the remove_empty_author method. This will be tested in a model unit test delete a publisher and all of the authors only if the author has no books.
What I have tried
describe "#delete," do
context "Publisher has no authors," do
before do
author = mock_model("Author", book: [])
#publisher = Publisher.new(id: 2, authors: [author, author])
current_publisher = mock_model("Publisher", id: 1)
allow(Publisher).to receive(:find).and_return(#publisher)
allow(controller).to receive(:current_publisher).and_return(current_publisher)
end
it "Controller calls destroy on Publisher" do
expect_any_instance_of(Publisher).to receive(:destroy)
end
it "Publisher calls remove_empty_author" do
expect_any_instance_of(Author).to receive(:destroy)
#publisher.run_callbacks(:destroy) do
false # Prevent active record from proceeding with destroy
end
end
end
end
Things that are working
Controller calls destroy on publisher test works.
The test successfully enters the remove_empty_author call back where
books_present = false,
authors = [(Double "Author_1001"), (Double "Author_1001")] authors.destroy_all = []
Error I am trying to overcome
Failure/Error:
DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
Exactly one instance should have received the following message(s) but didn't: destroy
What is not working
expect_any_instance_of(Author).to receive(:destroy)
My objective is to dynamically load a set of methods to an ActiveRecord model instance based on an attribute that's set:
class Book < ActiveRecord::Base
after_initialize do |cp|
self.class.include "#{cp.subject}".constantize
end
end
I then have the following concerns:
module Ruby
extend ActiveSupport::Concern
def get_framework
'rails'
end
end
module Python
extend ActiveSupport::Concern
def get_framework
'django'
end
end
Then, when I run these separately, I get the correct framework string:
python_book = Book.create(:subject => 'python', :id => 1)
python_book.get_framework -> 'django'
ruby_book = Book.create(:subject => 'ruby', :id => 2)
ruby_book.get_framework -> 'rails'
My problem is that when I have both of the books returned in a query, the Concern is included is the last in the result set and is not picking up the correct Concern methods.
Books.all.order(:id => 'asc').collect do |book|
puts book.get_framework
end
# Result
['rails', 'rails']
I am assuming that this is because the 'include' is happening at the class level and not the instance level. Would love some help as to how to clean this up and make this work.
Use .extend
to add instance methods to a instances of Book instead.
Extends in action:
module Greeter
def say_hello
"Hello"
end
end
irb(main):008:0> a = Object.new
=> #<Object:0x00000101e01c38>
irb(main):009:0> a.extend(Greeter)
=> #<Object:0x00000101e01c38>
irb(main):010:0> a.say_hello
=> "Hello"
irb(main):011:0> Object.new.say_hello
NoMethodError: undefined method `say_hello' for #<Object:0x00000101e196d0>
class Book < ActiveRecord::Base
after_initialize do |cp|
self.extend subject.constantize
end
end
class Recipe < ActiveRecord::Base
def self.tagged_with(name)
recipes = Tag.find_by(name: name).recipes
end
end
In controller
def tag
#recipes = Recipe.tagged_with(params[:tag])
render 'index'
end
Routes
get 'tag/:tag', to "recipes#tag"
How can I protect this method from breaking? If I search for a tag that hasn't been created, I get a noMethodError 'recipes' for nil:NilClass. I have tried putting
return false if recipes.nil?
as well as
redirect_to(recipes_path) if recipes.nil?
at the end of the method but nothing as worked.
It throws that error because when the value returned by Tag#find_by is nil, it cannot call the method recipes on it. In other words, nil doesn't have a recipes method.
Try checking if the tag exists first, with something like:
class Recipe < ActiveRecord::Base
def self.tagged_with(name)
tag = Tag.find_by(name: name)
recipes = tag.recipes unless tag.nil?
end
end
I don't know the way you organized your data, but a better way would be something like this probably:
class Recipe < ActiveRecord::Base
def self.tagged_with(name)
Recipe.find_by(tag: name)
end
end
def self.tagged_with(name)
tag = Tag.find_by(name: name)
if !tag.nil?
recipes = tag.recipes
else
return false
end
end
In Controller:
def tag
#recipes = Recipe.tagged_with(params[:tag])
if #recipes != false
render 'index'
else
redirect_to root_path
end
end
You see NoMethodError because you call method recipies on NilClass.find_by method returns nil if nothing found (by the way, find would raise an exception, and where return empty relation)
So, first you can do is to use try:
`
class Recipe < ActiveRecord::Base
def self.tagged_with(name)
recipes = Tag.find_by(name: name).try(:recipes)
end
end
try will silently return nil
But then you could use a better approach with scope (assuming you have tags relation):
`
class Recipe < ActiveRecord::Base
has_many :tags
scope :tagged_with, ->(name) { joins(:tags).where(tags: { name: name }) }
end
Probably, you then want to call uniq on result relation or not to use joins right in a scope etc.
I want add any kind of permissions for my rails models just including one module to the model and defining metadata in one database field. How i can do this?
For example:
Folder < AR::B
#permissions_list = [:is_private, :public_on_negotioation]
#permissions_field = :perms
include Permissions
end
module Permissions
"...?"
end
i want to have methods "is_private?", "is_private", "is_private=" for all items in a #permissions_list variable.
So i can use model in this way:
f = Folder.new
f.is_private = true
f.public_on_negotioation = false
f.save
f.reload
f.is_private?
=> true
f.public_on_negotioation?
=> false
so i wrote next Module:
module Permissions
def self.included(mod)
permissions_list = mod.instance_variable_get(:#permissions_list)
permissions_list.each_with_index do |permission, index|
define_method permission.to_sym do
perms_bits[index] == '1'
end
alias_method (permission.to_s << "?").to_sym, permission.to_sym
end
end
def perms_bits
send(self.class.instance_variable_get(:#permissions_field)).to_i.to_s(2).reverse
end
def set_permission(name, weight, options)
permissions_field = self.class.instance_variable_get(:#permissions_field)
if options[name]
self.send("#{permissions_field}=", self.send(permissions_field).to_i + weight.to_i) unless send(name)
elsif options.has_key?("#{name}_off")
self.send("#{permissions_field}=", self.send(permissions_field).to_i - weight.to_i) if send(name)
end
end
def update_perms(options)
permissions_list = self.class.instance_variable_get(:#permissions_list)
permissions_list.each_with_index do |permission, index|
set_permission(permission.to_sym, 2**index, options)
end
save
end
end
some improvements?
To extend the answer from mdesantis. The way you can wrap up the permissions code for reuse could be something like this (untested):
class Folder < ActiveRecord::Base
include Permissions
end
PERMISSIONS_STRUCT = Struct.new(:is_private, :public_on_negotiation)
module Permissions
def self.included(klass)
klass.class_eval do
serialize :permissions, PERMISSIONS_STRUCT
end
klass.include(InstanceMethods)
end
module InstanceMethods
def is_private?
permissions.is_private
end
def is_private=(is_private)
permissions.is_private = is_private
end
end
end
Take a look at ActiveRecord::serialize:
Folder < AR::B
# Must be costant, otherwise Rails will raise an
# ActiveRecord::SerializationTypeMismatch
PERMISSIONS_STRUCT = Struct.new(:is_private, :public_on_negotiation)
serialize :permissions, PERMISSIONS_STRUCT
def is_private?
permissions.is_private
end
def is_private=(is_private)
permissions.is_private = is_private
end
# The same for public_on_negotiation
end
f = Folder.new
f.is_private = true
f.save
f.reload
f.is_private?
=> true
If you need to dynamically define accessor methods:
Folder < AR::B
[:is_private, :public_on_negotiation].each do |action|
define_method("#{action}?") do
permissions.send action
end
end
# And so on for "#{action}=", ...
end
And remember: refactoring is up to you! :-)
I have an (I think) relatively straightforward has_many :through relationship with a join table:
class User < ActiveRecord::Base
has_many :user_following_thing_relationships
has_many :things, :through => :user_following_thing_relationships
end
class Thing < ActiveRecord::Base
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
end
class UserFollowingThingRelationship < ActiveRecord::Base
belongs_to :thing
belongs_to :user
end
And these rspec tests (I know these are not necessarily good tests, these are just to illustrate what's happening):
describe Thing do
before(:each) do
#user = User.create!(:name => "Fred")
#thing = Thing.create!(:name => "Foo")
#user.things << #thing
end
it "should have created a relationship" do
UserFollowingThingRelationship.first.user.should == #user
UserFollowingThingRelationship.first.thing.should == #thing
end
it "should have followers" do
#thing.followers.should == [#user]
end
end
This works fine UNTIL I add an after_save to the Thing model that references its followers. That is, if I do
class Thing < ActiveRecord::Base
after_save :do_stuff
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
def do_stuff
followers.each { |f| puts "I'm followed by #{f.name}" }
end
end
Then the second test fails - i.e., the relationship is still added to the join table, but #thing.followers returns an empty array. Furthermore, that part of the callback never gets called (as if followers is empty within the model). If I add a puts "HI" in the callback before the followers.each line, the "HI" shows up on stdout, so I know the callback is being called. If I comment out the followers.each line, then the tests pass again.
If I do this all through the console, it works fine. I.e., I can do
>> t = Thing.create!(:name => "Foo")
>> t.followers # []
>> u = User.create!(:name => "Bar")
>> u.things << t
>> t.followers # [u]
>> t.save # just to be super duper sure that the callback is triggered
>> t.followers # still [u]
Why is this failing in rspec? Am I doing something horribly wrong?
Update
Everything works if I manually define Thing#followers as
def followers
user_following_thing_relationships.all.map{ |r| r.user }
end
This leads me to believe that perhaps I am defining my has_many :through with :source incorrectly?
Update
I've created a minimal example project and put it on github: https://github.com/dantswain/RspecHasMany
Another Update
Thanks a ton to #PeterNixey and #kikuchiyo for their suggestions below. The final answer turned out to be a combination of both answers and I wish I could split credit between them. I've updated the github project with what I think is the cleanest solution and pushed the changes: https://github.com/dantswain/RspecHasMany
I would still love it if someone could give me a really solid explanation of what is going on here. The most troubling bit for me is why, in the initial problem statement, everything (except the operation of the callback itself) would work if I commented out the reference to followers.
I've had similar problems in the past that have been resolved by reloading the association (rather than the parent object).
Does it work if you reload thing.followers in the RSpec?
it "should have followers" do
#thing.followers.reload
#thing.followers.should == [#user]
end
EDIT
If (as you mention) you're having problems with the callbacks not getting fired then you could do this reloading in the object itself:
class Thing < ActiveRecord::Base
after_save { followers.reload}
after_save :do_stuff
...
end
or
class Thing < ActiveRecord::Base
...
def do_stuff
followers.reload
...
end
end
I don't know why RSpec has issues with not reloading associations but I've hit the same types of problems myself
Edit 2
Although #dantswain confirmed that the followers.reload helped alleviate some of the problems it still didn't fix all of them.
To do that, the solution needed a fix from #kikuchiyo which required calling save after doing the callbacks in Thing:
describe Thing do
before :each do
...
#user.things << #thing
#thing.run_callbacks(:save)
end
...
end
Final suggestion
I believe this is happening because of the use of << on a has_many_through operation. I don't see that the << should in fact trigger your after_save event at all:
Your current code is this:
describe Thing do
before(:each) do
#user = User.create!(:name => "Fred")
#thing = Thing.create!(:name => "Foo")
#user.things << #thing
end
end
class Thing < ActiveRecord::Base
after_save :do_stuff
...
def do_stuff
followers.each { |f| puts "I'm followed by #{f.name}" }
end
end
and the problem is that the do_stuff is not getting called. I think this is the correct behaviour though.
Let's go through the RSpec:
describe Thing do
before(:each) do
#user = User.create!(:name => "Fred")
# user is created and saved
#thing = Thing.create!(:name => "Foo")
# thing is created and saved
#user.things << #thing
# user_thing_relationship is created and saved
# no call is made to #user.save since nothing is updated on the user
end
end
The problem is that the third step does not actually require the thing object to be resaved - its simply creating an entry in the join table.
If you'd like to make sure that the #user does call save you could probably get the effect you want like this:
describe Thing do
before(:each) do
#thing = Thing.create!(:name => "Foo")
# thing is created and saved
#user = User.create!(:name => "Fred")
# user is created BUT NOT SAVED
#user.things << #thing
# user_thing_relationship is created and saved
# #user.save is also called as part of the addition
end
end
You may also find that the after_save callback is in fact on the wrong object and that you'd prefer to have it on the relationship object instead. Finally, if the callback really does belong on the user and you do need it to fire after creating the relationship you could use touch to update the user when a new relationship is created.
UPDATED ANSWER **
This passes rspec, without stubbing, running callbacks for save (after_save callback included ), and checks that #thing.followers is not empty before trying to access its elements. (;
describe Thing do
before :each do
#user = User.create(:name => "Fred");
#thing = Thing.new(:name => 'Foo')
#user.things << #thing
#thing.run_callbacks(:save)
end
it "should have created a relationship" do
#thing.followers.should == [#user]
puts #thing.followers.inspect
end
end
class Thing < ActiveRecord::Base
after_save :some_function
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
def some_function
the_followers = followers
unless the_followers.empty?
puts "accessing followers here: the_followers = #{the_followers.inspect}..."
end
end
end
ORIGINAL ANSWER **
I was able to get things to work with the after_save callback, so long as I did not reference followers within the body / block of do_stuff. Do you have to reference followers in the real method you are calling from after_save ?
Updated code to stub out callback. Now model can remain as you need it, we show #thing.followers is indeed set as we expected, and we can investigate the functionality of do_stuff / some_function via after_save in a different spec.
I pushed a copy of the code here: https://github.com/kikuchiyo/RspecHasMany
And spec passing thing* code is below:
# thing_spec.rb
require 'spec_helper'
describe Thing do
before :each do
Thing.any_instance.stub(:some_function) { puts 'stubbed out...' }
Thing.any_instance.should_receive(:some_function).once
#thing = Thing.create(:name => "Foo");
#user = User.create(:name => "Fred");
#user.things << #thing
end
it "should have created a relationship" do
#thing.followers.should == [#user]
puts #thing.followers.inspect
end
end
# thing.rb
class Thing < ActiveRecord::Base
after_save :some_function
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
def some_function
# well, lets me do this, but I cannot use #x without breaking the spec...
#x = followers
puts 'testing puts hear shows up in standard output'
x ||= 1
puts "testing variable setting and getting here: #{x} == 1\n\t also shows up in standard output"
begin
# If no stubbing, this causes rspec to fail...
puts "accessing followers here: #x = #{#x.inspect}..."
rescue
puts "and this is but this is never seen."
end
end
end
My guess is that you need to reload your Thing instance by doing #thing.reload (I'm sure there's a way to avoid this, but that might get your test passing at first and then you can figure out where you've gone wrong).
Few questions:
I don't see you calling #thing.save in your spec. Are you doing that, just like in your console example?
Why are you calling t.save and not u.save in your console test, considering you're pushing t onto u? Saving u should trigger a save to t, getting the end result you want, and I think it would "make more sense" considering you are really working on u, not t.