Build model with nested model in rspec integration test - ruby-on-rails-3

I understand that I can do something like in rspec:
let(:project) { Project.new }
but in my app a project accepts_nested_attributes_for tasks and when I generate the Project form I build a task along with it using:
#project = Project.new
#project.tasks.build
I need something like:
let(:project) { Project.new.tasks.build }
but that doesn't seem to work.
How can I do this as a let in my rspec test?

Don't sure if I understand you right..Anyway if Project and Task are models, Project has_many tasks, Task belongs to project, then (with FactoryGirl gem):
factories.rb
factory :project do
name "Foo"
end
factory :task do
name "Bar"
project
end
in spec:
let(:task) { FactoryGirl.build(:task) }
before { FactoryGirl.build(:project) }

Related

rails 3 how can i give permissions to user for specific task like tickets, messages?

i am new to rails and i want to give permissions to users for particular task involved in my project on different modules.
i have two models "user" and "project"... in "user.rb" has_many : projects
and in "project.rb" belongs_to :user. and i have one more model which combine both models named "user_project.rb" in this model have proj_id an user_id stored.
i want to give permission and after that also check for the permission to current user for creating messages and tasks according to permissions assign to them.
where can i define permissions and how it works for me in view as well....
If you created user_project.rb only for storing these ids - remove it.
This look like you need to write before_filter in your controllers
class ProjectsController << ApplicationController
before_filter :current_user_required, only: [ :edit, :update ]
#there def of actions
private
def current_user_required
unless current_user == #project.user
flash[:error] = 'error 403'
redirect_to :back
end
end
And when you find #project, for edit and update actions, you can do it like:
#project = current_user.projects.find(params[:project_id]) #need to change :project_id
If you want to create model Message(belongs_to :project and Project has_name: messages) and give access for creation and edit it only for project.user you can do it using before_filter or validation in model
class Message << ActiveRecord::Base
validate :author_is_project_user, on: :create
private
def author_is_project_user
errors.add :base, 'author not is project user' unless self.author == self.project.user
end
end
According to this you can define permission for another things
And if you want to get permission for another user you have_to create model which belongs_ to :user and :project and check in before_filter present of it.
sort of this:
class Permission << ActiveRecord::Base
belongs_to :user
belongs_to :project
scope :about, -> project { where project_id: project }
scope :of_user, -> user { where project_id: project }
end
and in User model define method like
def access_to_project? project
Permission.about(project).of_user(self).first.present?
end
or you can add variable to this model and make more complicated logic of access

ActiveRecord::AssociationTypeMismatch ruby 1.8 without using spork

I have models specs, controllers spec and request spec. When I run:
rspec spec
models spec are run first, then request and then when controller specs are run the specs for the first controller are OK, but the next fail. But when I run only the controller specs they all pass. I am with rails 3.0.9, ruby 1.8, factory_girl 2.2.0. I have
config.cache_classes = true
in test.rb and I can't change the version of ruby or factory_girl. Can someone help me?
UPDATE:
This is the error:
96) UsersController reset_password: as non-master_admin: does not reset a user's password
Failure/Error: let!(:user) { Factory(:admin_user) }
ActiveRecord::AssociationTypeMismatch:
AdminUser(#-630697398) expected, got MerchantUser(#-629918188)
# ./app/models/activity.rb:33:in `log'
# ./config/initializers/add_activity_logging.rb:8:in `_callback_after_759'
# ./spec/controllers/users_controller_spec.rb:8
in spec/controllers/users_controller_spec.rb:8:
let!(:user) { Factory(:admin_user) }
in activity.rb:33:
create(:user => user, :title => title, :changeable_id => changeable.id,
:changeable_type => changeable.class.to_s, :data => attributes)
also there is:
belongs_to :user, :class_name => 'AdminUser'
in the class AdminUser there isn't has_many activities but when I tried to add it I couldn't add it correctly I guess.
Thanks for the help
UPDATE:
AdminUser and MerchantUser are descendants of User
POSIBLE FIX
The line that gave error was actually:
admin_user = Factory(:tech_admin)
I replaced it with:
admin_user = FactoryGirl.build_stubbed(:tech_admin)
This way the file activity.rb is not reached
FINAL FIX
Apparently the problem was with Factory(:reseller). I replaced it with FactoryGirl.create(:reseller) and it everything work. Though now I am wondering and searching what is the difference between the two uses

Call FactoryGirl.create for all rspec controller tests?

is there any way you can load some factories for all controller tests? I've got a few of them which are necessary for all controller tests (menu items) and I don't like putting them all in controllers seperately. Not very DRY :)
Thanks
Maybe you can take a look at rspec's shared context, highlighted here: https://www.relishapp.com/rspec/rspec-core/v/2-11/docs/example-groups/shared-context
Create an RSpec macro.
Using user as the Factory you want to use, here's an example you'd put in spec/support/controller_macros.rb:
module ControllerMacros
let!(:user) { FactoryGirl.create :user }
end
In your spec/spec_helper.rb include the macro for controllers:
RSpec.configure do |config|
config.extend ControllerMacros, :type => :controller
end
The let! is like let as if it was referenced in a before block.
The ControllerMacros is also a handy place to add things like a sign_in method, etc.
You can also put shared examples in spec/support as well.

Generate primary keys without creating the record

I want to be able to do something like
#foo = MyClass.new
5.times do
#foo.things.build
end
But my #foo needs to have a primary key for this to work, Soo what is the best way to generate primary keys without creating the object?
The purpose for this is to be able to use nested forms more easely
form_builder.fields_for :things do ...
I believe the OP is asking for how to initialize a view action property for use in new action for a standard Rails resource. At this point, there is no ID for the main parent. The solution is simple:
The model:
class ParentObject < ActiveRecord::Base
# the child model in this example is called child_objects
has_many :child_objects, :dependent => :destroy
accepts_nested_attributes_for :child_objects
The controller action for new:
#object = Object.new :example_field => "my field"
#object.child_objects.build :name => "value_1" # pretending that name is a field
#object.child_objects.build :name => "value_2"
Then, in the view:
= form_for(#object) do |f| # top level Object
= f.label :example_field
= f.text_field :example_field
=# the next line loops twice in this example
= f.fields_for :child_objects do |child|
= child.label :name
= child.text_field :name
There is also a good gem called nested_form written by Ryan Bates (https://github.com/ryanb/nested_form) which may help you with the rest of the CRUD operations.
What you probable want is NestedAttributes
Nested attributes allow you to save attributes on associated records through the parent. By default nested attribute updating is turned off, you can enable it using the accepts_nested_attributes_for class method. When you enable nested attributes an attribute writer is defined on the model.
The implementation is different between each ORM, here is for sequel and ActiveRecord
NOTE: Full tutorial also available at Nerdgem
Sequel impementation
Imagine there is a Project class that has many tasks
class Project < Sequel::Model
one_to_many :tasks
end
class Task < Sequel::Model
many_to_one :project
end
To enable the nested attributes you will need include two plugins for the Project class
Sequel::Plugins::NestedAttributes: allows you to create, update, and delete associated objects directly by calling a method on the current object. Nested attributes are defined using the nested_attributes class method:
Sequel::Plugins::InstanceHooks: which is a dependency of NestedAttributes
You can find really good doc on the plugin site
Project.plugin :instance_hooks
Project.plugin :nested_attributes
After that is done you can call the nested_attributes method on the desired class
Project.nested_attributes :tasks
Now you can do this
p = Project.new(:title=>'Project')
p.tasks_attributes = [{:title=>'First Task'}, {:title=>'Second Task'}]
puts p.tasks.inspect
# It will output this
# [#<Task #values={:title=>"First Task"}>, #<Task #values={:title=>"Second Task"}>]
When you save the project it will save both the project and the tasks.
If you can even to edit many tasks at the same.
ActiveRecord implementation
Here is how to use it.
Imagine there is a Project class that has many tasks
Project.rb
class Project < ActiveRecord::Base
attr_accessible :title
has_many :tasks
accepts_nested_attributes_for :tasks
end
Task.rb
class Tasks < ActiveRecord::Base
attr_accessible :title, :project_id
belongs_to :project
end
Now you can do this.
p = Project.new
p.tasks_attributes=[{title: "First Task"}]
p.things
# Would output this
#=> [#<Thing id: nil, title: "First Task", created_at: nil, updated_at: nil, bar_id: nil>]
p.save
When you save the project it will save both the project and the tasks.
If you want to edit many project tasks at the same time you can to this
p.tasks_attributes=[{title: "First Task"},{title: "Second Task"}]
NOTE: there is also a Railscasts that can help you out with nested forms. Orginal Railscast, Revised Railscast

Routes in Rails3 - Controller and routes for 2 post functions

I'm trying to write an app in rails 3 and I'm having some trouble figuring out the routes and controllers for a test that I want the user to take. The basic requirements for this app are:
Users, Tests and Questions are all in separate models.
A User has_many Tests. A Test has_many Questions
Provide a link on the user_profile page to /test/new to create the test record.
Provide a link on /test/new to /test/:id/part1 (where :id is the test_id) so that the user can complete the first part of the test. Questions will be retrieved from the db and presented on this page.
Provide a link on /test/:id/part1 to /test/:id/part2 so that the user can complete the second part of the test. Again, questions are retrieved from the db.
Provide a link on /test/:id/part2 to submit the test and return to the user's profile.
I've completed the models, which even pass their tests, so I think I have finished parts 1 and 2.
user.rb
Class User < ActiveRecord::Base
has_many :tests
end
test.rb
Class Test < ActiveRecord::Base
belongs_to :user
has_many :questions
end
question.rb
Class Question < ActiveRecrod::Base
belongs_to :test
end
My issues start when I try to put these models together using routes and controllers.
routes.rb
resources :users
resources :tests do
member do
post 'part1'
post 'part2'
end
end
users/show.html.erb
<%= link_to "Start The Test", new_test_path %>
tests/new.html.erb
<%= link_to "Part 1", part1_test_path(#test) %>
tests_controler.rb
class TestsController < ApplicationController
def new
#test = Test.new(current_user)
end
def part1
# still just a stub
end
end
I'm getting this error when I click on the link to take Part 1 of the test:
No route matches {:action=>"part1", :controller=>"tests", :id=>#<Test id: nil, taken_at: nil, user_id: nil, created_at: nil, updated_at: nil>}
Any help on this would be greatly appreciated.
By defining a member of the routes it's expecting an existent test, ie. one which is saved and has an id.
e.g.
part1_test_path = /test/123/part1
What you need is a collection route.
resources :tests do
collection do
post 'part1'
end
member do
post 'part2'
end
end
e.g.
part1_test_path = /test/part1
edit
Suggested solution:
resources :test, :path_names => { :new => 'part_1', :edit => 'part_2' } *1
def new
#test = Test.new
#new view
form_for #test do
...
def create
#test = Test.new params[:test]
if #test.save
redirect_to edit_test_path #test
def edit
#test = Test.find params[:id]
#edit view
form_for #test do
def update
#test = Test.find params[:id]
if #test.update_attributes params[:test]
redirect_to test_path #test
def show # test results
#test = Test.find params[:id]
if #test.incomplete *2
redirect_to edit_test_path #test
*1 See rails guide on routing. This will give you urls like this
test/part1
test/123/part2
You should put all of your validation in the model; your requirements of test data. Conditional validation will be required, depending on whether it's a new_record? or not ie if you're at part 1 or 2.
*2
add a method to your model which checks test completeness.
def incomplete
self.some_test_field.blank?
Let me know if you don't understand anything.