factory girl nested factory - ruby-on-rails-3

I have an account model that belongs_to a role model.
factory :role do
name "student"
end
factory :account do
user
role
end
The first factory creates a role named "student". The second factory creates an account that is associated to the student role that was created in the previous factory. It also is associated with a user...which is not important for this question.
I have many roles to be tested (admin, student, assistant)... I dont want to specify 'student' in the role factory...thats too static. How do I specify what role to create at the time the account factory is created? Like:
factory :account do
user
role_id { factory :role { name: "admin"} }
end
What is the best way to accomplish this?

If you want a purely FG solution, you could use Traits:
factory :account do
user
trait :student do
association :role, :name => "student"
end
trait :admin do
association :role, :name => "admin"
end
end
FactoryGirl.create :account, :student
FactoryGirl.create :account, :admin
However, you can override the properties of the factory when you create the factory object. This allows for more flexibility:
FactoryGirl.create(:account,
:role => FactoryGirl.create(:role, :name => "student")
)
Since this is obviously verbose, I'd create a little helper method:
def account_as(role, options = {})
FactoryGirl.create(:account,
options.merge(:role => FactoryGirl.create(:role, :name => "student"))
)
end
Then in your tests:
let(:account) { account_as "student" }
Alternately, you could just shorten up your role generator so you could use it like:
def role(role, options = {})
FactoryGirl.create :role, options.merge(:name => role)
end
account = FactoryGirl.create :account, :role => role("student")

Related

Factory girl, dependent factories

UPDATE
I went back to using Fixtures. IMOP, fixtures are FAR better than factories; easier to use, easier to write, easier to understand (no magic). My suggestion: limit your testing library to the very basics (listen to DHH)...use minitest with fixtures.
original post
In my app a district has many schools, a school has many uses, a user has many accounts, an account has one role. In order to create complete factories for testing I need to create a user and school that persists across factories. Im getting a "stack level too deep" error in my recent attempts.
My user_test.rb
FactoryGirl.define do
factory :district do
name "Seattle"
end
factory :school do
association :primarycontact, factory: :user # expecting this to attach the user_id from factory :user as :primary contact_id in the school model
association :district, factory: :district # expecting this to attach the :district_id from the :district factory as :district_id in the school model
name "Test School"
end
factory :user do, aliases: [:primarycontact]
email "adam#example.com"
name "Who What"
username "wwhat"
password "123456"
password_confirmation { |u| u.password }
association :school, factory: :school # expecting this to create :school_id in the users model, using the :school factory
end
factory :role do
name "student"
end
factory :account do
association :user, factory: :user
association :role, factory: :role
end
end
So, I am attempting to do FactoryGirl.create(:account)... which I am expecting to create an account, with the user and role from the factories above, with the user associated with the school that is associated with the district. This is not working for me. Among failing tests I get a "stack level too deep" error. And, I believe my before each DatabaseCleaner.clean is clearing the test db before each new factory.
The test that calls these factories is:
describe "User integration" do
def log_em_in
visit login_path
fill_in('Username', :with => "wwhat")
fill_in('Password', :with => "123456")
click_button('Log In')
end
it "tests log in" do
user = FactoryGirl.create(:account)
log_em_in
current_path.should == new_user_path
end
end
.
current_path.should == new_user_path returns unknown method error 'should'
How can I improve this code to nest the factories correctly and get a current_user in order to continue testing?
MODELS
school.rb
belongs_to :district
belongs_to :primarycontact, :class_name => "User"
has_many :users, :dependent => :destroy
user.rb
belongs_to :school
has_many :accounts, :dependent => :destroy
district.rb
has_many :schools
account.rb
belongs_to :role
belongs_to :user
role.rb
has_many :accounts
has_many :users, :through => :accounts
Your basic problem is that you have a circular dependency between your user factory and your school factory, caused by the fact that you create a primarycontact (a user) when you create a school, then that user creates a school, and so on.
You can get around this by changing how you define your school association inside the user factory. Before doing that though, I'd suggest as a general rule using the shorthand notation for associations. So replace this:
factory :account do
association :user, factory: :user
association :role, factory: :role
end
with this:
factory :account do
user
role
end
Using this simplification, the following factories will do what you want without generating any circular dependency:
FactoryGirl.define do
factory :district do
name "Seattle"
end
factory :school do |school|
district
primarycontact
name "Test School"
after_build do |s|
s.primarycontact.school = s
end
end
factory :user do
email "adam#example.com"
name "Who What"
username "wwhat"
password "123456"
password_confirmation { |u| u.password }
school
end
factory :primarycontact, class: "User" do
# add any attributes you want the primarycontact user to have here
end
factory :role do
name "student"
end
factory :account do
user
role
end
end
Notice that what I have done is to create a factory for primarycontact with the class: "User" option. Unlike the user factory, this factory does not create the school by default, avoiding the circular dependency.
Then in the school factory, I use an after_build callback to assign the school itself to the school association on primarycontact, rather than creating a new school (which was causing the problem in your factories).
Hope that makes sense. Note that the callback syntax has changed in the more recent version of factory_girl, see the documentation for details.

Omniauth Facebook auth + identity using the same model instead of two

I've setup Omniauth Facebook authentication according to this tutorial: http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/
And now I'm trying to combine it with omniauth-identity using the same User model instead of a separate Identity model as in this tutorial: http://railscasts.com/episodes/304-omniauth-identity?view=asciicast , but I cannot get it to work properly.
This is is my initializers/omniauth.rb file:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, 'xxxxx', 'xxxxx'
provider :identity, :fields => [:email], :model => User
end
I've added 'password_digest' column that is needed by omniauth-identity to my User model/table and changed the User model code
from
class User < ActiveRecord::Base
has_many :authorizations
#validates :name, :email, :presence => true
def add_provider(auth_hash)
# check if the provider already exists, so we don't add it twice
unless authorizations.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
Authorization.create :user => self, :provider => auth_hash["provider"], :uid => auth_hash["uid"], :token => auth_hash["token"]
end
end
end
to
class User < OmniAuth::Identity::Models::ActiveRecord
...
end
but when I do that the code in the Authorization model that creates the User and the Authorization models does not work properly
When the User model extends from ActiveRecord::Base the records are created just fine but when I extend the user model from OmniAuth::Identity::Models::ActiveRecord the user model is not stored in the database when you create a new authorization.
This is the Authorization model code:
class Authorization < ActiveRecord::Base
belongs_to :user
validates :provider, :uid, :presence => true
def self.find_or_create(auth_hash)
unless auth = find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
user = User.create :name => auth_hash["info"]["name"], :email => auth_hash["info"]["email"]
auth = create :user => user, :provider => auth_hash["provider"], :uid => auth_hash["uid"], :token => auth_hash["credentials"]["token"]
end
auth
end
end
When I extend the User model from ActiveRecord::Base and try to create a new registration with Identity I get this error:
ActiveRecord::UnknownAttributeError
unknown attribute: password
Is there any way to get this working this way? I don't know what to do now.
not sure you're still having the problem, but maybe someone on the interwebz will.
I just posted a solution on by blog, should solve your problems:
http://bernardi.me/2012/09/using-multiple-omniauth-providers-with-omniauth-identity-on-the-main-user-model/
try to add attr_accessor :password and may be attr_accessor :email

Rails 3 has_many :through + join table conditions / scoping

I'm working on an app that has the models User and Project, and User can be assigned to multiple Projects, via ProjectUser, with a role (e.g. Developer, Designer).
Project
has_many :project_users
has_many :users, :through => :project_users
User
has_many :project_users
has_many :projects, :through => :project_users
ProjectUser (user_id, project_id, role)
belongs_to :user
belongs_to :project
I can call #project.users and #user.projects, but since there are varying roles, I'd like to be a bit more specific with the relations. Ideally, I want to be able to do the following:
#project.developers
# returns #project.users, but only where ProjectUser.role = 'Developer'
#project.designers << #user
# creates a ProjectUser for #project, #user with role 'Designer'
#user.development_projects
# returns projects where #user is assigned as a 'Developer'
#user.design_projects << #project
# creates a ProjectUser for #project, #user with role 'Designer'
I currently have the following code:
has_many :developers, :through => :project_users, :source => :user,
:class_name => "User",
:conditions => ['project_users.role = ?','Developer']
But this only really does the fetching one-way, and doesn't give me much else - I can't build or assign or anything.
I'm attempting some more complex logic which I think might work, but would appreciate some pointers:
has_many :developer_assignments, :source => :project_user,
:conditions => { :role => 'Developer' }
has_many :developers, :through => :developer_assignments # class_name?
Any suggestions? Thanks!
has_many accepts a block that can define/override methods for the association. This will allow you to create a custom method for <<. I've created a small example for you, you could create build in a similar fashion.
# Project.rb
has_many :developers, :through => :project_users, :source => :user,
:conditions => "project_users.role = 'developer'" do
def <<(developer)
proxy_owner.project_users.create(:role => 'developer', :user => developer)
end
end
Now you can add a new developer to your your project with: #project.developers << #user as requested. #project.developers gives you all the developers.
If you have a lot of roles, it might be useful to create these has_many statements dynamically.
# Project.rb
ROLES = ['developer','contractor']
ROLES.each do |role|
self.class_eval <<-eos
has_many :#{role.downcase}s, :through => :project_users, :source => :user,
:conditions => "project_users.role = '#{role}'" do
def <<(user)
proxy_owner.project_users.create(:role => '#{role}', :user => user)
end
end
eos
end
Looking back at everything above it doesn't seem like the rails way of doing things. Scoping this should make it possible to get the build and create commands working without redefining everything.
Hope this helps!
It sounds like what you're looking for is a combination of RoR's single table inheritance and named scopes.
Take a look at the following article for a nice example about polymorphic associations. This should help you with achieving the following:
#project.developers
# returns #project.users, but only where ProjectUser.role = 'Developer'
#project.designers << #user
# creates a ProjectUser for #project, #user with role 'Designer'
Scopes will give you a clean way to implement #user.development_projects but there may be more trickery required to get the << operator.
Did you try using scopes yet? It doesn't let you do <<. But it simplifies querying.
Try:
Project
scope :developers, lambda {
includes(:project_users).where("project_users.role = ?", "developer")
}
You will be able to get all developers using: #project.developers

CanCan Separate Role Model

I've been following this guide on the Separate Role Model implementation in CanCan. When a User, tries to sign up this error is thrown when creating the Assignment.
User(#21477600) expected, got Symbol(#5785720)
I'm using a Devise generated User with the following before_save functions
class User < ActiveRecord::Base
.
.
.
def create_profile
profile = Profile.new :user_id => :id
end
def create_role
Assignment.new :user => :id, :role => Role.find_by_role("user").id
end
end
I want to default the user's role to "user", but I'm obviously doing something wrong. How should this be implemented?
Not sure if you've seen this or not, but Ryan Bates has produced a wonderful document regarding:
Separate Role Models
EDIT:
Here's what I am currently using. I believe your 'Assignment' is the same as my 'UserRole'.
user.rb
#--
# Relationship
has_many :user_roles, :dependent => :destroy, :uniq => true
has_many :roles, :through => :user_roles, :uniq => true
#--
# Instance Method
# Determine if the user has a specified role
# You can find this method at: https://github.com/ryanb/cancan/wiki/Separate-Role-Model
# Written by Ryan Bates, I added the downcase though to detect 'Admin' vs 'admin'.
# Example:
# user.has_role? :Admin
# => true
def has_role?(role_sym)
roles.any? { |role| role.name.underscore.to_sym == role_sym.downcase }
end
role.rb
# id :integer(4) not null, primary key
# name :string(255)
#--
# Relationship
has_many :user_roles, :dependent => :destroy, :uniq => true
has_many :users, :through => :user_roles, :uniq => true
user_role.rb
# id :integer(4) not null, primary key
# user_id :integer(4)
# role_id :integer(4)
#--
# Relationship
belongs_to :user
belongs_to :role
Then in my ability.rb
def initialize(user)
user ||= User.new # in case of a guest
if user.has_role? :Admin # The user is an Administrator
can :manage, :all
else
can :read, :all
end
end
Then I can easily assign roles, like in my seed file by doing something like:
# Create Users
...
# Roles
admin = Role.create!(:name => "admin")
standard = Role.create!(:name => "standard")
# UserRoles :Admin
user1.roles << admin
user2.roles << standard
So by calling user.roles << [role_name], I am essentially creating a UserRole, which has a user_id and a role_id.
There might be some more effective ways to accomplish this, but I cant tell without the exact model associations.
Anyway, I think this should work:
def create_role
Assignment.new :user => self, :role => Role.find_by_role("user")
end
Since you specify :user and not :user_id, you should pass self. The same thing for :role. If you had specified :role_id then you should have entered .id after find_by_role but since you only specify :role then remove .id
It looks like you're passing symbols to hash conditions that are expecting objects.
DanneManne's answer should work. You could alternatively do
Assignment.new( :user_id=>self.id, :role_id => Role.find_by_role('user').id )
(but Danne's is better, imo)
One last suggestion -- why not say the name of the role is "name", not "role". So then you'd be doing, Role.find_by_name('user'). That would be easier for a subsequent programmer to follow.
Firstly, you should not use save callback because it will be fired on both create & update.
Secondly, if you set up associations between models like that:
class User < ActiveRecord::Base
has_one :profile
has_many :assignments
end
class Profile < ActiveRecord::Base
belongs_to :user
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
You will have convenient methods like user.profile, user.build_profile and user.create_profile. Build & create will set up user_id on profile automatically. You can use them in your callbacks without having to define any methods.
Note that before user is saved it does not have an id. So you need to use either before_create :build_profile either after_create :create_profile. The first one will create profile in memory that will be autosaved after user is saved, the second one is pretty straightforward.
There will be similar methods for assignments too: user.assignments.build user.assignments.create. So the final code for User will look something like this
class User < ActiveRecord::Base
has_one :profile
has_many :assignments
after_create :create_profile, :create_assignment
def create_assignment
assignments.create :role => Role.find_by_role("user")
end
end

Rails 3 - Model Association Problems

I have a project where an User can own a Project and make part of a Project as a Team.
My models are like that:
class User
has_many :projects, :foreign_key => "owner_id"
has_many :project_memberships, :foreign_key => "member_id"
has_many :shared_projects, :class_name => "Project", :through => :project_memberships, :foreign_key => "member_id"
end
class Project
belongs_to :owner, :class_name => "User"
has_many :project_memberships
has_many :members, :class_name => "User", :through => "project_memberships", :foreign_key => "member_id"
end
My question is: How can I create/delete etc a new Project so an User can own it since I'm not using nested resources?
Here is my Project Controller:
def new
#project = Project.new
end
def create
#owner = User.find(params[:user_id])
#project= #owner.projects.build(params[:project])
...
end
Thanks in advance.
If I understand your question correctly, you need to store current signed in user ID in session or use some authentication gem (like devise) which will do it for you.
Devise provides helper method current_user which returns an instance of User model. So you could do like so:
def create
#project= current_user.projects.build(params[:project])
...
end
Update
If you pass user_id through form, you allow anyone to create project with another user's id. Actions that create something, that belong to current user should be constrained to current user on the serverside