I'm new to unit testing and Rails in general. I've decided to build my projects in a TDD environment, but this has left me with some early questions.
I need help building the models to pass this test:
describe User do
it "should add user to team" do
team = Team.create(:name => "Tigers")
akash = User.create(:name => "Akash")
akash.teams << team
akash.memberships.size.should == 1
end
it "should allow buddyup"
john = User.create(:name => "John")
john.buddyup_with(akash)
john.memberships.size.should == 1
end
it "should validate linked buddys"
akash.buddys.should include(john)
end
end
Basically, ALL I want to do right now is pass the tests. Here is what I have so far:
class Team < ActiveRecord::Base
has_and_belongs_to_many :users
attr_accessubke :name
validates :name, :presence = true
:uniqueness => true
end
class User < ActiveRecord::Base
has_and_belongs_to_many: :teams
attr_accessible :name
validates :name, :presence = true
:uniqueness => true
end
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.timestamps
end
end
def self.down
drop_table :users
end
end
class CreateTeams < ActiveRecord::Migration
def self.up
create_table :teams do |t|
t.string :name
t.timestamps
end
end
def self.down
drop_table :teams
end
end
class CreateTeamsUsersJoinTable < ActiveRecord::Migration
def self.up
create_table :teams_users, :id => false do |t|
t.integer :team_id
t.integer :user_id
end
end
def self.down
drop_table :teams_users
end
end
That is all I have so far, and clearly it is nowhere near completion. Could you provide some insight, and perhaps code I should use to complete this? My biggest problem right now is the buddyup_with part. Adding a buddy will add a person to every team you are a member of, think of teams as parts of a development company, and buddys as understudies or something.
Suggestions I would make:
Use before do
# code #
end
to set up your conditions.
Do 1 test per. You have a lot going on there :)
Use Factory Girl.
Try what you have and work from there (Agile approach, even to adding tests).
Related
This question follows up on Rails has_many :through association: save instance into join table and I am restating things here for more clarity.
In our Rails app, there are 3 models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
And here are the corresponding migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
Here is what we are trying to accomplish:
When a logged in user (current_user) creates a calendar, we should:
Create a new #calendar and save it to the Calendar table
Assign the "Creator" role to the user (current_user) for this newly created calendar through the Role column in the Administration table
Increment the total_calendar_count and the owner_calendar_count columns of the User table
In order to do that, we think we need to work on calendars#create.
In the CalendarsController, we already have the following code:
def create
#calendar = current_user.calendars.create(calendar_params)
if #calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
And we collect data from users through the following _calendar_form.html.erb form:
<%= form_for(#calendar) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Your new calendar name" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
We are considering updating the controller as follows:
def create
#calendar = current_user.calendars.create(calendar_params)
#current_user.total_calendar_count += 1
#current_user.owned_calendar_count += 1
current_user.administrations << #calendar.id
#calendar.administration.role = 'Creator'
if #calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
ActiveRecord::AssociationTypeMismatch in CalendarsController#create
Administration(#70307724710480) expected, got Fixnum(#70307679752800)
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
app/controllers/calendars_controller.rb:7:in `create'
How can we make it work?
This line is actually causing the error: current_user.administrations << #calendar.id.
current.administrations expects an object of type Administration while you are passing a Fixnum into it.
You can accomplish the same functionality in the following way:
current_user.administrations.create(calendar_id: #calendar.id)
Edit:
As OP asked in comments that it is a good practice or not. See, there is rule that says that controllers should be skinny, and models should be fatty. Well, it means you should try to write minimum code, and all the logic and fetching of objects should be there in models. But that isn't the case in your code scenario. You should move your code into model, and then call that into your controller.
Here's how:
class User < ActiveRecord::Base
def add_calendar_and_role(calendar_id, role)
self.administrations.find_by(calendar_id: calendar_id).update(role: role)
end
end
This way, your code reduces to just:
current_user.add_calendar_and_role(#calendar.id, 'Creator')
And on the same way, you can further refactor your controller code.
I have the following 2 tables defined in migrations
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.string :phone
t.string :email
t.string :address
t.string :resume
t.timestamps
end
end
end
Class CreateResumeSections < ActiveRecordMigration
def self.up
create_table :resume_sections do |t|
t.string :section_name
t.string :html
t.timestamps
end
end
end
I have following 2 models
class User
has_many :resume_sections, :dependent => :destroy
attr_accessor :section_layout
after_save :save_sections
private
def save_sections
self.section_layout = ###Someother logic here that sets this variable
end
end
class ResumeSection
belongs_to :user
end
In my users_controller, I have the following code
class UserController < ApplicationController
def create
#user = User.new(params[:user])
#user.save
#user.section_layout.each {|key,value|
rs = ResumeSection.new(:section_name => key, :html => value, :user => #user)
rs.save
}
end
end
In my view I have the following code
<% #user.resume_sections.each do |section| %>
<%= section.section_name %>
<%= section.html %>
<% end %>
I get Undefined method error for Nil:NilClass in the view. The expression #user.resume_sections is not returning to me the records that I just created and saved in the UsersController. Instead it returns nil to me. When I check the database the records are there.
Is the expression #user.resume_sections the correct expression to access these records?
Thanks
Paul
It seems to me that your you missed something in you migrations. ResumeSection needs to have and integer field called user_id. Just create a new migration that has something like this in it:
def change
add_column :resume_section, :user_id, :integer
end
I have two models ForumThread and Post set-up like this:
class ForumThread < ActiveRecord::Cached
has_many :posts
end
class Post < ActiveRecord::Cached
end
class CreateForumThreads < ActiveRecord::Migration
def self.up
create_table :forum_threads do |t|
t.column :thread_name, :text
end
add_index :forum_threads, :thread_name
end
def self.down
drop_table :forum_threads
end
end
class CreatePosts < ActiveRecord::Migration
def self.up
create_table :posts do |t|
t.column :post_body, :text
t.integer :forum_thread_id, :null => false
t.integer :priority
end
end
def self.down
drop_table :posts
end
end
I'd like to create a query that returns all forum threads where there's at least one post in each thread with priority of one. How do I create this query?
I've been considering something like ForumThread.joins(:posts).select(:priority => 1). I'm relatively new to Active Record (and totally new to joins) so any help is appreciated.
ForumThread.joins(:posts).where(:posts => {:priority => 1})
see join with conditions
First of all you should rename thread_id field to forum_thread_id in posts table and add posts_count to forum_threads table.
In Post class add belongs_to :forum_thread, :counter_cache => true
Now you can query ForumThread.where("posts_count > ?", 1).joins(:posts).where("posts.priority = ?", 1) which will return you a collection of posts.
I've been searching for a while now, but google isn't really helping me.
The ArgumentError Unknown key(s): client_id appears in the ProjectsController:
# projects_controller.rb
class Management::ProjectsController < Management::ManagementController
def index
#projects = Project.find( :client_id => current_user.client )
end
end
This is the project model:
# project.rb
class Project < ActiveRecord::Base
belongs_to :client
end
This is the client model:
# client.rb
class Client < ActiveRecord::Base
has_many :projects
end
And finally, the migration:
# 20110404155917_create_projects.rb
class CreateProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.string :name
t.datetime :date
t.text :description
t.integer :client_id
t.timestamps
end
end
def self.down
drop_table :projects
end
end
Should be possible, right?
Can't see what I'm missing here..
Anyone got a suggestion?
Thanks!
Use
#projects = Project.where( :client_id => current_user.client.id)
or
#projects = Project.find_by_client_id(current_user.client.id)
or you could do
#projects = current_user.client.projects
Little bit cleaner perhaps?
Lets say I have two tables.
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :type, :default => 'User'
t.string :user_name, :null => false
t.boolean :is_registered, :default => true
# ... many more fields
end
end
end
class CreateContactInfo < ActiveRecord::Migration
def self.up
create_table :contact_info do |t|
t.integer :resource_id
t.string :resource_type
t.string :first_name
t.string :last_name
t.string :middle_initial
t.string :title
end
end
end
class ContactInfo < ActiveRecord::Base
belongs_to :contactable, :polymorphic => true
end
class User < ActiveRecord::Base
has_one :contact_info, :as => :contactable
# composed_of :contact_info # ... It would be nice if magics happened here
end
I would like to have the User's contact_info automatically merged into my User object as attributes of the user object without having to say #user.contact_info.first_name; instead, I would prefer to be able to write #user.first_name.
The reason I am breaking out attributes to the contact_info table is that these are common attributes to multiple models. That is why I am making setting up the contact_info as a polymorphic association.
Does anyone know of a good way to aggregate/merge the attributes of contact_info directly into my user model?
Use delegate:
class User < ActiveRecord::Base
has_one :contact_info, :as => :contactable
delegate :name, :name=, :email, :email=, :to => :contact_info
end
Not necessarily a good way to do it, but I did something similar by overriding the method_missing method and then calling my aggregated object. So, it would look something like:
class User
def method_missing(method_id)
self.contact_info.send(method_id)
end
end
Edit 1: Better implementation (I think this will work):
class User
alias_method :orig_method_missing, :method_missing
def method_missing(method_id)
if (self.contact_info.respond_to?(method_id))
self.contact_info.send(method_id)
else
orig_method_missing(method_id)
end
end
end
The above has the advantage that all other unknown method calls will get passed correctly.
I finally got it! Thank you both amikazmi and Topher Fangio. I had to implement both the delegate and method_missing techniques to get this to work.
Here is the total madness that finally ended up working for me! If anybody has suggestions on how to further improve this, I'd love to hear your suggestions.
class User < ActiveRecord::Base
attr_accessible *([:user_name, :udid, :password, :password_confirmation, :contact_info] + ContactInfo.accessible_attributes.to_a.map {|a| a.to_sym})
has_one :contact_info, :as => :contactable
def method_missing(method_id, *args)
if (!self.respond_to?(method_id) && self.contact_info.respond_to?(method_id))
self.contact_info.send(method_id, *args)
elsif (!self.class.respond_to?(method_id) && ContactInfo.respond_to?(method_id))
ContactInfo.send(method_id, *args)
else
super(method_id, *args)
end
end
# delegating attributes seems redundant with the method_missing above, but this secret sauce works.
ContactInfo.accessible_attributes.to_a.each do |a|
delegate a.to_sym, "#{a}=".to_sym, :to => :contact_info
end
def initialize(*args)
options = args.extract_options!
contact_attrs = ContactInfo.accessible_attributes.to_a.map{|a| a.to_sym}
#ci = ContactInfo.new(options.reject {|k,v| !contact_attrs.include?(k) })
super(*(args << options.reject { |k,v| contact_attrs.include?(k) }.merge(:contact_info => #ci) ) )
self.contact_info = #ci
end
validates_presence_of :user_name
validates_uniqueness_of :user_name
validates_associated :contact_info
def after_save
# automatically save the contact info record for the user after the user has been saved.
self.contact_info.save!
end
end
class ContactInfo < ActiveRecord::Base
set_table_name "contact_info"
belongs_to :contactable, :polymorphic => true
validates_presence_of :email
validates_uniqueness_of :email
attr_accessible :first_name,
:last_name,
:middle_initial,
:title,
:organization_name,
:email,
:email_2,
:twitter_name,
:website_url,
:address_1,
:address_2,
:city,
:state,
:zip,
:phone_work,
:phone_mobile,
:phone_other,
:phone_other_type
def full_name
[self.first_name, self.last_name].compact.join(' ')
end
end