I am new to rails and testing models. My model class is like this:
class Tester < Person
has_one :company
accepts_nested_attributes_for :skill
end
And I want to do test for "accepts_nested_attributes_for :skill" using rspec with out any other gem. How can I accomplish this?
There are convenient shoulda gem matchers for testing accepts_nested_attributes_for, but you mentioned that you don't want to use other gems. So, using Rspec only, the idea is to set attributes hash that would include required Tester attributes and nested hash called skill_attributes that would include required Skill attributes; then pass it into create method of Tester, and see if it changes the number of Testers and number of Skills. Something like that:
class Tester < Person
has_one :company
accepts_nested_attributes_for :skill
# lets say tester only has name required;
# don't forget to add :skill to attr_accessible
attr_accessible :name, :skill
.......................
end
Your tests:
# spec/models/tester_spec.rb
......
describe "creating Tester with valid attributes and nested Skill attributes" do
before(:each) do
# let's say skill has languages and experience attributes required
# you can also get attributes differently, e.g. factory
#attrs = {name: "Tester Testov", skill_attributes: {languages: "Ruby, Python", experience: "3 years"}}
end
it "should change the number of Testers by 1" do
lambda do
Tester.create(#attrs)
end.should change(Tester, :count).by(1)
end
it "should change the number of Skills by 1" do
lambda do
Tester.create(#attrs)
end.should change(Skills, :count).by(1)
end
end
Hash syntax may be different. Also, if you have any uniqueness validations, make sure you are generating #attrs hash dynamically before every test.
Cheers, mate.
Related
I want to override the << setter in my relation. For example, given:
class Library < ActiveRecord::Base
has_many :groups
def readers
groups.find_by(name: 'readers').users
end
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, through: :group_memberships
end
class GroupMembership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class User < ActiveRecord::Base
has_many :groups, through :group_membership
end
I want to do something like
someLibrary.readers << user1
and some additional things to happen after this.
The code should look something like:
def <<(objects)
super objects
#do other things here
end
Where should it be? I guess in Group, like:
class Group
...
def users<<(objects)
super objects
#do stuff
end
end
but I only want to do it when I'm invoking << on readers.
I want to know if there is a way to know whether I'm invoking << on a group users relationship, or whether I have access to group object when I'm invoking << method on group users through the relationship.
I want to do it because it looks nice. The easiest way would be to define separate method to set readers (and be more explicit), but I want to know if it is possible in activerecord or in ruby.
edit:
Yeah I know that overriding core methods is bad thing and people go to hell for that, yada yada yada.
I'm just curious how it's done. Like, for learning purposes.
Besides the aim is just to override the << method on that particular relation so probable there might be some justification why someone might want to do it.
Obligatory disclaimer:
I do not recommend that you do this, in 'important' code. Changing the behaviour of methods like this will confuse the hell out of other developers (as well as your future self), and lead to all sorts of unintended behavioural changes!
But assuming that this is 'just for fun'...
Based on the information above, someLibrary.readers returns a collection of User records. So all we need to do is add the desired behaviour to that class.
Normally you can do this by just defining a class method, in one of two ways:
class User
def self.foo
puts 'this works!'
end
class << self
def bar
puts 'this works too!'
end
end
end
With the above in place, you can call the methods like:
someLibrary.readers.foo
someLibrary.readers.bar
...However, there is some rails black magic going on under the hood here. someLibrary.readers is actually an instance of User::ActiveRecord_Associations_CollectionProxy, and the above methods are being picked up dynamically and appended to ActiveRecord::Associations::CollectionProxy.
Because of this dynamic method definition, it is not possible to override existing Rails methods (such as <<) in this manner. Instead, we'll need to monkey-patch the User::ActiveRecord_Associations_CollectionProxy class directly:
class User
class ActiveRecord_Associations_CollectionProxy
def <<(objects)
super(objects)
# do stuff
end
end
end
If you're looking for a better way of doing this however, I'd recommend using a service object design pattern. You can then encapsulate any more complex/custom logic relating to creating/updating/deleting users, libraries, etc. in a clean and isolated abstraction.
The more established way to do this...
class Library < ActiveRecord::Base
has_many :groups
has_one :reader_group -> {groups.find_by(name: 'readers')}
has_many :readers, through: :reader_group, class_name: 'User', foreign_key: 'user_id'
end
And that's it. You can now do
my_library.readers << another_user
I've written some RSpec tests that successfully create objects with :let statements. However, the test environment doesn't maintain the associations that function properly everywhere else. Below is an example of a class that would turn up a NoMethodError (undefined method `money' for nil:NilClass). Money is a column in Inventory. Any thoughts?
class Inventory < ActiveRecord::Base
belongs_to :character
def self.return_money(character)
character.inventory.money
end
end
And here's a corresponding example for a spec doc:
require 'spec_helper'
describe 'Test methods' do
let(:trader) {
Character.create(
name: "Trader",
location_id: 1)
}
let(:trader_inventory) {
Inventory.create(
character_id: trader.id,
storage_capacity: 50000,
money: 20000,
markup: 1.35)
}
it "test method" do
expect(Inventory.return_money(trader)).to eq(100)
end
end
There is no reason this shouldn't work. RSpec isn't special, it's just regular Ruby code. You can confirm this by moving all of your code into a single file, something like:
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)
class Inventory < ActiveRecord::Base
end
class Character < ActiveRecord::Base
has_one :inventory
end
describe 'test' do
it 'works' do
puts Character.first.inventory.money.inspect
end
end
Guesses as to what may be broken:
money is a composite field or something like that. Can you post your database schema?
Library files aren't being loaded correctly. Use puts $LOADED_FEATURES to verify that all the files that should be required have been.
I'm trying to understand metaprogramming in rails, creating validations and associations dynamically on a class.
Let's say I have the following models:
class House < ActiveRecord::Base
belongs_to :owner
end
class Owner < ActiveRecord::Base
end
Now let's say my House model has a boolean attribute is_ownable, and I only want the house to have the owner association if is_ownable==true.
I thought this would work:
class House < ActiveRecord::Base
after_initialize :create_associations
after_find :create_associations
def create_associations
if self.is_ownable
self.singleton_class.belongs_to :owner
end
end
end
Now when I build or find a record of House, the create_associations function gets called with no errors, but then when I try to access the House.first.owner it throws ActiveRecord::AssociationNotFoundError.
Am I misunderstanding something about how AR associations work?
I hate to say it but this is probably a bad idea. Models should have consistent relationships even if they're not utilized on every model. This is not only against the spirit of ActiveRecord or Ruby, but object oriented programming in general. In most cases objects of a particular class are expected to have an identical interface for the sake of consistency and clarity. Adding methods to individual objects is permitted, but there should be exceptional circumstances to justify such a thing.
That's not to say you can't get the effect you want in a more idiomatic way:
class House < ActiveRecord::Base
belongs_to :owner
validates :validate_owner_assignment
protected
def validate_owner_assignment
if (self.ownable? and !self.owner)
self.errors.add(:owner, "is required if ownable")
elsif (!self.ownable? and self.owner)
self.errors.add(:owner, "cannot be assigned if not ownable")
end
end
end
Now assigning owner will trigger a save failure of type ActiveRecord::RecordInvalid if the expectations aren't met.
I'd advocate calling your booleans x and not is_x to reduce verbosity. The vast majority of the time the is_ part is redundant.
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
Consider the following parent/child relationship where Parent is 1..n with Kids (only the relevant stuff here)...
class Parent < ActiveRecord::Base
# !EDIT! - was missing this require originally -- was the root cause!
require "Kid"
has_many :kids, :dependent => :destroy, :validate => true
accepts_nested_attributes_for :kids
validates_associated :kids
end
class Kid < ActiveRecord::Base
belongs_to :parent
# for simplicity, assume a single field: #item
validates_presence_of :item, :message => "is expected"
end
The validates_presence_of methods on the Kid model works as expected on validation failure, generating a final string of Item is expected per the custom message attribute supplied.
But if try validates_with, instead...
class Kid < ActiveRecord::Base
belongs_to :parent
validates_with TrivialValidator
end
class TrivialValidator
def validate
if record.item != "good"
record.errors[:base] << "Bad item!"
end
end
end
...Rails returns a NameError - uninitialized constant Parent::Kid error following not only an attempt to create (initial persist) user data, but also when even attempting to build the initial form. Relevant bits from the controller:
def new
#parent = Parent.new
#parent.kids.new # NameError, validates_* methods called within
end
def create
#parent = Parent.new(params[:parent])
#parent.save # NameError, validates_* methods called within
end
The error suggests that somewhere during model name (and perhaps field name?) resolution for error message construction, something has run afoul. But why would it happen for some validates_* methods and not others?
Anybody else hit a wall with this? Is there some ceremony needed here that I've left out in order to make this work, particularly regarding model names?
After a few hours away, and returning fresh -- Was missing require "Kid" in Parent class. Will edit.