I am using the public_activity gem and I have a method which deletes an activity record when its trackable parent is deleted. As per below.
def destroy
#comment = Comment.find(params[:id])
if #comment.destroy
#activity = PublicActivity::Activity.where(trackable_type: "Comment", trackable_id: #comment.id).first
#activity.destroy
respond_to do |format|
format.js
end
end
end
I have tried quite a few different names to use for the public_activity factory however none seem to work and return trait not registered
How can i set up a public_activity factory with the correct name?
factories.rb
factory :activity do
trackable_id :answer
recipient_id :user
owner_id :user
end
Blind answer, try this:
factory :activity, class: 'PublicActivity::Activity' do
trackable_id :answer
recipient_id :user
owner_id :user
end
This works for FactoryBot:
FactoryBot.define do
factory :activity, class: 'PublicActivity::Activity' do
association :trackable, factory: :answer
association :recipient, factory: :user
association :owner, factory: :user
end
end
Related
I can't create a valid comment factory using factory girl. My comment model belongs to commentable and is polymorphic. I've tried a whole bunch of different things, but at the moment, most of my tests are failing with this error:
ActiveRecord::RecordInvalid:
Validation failed: User can't be blank, Outlet can't be blank
I'm not sure why it isn't passing validation, especially because my comment model validates the presence of user_id and outlet_id, not User and Outlet
Here is my factory:
factory :comment do
body "This is a comment"
association :outlet_id, factory: :outlet
association :user_id, factory: :user
#outlet_id factory: :outlet
association :commentable, factory: :outlet
end
class CommentsController < ApplicationController
def new
#comment = Comment.new
end
def create
#outlet = Outlet.find(params[:outlet_id])
#comment = #outlet.comments.build(comment_params)
#comment.user_id = User.find(params[:user_id]).id
if #comment.save
redirect_to(#outlet)
end
end
def edit
#comment = Comment.find(params[:id])
end
def update
#comment = Comment.find(params[:id])
if #comment.update(comment_params)
redirect_to #comment.outlet
end
end
def destroy
#comment = Comment.find(params[:id])
if #comment.destroy
redirect_to #comment.outlet
end
end
private
def comment_params
params.require(:comment).permit(:body, :outlet_id, :user_id)
end
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
validates :body, :user_id, :outlet_id, presence: true
validates :body, length: { in: 1..1000 }
end
Is there a particular reason for using association :user_id?
You probably want something more like:
factory :comment do
body "This is a comment"
association :outlet, factory: :outlet
association :user, factory: :user
association :commentable, factory: :outlet
end
Which incidentally can simplify to:
factory :comment do
body "This is a comment"
outlet
user
association :commentable, factory: :outlet
end
I have a User who has many Accounts through a User_Accounts model. The User_Accounts model also tracks other information such as admin and billing access. Via the user edit form, I want to be able to edit the admin and billing boolean fields for the users current account.
user.rb
class User < ActiveRecord::Base
has_one :owned_account, class_name: 'Account', foreign_key: 'owner_id'
has_many :user_accounts
has_many :accounts, through: :user_accounts
accepts_nested_attributes_for :user_accounts
end
user_account.rb
class UserAccount < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
In the users controller, I specified which user_account I wanted to edit via the nested form and assigned to the #user_account instance variable.
users_controller.rb
def edit
#user = User.find(params[:id])
#user_account = #user.user_accounts.find_by_account_id(current_account)
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user, notice: 'User was successfully updated.'
else
render action: "edit"
end
end
private
def user_params
params.require(:user).permit(:name, :email, user_accounts_attributes: [:admin, :billing] )
end
user/edit.html.erb
<%= f.fields_for :user_accounts, #user_account do |o| %>
<%= o.check_box :admin, class: 'checkbox' %>
<% end %>
When I submit the change, it successfully saves the user record, but doesn't update the User_account record. It appears to be passing the following:
{"name"=>"Colin 21", "email"=>"mike21#example.com", "user_accounts_attributes"=>{"0"=>{"admin"=>"1"}}}
Id is required to edit an object via accepts_nested_attributes_for. Seems that the id attribute is not allowed through strong parameters.
Try changing the user_params method in 'users_controller.rb'
def user_params
params.require(:user).permit(:name, :email, user_accounts_attributes: [:id, :admin, :billing] )
end
You need to add a hidden field for user account's id field within fields_for part of the form too.
There are different kinds of users in my system. One kind is, let's say, a designer:
class Designer < ActiveRecord::Base
attr_accessible :user_id, :portfolio_id, :some_designer_specific_field
belongs_to :user
belongs_to :portfolio
end
That is created immediately when the user signs up. So when a user fills out the sign_up form, a Devise User is created along with this Designer object with its user_id set to the new User that was created. It's easy enough if I have access to the code of the controller. But with Devise, I don't have access to this registration controller.
What's the proper way to create a User and Designer upon registration?
In a recent project I've used the form object pattern to create both a Devise user and a company in one step. This involves bypassing Devise's RegistrationsController and creating your own SignupsController.
# config/routes.rb
# Signups
get 'signup' => 'signups#new', as: :new_signup
post 'signup' => 'signups#create', as: :signups
# app/controllers/signups_controller.rb
class SignupsController < ApplicationController
def new
#signup = Signup.new
end
def create
#signup = Signup.new(params[:signup])
if #signup.save
sign_in #signup.user
redirect_to projects_path, notice: 'You signed up successfully.'
else
render action: :new
end
end
end
The referenced signup model is defined as a form object.
# app/models/signup.rb
# The signup class is a form object class that helps with
# creating a user, account and project all in one step and form
class Signup
# Available in Rails 4
include ActiveModel::Model
attr_reader :user
attr_reader :account
attr_reader :membership
attr_accessor :name
attr_accessor :company_name
attr_accessor :email
attr_accessor :password
validates :name, :company_name, :email, :password, presence: true
def save
# Validate signup object
return false unless valid?
delegate_attributes_for_user
delegate_attributes_for_account
delegate_errors_for_user unless #user.valid?
delegate_errors_for_account unless #account.valid?
# Have any errors been added by validating user and account?
if !errors.any?
persist!
true
else
false
end
end
private
def delegate_attributes_for_user
#user = User.new do |user|
user.name = name
user.email = email
user.password = password
user.password_confirmation = password
end
end
def delegate_attributes_for_account
#account = Account.new do |account|
account.name = company_name
end
end
def delegate_errors_for_user
errors.add(:name, #user.errors[:name].first) if #user.errors[:name].present?
errors.add(:email, #user.errors[:email].first) if #user.errors[:email].present?
errors.add(:password, #user.errors[:password].first) if #user.errors[:password].present?
end
def delegate_errors_for_account
errors.add(:company_name, #account.errors[:name].first) if #account.errors[:name].present?
end
def persist!
#user.save!
#account.save!
create_admin_membership
end
def create_admin_membership
#membership = Membership.create! do |membership|
membership.user = #user
membership.account = #account
membership.admin = true
end
end
end
An excellent read on form objects (and source for my work) is this CodeClimate blog post on Refactoring.
In all, I prefer this approach vastly over using accepts_nested_attributes_for, though there might be even greater ways out there. Let me know if you find one!
===
Edit: Added the referenced models and their associations for better understanding.
class User < ActiveRecord::Base
# Memberships and accounts
has_many :memberships
has_many :accounts, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class Account < ActiveRecord::Base
# Memberships and members
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
has_many :admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => true }
has_many :non_admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => false }
end
This structure in the model is modeled alongside saucy, a gem by thoughtbot. The source is not on Github AFAIK, but can extract it from the gem. I've been learning a lot by remodeling it.
If you don't want to change the registration controller, one way is to use the ActiveRecord callbacks
class User < ActiveRecord::Base
after_create :create_designer
private
def create_designer
Designer.create(user_id: self.id)
end
end
I have 2 models - User and Teacher. Teacher belongs_to User, User has Teacher.
So, i use Factory girl gem:
Factory.define :user do |user|
user.user_login "Another User"
user.user_role "admin"
user.password "foobar"
end
Factory.sequence :user_login do |n|
"person-#{n}"
end
Factory.define :teacher do |teacher|
...
teacher.user
end
I met problem and i don't understand how to solve that. When i create user via factory i can easily write:
#user = Factory( :user, :user_login => Factory.next(:user_login) )
And this creates user with inique login.
How can i do same thing for teacher? I tried that:
#teacher = Factory( :teacher, :user_login => Factory.next(:user_login) )
And it doesn't work.
You don't have to specify sequences separately and then pass them to another factory - you can use them inside factories like this:
Factory.define :user do |user|
# ...
user.sequence(:user_login) { |n| "person=#{n}" }
end
or shorter
Factory.define :user do
# ...
sequence(:user_login) { |n| "person=#{n}" }
end
Then, to association a user with teacher:
Factory.define :teacher do
association :user
end
Then you can just call
#teacher = Factory(:teacher)
which will automatically create the associated user with the next user_login in the sequence.
I solved that.
#teacher = Factory( :teacher,
:user => Factory(:user, :user_login => Factory.next(:user_login)) )
I have an Account model that has_one User model, and a User model that belongs_to Account model. I think that the basic code required for demonstration is:
class Account < ActiveRecord::Base
has_one :user
validates_presence_of :user
accepts_nested_attributes_for :user
end
class User < ActiveRecord::Base
belongs_to :account
# validates_presence_of :account # this is not actually present,
# but is implied by a not null requirement
# in the database, so it only takes effect on
# save or update, instead of on #valid?
end
When I define associations in each factory:
Factory.define :user do |f|
f.association :account
end
Factory.define :account do |f|
f.association :user
end
I get a stack overflow, as each is creating an account/user recursively.
The way I've been able to solve this is to emulate nested attribute forms in my tests:
before :each do
account_attributes = Factory.attributes_for :account
account_attributes[:user_attributes] = Factory.attributes_for :user
#account = Account.new(account_attributes)
end
However, I'd like to keep this logic in the factory, as it can get out of hand once I start adding other modules:
before :each do
account_attributes = Factory.attributes_for :account
account_attributes[:user_attributes] = Factory.attributes_for :user
account_attributes[:user_attributes][:profile_attributes] = Factory.attributes_for :profile
account_attributes[:payment_profile_attributes] = Factory.attributes_for :payment_profile
account_attributes[:subscription_attributes] = Factory.attributes_for :subscription
#account = Account.new(account_attributes)
end
Please help!
I was able to solve this problem by using factory_girl's after_build callback.
Factory.define :account do |f|
f.after_build do |account|
account.user ||= Factory.build(:user, :account => account)
account.payment_profile ||= Factory.build(:payment_profile, :account => account)
account.subscription ||= Factory.build(:subscription, :account => account)
end
end
Factory.define :user do |f|
f.after_build do |user|
user.account ||= Factory.build(:account, :user => user)
user.profile ||= Factory.build(:profile, :user => user)
end
end
This will create the associated classes before the owning class is saved, so validations pass.
Have a look at the factory_girl documentation. The way you're building those accounts seems like you're not really taking advantage of factory_girl.
I've always taken care of associations by creating the objects that I need before testing. I'm going to take a stab at this based on the models you're referencing above:
before :each do
#account = Factory(:account, :user_id => Factory(:user).id, :profile_id => Factory(:profile).id)
end
Now #account will have #account.user and #account.profile available. If you need to define those, #profile = #account.profile works just great.