I've got the following models: Team, Member, Assignment, Role
The Team model has_many Members. Each Member has_many roles through assignments. Role assignments are Captain and Runner. I have also installed devise and CanCan using the Member model.
What I need to do is limit each Team to have a max of 1 captain and 5 runners.
I found this example, and it seemed to work after some customization, but on update ('teams/1/members/4/edit'). It doesn't work on create ('teams/1/members/new'). But my other validation (validates :role_ids, :presence => true
) does work on both update and create. Any help would be appreciated.
Update: I've found this example that would seem to be similar to my problem but I can't seem to make it work for my app.
It seems that the root of the problem lies with how the count (or size) is performed before and during validation.
For Example:
When updating a record...
It checks to see how many runners there are on a team and returns a count. (i.e. 5) Then when I select a role(s) to add to the member it takes the known count from the database (i.e. 5) and adds the proposed changes (i.e. 1), and then runs the validation check. (Team.find(self.team_id).members.runner.count > 5) This works fine because it returns a value of 6 and 6 > 5 so the proposed update fails without saving and an error is given.
But when I try to create a new member on the team...
It checks to see how many runners there are on a team and returns a count. (i.e. 5) Then when I select a role(s) to add to the member it takes the known count from the database (i.e. 5) and then runs the validation check WITHOUT factoring in the proposed changes. This doesn't work because it returns a value of 5 known runner and 5 = 5 so the proposed update passes and the new member and role is saved to the database with no error.
Member Model:
class Member < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :password, :password_confirmation, :remember_me
attr_accessible :age, :email, :first_name, :last_name, :sex, :shirt_size, :team_id, :assignments_attributes, :role_ids
belongs_to :team
has_many :assignments, :dependent => :destroy
has_many :roles, through: :assignments
accepts_nested_attributes_for :assignments
scope :runner, joins(:roles).where('roles.title = ?', "Runner")
scope :captain, joins(:roles).where('roles.title = ?', "Captain")
validate :validate_runner_count
validate :validate_captain_count
validates :role_ids, :presence => true
def validate_runner_count
if Team.find(self.team_id).members.runner.count > 5
errors.add(:role_id, 'Error - Max runner limit reached')
end
end
def validate_captain_count
if Team.find(self.team_id).members.captain.count > 1
errors.add(:role_id, 'Error - Max captain limit reached')
end
end
def has_role?(role_sym)
roles.any? { |r| r.title.underscore.to_sym == role_sym }
end
end
Member Controller:
class MembersController < ApplicationController
load_and_authorize_resource :team
load_and_authorize_resource :member, :through => :team
before_filter :get_team
before_filter :initialize_check_boxes, :only => [:create, :update]
def get_team
#team = Team.find(params[:team_id])
end
def index
respond_to do |format|
format.html # index.html.erb
format.json { render json: #members }
end
end
def show
respond_to do |format|
format.html # show.html.erb
format.json { render json: #member }
end
end
def new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #member }
end
end
def edit
end
def create
respond_to do |format|
if #member.save
format.html { redirect_to [#team, #member], notice: 'Member was successfully created.' }
format.json { render json: [#team, #member], status: :created, location: [#team, #member] }
else
format.html { render action: "new" }
format.json { render json: #member.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #member.update_attributes(params[:member])
format.html { redirect_to [#team, #member], notice: 'Member was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #member.errors, status: :unprocessable_entity }
end
end
end
def destroy
#member.destroy
respond_to do |format|
format.html { redirect_to team_members_url }
format.json { head :no_content }
end
end
# Allow empty checkboxes
# http://railscasts.com/episodes/17-habtm-checkboxes
def initialize_check_boxes
params[:member][:role_ids] ||= []
end
end
_Form Partial
<%= form_for [#team, #member], :html => { :class => 'form-horizontal' } do |f| %>
#...
# testing the count...
<ul>
<li>Captain - <%= Team.find(#member.team_id).members.captain.size %></li>
<li>Runner - <%= Team.find(#member.team_id).members.runner.size %></li>
<li>Driver - <%= Team.find(#member.team_id).members.driver.size %></li>
</ul>
<div class="control-group">
<div class="controls">
<%= f.fields_for :roles do %>
<%= hidden_field_tag "member[role_ids][]", nil %>
<% Role.all.each do |role| %>
<%= check_box_tag "member[role_ids][]", role.id, #member.role_ids.include?(role.id), id: dom_id(role) %>
<%= label_tag dom_id(role), role.title %>
<% end %>
<% end %>
</div>
</div>
#...
<% end %>
Try
class Member < ActiveRecord::Base
...
def validate_runner_count
if self.team.members.runner.count > 5
errors.add(:role_id, 'Error - Max runner limit reached')
end
end
def validate_captain_count
if self.team.members.captain.count > 1
errors.add(:role_id, 'Error - Max captain limit reached')
end
end
end
Related
I'm trying to setup a rails application that has a Business model and a Category model. A business can have multiple categories and the categories can belong to multiple businesses.
Everything appears to be working properly and I am not getting any errors, except when trying to show the categories associated with a specific business nothing is displaying.
Below are my models, controllers and views. This is one of my first rails projects and am looking for a little help.
business.rb
class Business < ActiveRecord::Base
attr_accessible :category_id
belongs_to :category
end
category.rb
class Category < ActiveRecord::Base
attr_accessible :name, :description
has_many :businesses
end
business_controller.rb
def show
#business = Business.find(params[:id])
#categories = Category.where(:business_id => #business).all
respond_to do |format|
format.html # show.html.erb
format.json { render json: #business }
end
end
def new
#business = Business.new
#categories = Category.order(:name)
respond_to do |format|
format.html # new.html.erb
format.json { render json: #business }
end
end
def edit
#business = Business.find(params[:id])
#categories = Category.order(:name)
end
business/_form.html.erb
...
<div class="field">
<%= f.association :category, input_html: { class: 'chosen-select', multiple: true } %>
</div>
...
business/show.html.erb
...
<ul class="tags">
<% #categories.each do |category| %>
<li><%= category.name %></li>
<% end %>
</ul>
...
Given that you want a business to have many categories, the relationships for your models should be updated as follows:
Business
def Business < ActiveRecord::Base
has_many :categories
attr_accessible :name, :etc
# attr_accessible :category_id would not apply as the business model
# would not have this relationship
end
Category
def Category < ActiveRecord::Base
attr_accessible :business_id, :name, :description
belongs_to :business
end
Then, in your controller you can can access the data:
BusinessesController
def show
#business = Business.find(params[:id])
#categories = #business.categories
respond_to ...
end
I am sorry because this look like a doubled post, but i saw a lot of other threads and i cant understand anything that i am doing.
I am trying to make an has_and_belongs_to_many but i am stuck.
I managed to make the form display the right information, but i dont know how to save it.
I got:
Orb class:
class Orb < ActiveRecord::Base
attr_accessible :descr, :id, :nome, :orb_type_id, :orbt
validates_presence_of :nome, :orb_type_id
validates :nome, :uniqueness => true
belongs_to :orb_type
has_and_belongs_to_many :books
end
Book class:
class Book < ActiveRecord::Base
attr_accessible :dataf, :datai, :descr, :id, :nome
validates_presence_of :nome
validates :nome, :uniqueness => true
has_and_belongs_to_many :orbs
# allows project page to add items via checkboxes
accepts_nested_attributes_for :orbs
end
A _form:
<% #book.each do |book| %>
<div>
<%= check_box_tag "orb[book_ids][]", book.id, #orb.books.include?(book), id: dom_id(book) %>
<%= book.nome %>
</div>
<% end %>
And the controller:
def new
#book = Book.all
#orb = Orb.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #orb }
end
end
# GET /orbs/1/edit
def edit
#orb = Orb.find(params[:id])
#book = Book.all
end
# POST /orbs
# POST /orbs.json
def create
#orb = Orb.new(params[:orb])
respond_to do |format|
if #orb.save
format.html { redirect_to #orb, notice: 'save was successful' }
format.json { render json: #orb, status: :created, location: #orb }
else
format.html { render action: "Novo" }
format.json { render json: #orb.errors, status: :unprocessable_entity }
end
end
end
# PUT /orbs/1
# PUT /orbs/1.json
def update
params[:orb][:book_ids] ||= []
#orb = Orb.find(params[:id])
respond_to do |format|
if #orb.update_attributes(params[:orb])
format.html { redirect_to #orb, notice: 'save was successful' }
format.json { head :no_content }
else
format.html { render action: "Editar" }
format.json { render json: #orb.errors, status: :unprocessable_entity }
end
end
end
With this, the form has a checkbox with the right values, but it wont be save anywere.
I dont know what i am doing, can some one me explain what i have to do?
You need a join table to use has_and_belongs_to_many
class CreateTableBooksOrbs < ActiveRecord::Migration
def change
create_table :books_orbs, :id => false do |t|
t.references :orb, :null => false
t.references :book, :null => false
end
add_index :books_orbs, [:book_id, :orb_id], :unique => true
end
end
see here my working version : https://github.com/senayar/books
I want a User(x) to be able to add another User(y) as a friend while User(x) is on User(y's) Profile Page. I set up a has_many_through and everything works except that I can only add a friend from the User Index View. Thank you in advance...The code is below:
Also:
I wanted to place the "friend" link on the view/profile/show.html.erb. When I added #users = User.all to the existing profiles_controller.rb I received the error - undefined method friendships' for nil:NilClass. When I replaced #user = User.find(params[:id]) with #users = User.all I received the error - NoMethodError in Profiles#show... undefined methodinverse_friends' for nil:NilClass
The Code that works in UserIndexView but not ProfileShowView:
% for user in #users %>
<div class="user">
<p>
<strong><%=h user.email %> <%= user.id %></strong>
<%= link_to "Add Friend", friendships_path(:friend_id => user), :method => :post%>
<div class="clear"></div>
</p>
</div>
<% end %>
The following error occurs:
NoMethodError in Profiles#show
Showing /Users/mgoff1/LOAP_1.2.2/app/views/profiles/show.html.erb where line #13 raised:
undefined method `each' for nil:NilClass
Extracted source (around line #13):
10:
11:
12:
13: <% for user in #users %>
14: <div class="user">
15: <p>
16: <strong><%=h user.email %> <%= user.id %></strong>
. . .
app/views/profiles/show.html.erb: 13:in`_app_views_profiles_show_html_erb___2905846706508390660_2152968520'
app/controllers/profiles_controller.rb:19:in `show'
The code to the rest is below.
friendship.rb
class Friendship < ActiveRecord::Base
attr_accessible :create, :destroy, :friend_id, :user_id
belongs_to :user
belongs_to :friend, :class_name => "User"
end
user.rb
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :profile_attributes
# attr_accessible :title, :body
has_one :profile
accepts_nested_attributes_for :profile
before_save do | user |
user.profile = Profile.new unless user.profile
end
end
friendships_controller.rb
class FriendshipsController < ApplicationController
def create
#friendship = current_user.friendships.build(:friend_id => params[:friend_id])
if #friendship.save
flash[:notice] = "Added friend."
redirect_to current_user.profile
else
flash[:error] = "Unable to add friend."
redirect_to root_url
end
end
def destroy
#friendship = current_user.friendships.find(params[:id])
#friendship.destroy
flash[:notice] = "Removed friendship."
redirect_to current_user.profile
end
end
users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def index
#users = User.all
end
end
profiles_controller.rb
class ProfilesController < ApplicationController
# GET /profiles
# GET /profiles.json
def index
#profiles = Profile.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #profiles }
end
end
# GET /profiles/1
# GET /profiles/1.json
def show
#user = User.find(params[:id])
#profile = Profile.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #profile }
end
end
# GET /profiles/new
# GET /profiles/new.json
def new
#profile = Profile.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #profile }
end
end
# GET /profiles/1/edit
def edit
#user = User.find(params[:id])
#profile = Profile.find(params[:id])
end
# POST /profiles
# POST /profiles.json
def create
#profile = Profile.new(params[:profile])
respond_to do |format|
if #profile.save
format.html { redirect_to #profile, notice: 'Profile was successfully created.' }
format.json { render json: #profile, status: :created, location: #profile }
else
format.html { render action: "new" }
format.json { render json: #profile.errors, status: :unprocessable_entity }
end
end
end
# PUT /profiles/1
# PUT /profiles/1.json
def update
#profile = Profile.find(params[:id])
respond_to do |format|
if #profile.update_attributes(params[:profile])
format.html { redirect_to #profile, notice: 'Profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #profile.errors, status: :unprocessable_entity }
end
end
end
# DELETE /profiles/1
# DELETE /profiles/1.json
def destroy
#profile = Profile.find(params[:id])
#profile.destroy
respond_to do |format|
format.html { redirect_to profiles_url }
format.json { head :no_content }
end
end
end
routes.rb
BaseApp::Application.routes.draw do
resources :friendships
resources :profiles
#get "users/show"
devise_for :users, :controllers => { :registrations => "registrations" }
resources :users
match '/show', to: 'profile#show'
match '/signup', to: 'users#new'
root to: 'static_pages#home'
match '/', to: 'static_pages#home'
. . .
You aren't setting #users in ProfilesController#show.
for object in collection just calls collection.each do |object|, which is why you're getting undefined method 'each' for NilClass (and also why it's generally discouraged to use that syntax, as it creates confusing errors like this one).
profiles_controller.rb
def show
#users = User.all
#...
end
Anytime you try to call methods with no actual object you'll get the 'method undefined'.
It means that the method IS defined - but you have a 'nil' and are trying to call it on that and that method doesn't exists for the 'nil' object.
Please check your actual users table. You'll need users to work with. Please verify that you have some.
If necessary you can create users (at the script/rails console) with
User.new(:name=>'fred', :password =>'pword', :password_confirmation => 'pword' )
You can also place this in your db/seeds.db file so you can run rake db:seed the first time you set the application up on a new machine.
In my app there is an association problem, which I'm unable to fix.
My app is quite simple: There's an Article model; each article has_many comments, and each of those comments has_many votes, in my case 'upvotes'.
To explain the way I designed it, I did a comments scaffold, edited the comment models and routes to a nested resource, everything works fine. Now, I basically did the same process again for 'upvotes' and again edited model and routes to make this a nested resource within the comment nested resource. But this fails at the following point:
NoMethodError in Articles#show
Showing .../app/views/upvotes/_form.html.erb where line #1 raised:
undefined method `upvotes' for nil:NilClass
My _form.html.erb file looks like this:
<%= form_for([#comment, #comment.upvotes.build]) do |f| %>
<%= f.hidden_field "comment_id", :value => :comment_id %>
<%= image_submit_tag "buttons/upvote.png" %>
<% end %>
Why is 'upvotes' undefined in this case, whereas here:
<%= form_for([#article, #article.comments.build]) do |form| %>
rest of code
everything works totally fine? I copied the same mechanism but with #comment.upvotes it doesn't work.
My upvotes_controller:
class UpvotesController < ApplicationController
def new
#upvote = Upvote.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #upvote }
end
end
def create
#article = Article.find(params[:id])
#comment = #article.comments.find(params[:id])
#upvote = #comment.upvotes.build(params[:upvote])
respond_to do |format|
if #upvote.save
format.html { redirect_to(#article, :notice => 'Voted successfully.') }
format.xml { render :xml => #article, :status => :created, :location => #article }
else
format.html { redirect_to(#article, :notice =>
'Vote failed.')}
format.xml { render :xml => #upvote.errors, :status => :unprocessable_entity }
end
end
end
end
I'm sorry for this much code.., my articles_controller: (extract)
def show
#upvote = Upvote.new(params[:vote])
#article = Article.find(params[:id])
#comments = #article.comments.paginate(page: params[:page])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #article }
end
end
And my 3 models:
class Article < ActiveRecord::Base
attr_accessible :body, :title
has_many :comments
end
class Comment < ActiveRecord::Base
attr_accessible :content
belongs_to :user
belongs_to :article
has_many :upvotes
end
class Upvote < ActiveRecord::Base
attr_accessible :article_id, :comment_id, :user_id
belongs_to :comment, counter_cache: true
end
Upvote migration file:
class CreateUpvotes < ActiveRecord::Migration
def change
create_table :upvotes do |t|
t.integer :comment_id
t.integer :user_id
t.timestamps
end
end
end
My routes:
resources :articles do
resources :comments, only: [:create, :destroy] do
resources :upvotes, only: [:new, :create]
end
end
Sorry for that much code. If anyone might answer this, they would be so incredibly awesome!
Thank you in advance!
Why is 'upvotes' undefined in this case, whereas here:
This is because you're calling upvotes on a nil object, the comment doesn't exist yet.
Best thing to do would be looking into nested attributes:
http://guides.rubyonrails.org/2_3_release_notes.html#nested-attributes
http://guides.rubyonrails.org/2_3_release_notes.html#nested-object-forms
Your error message, says that you try call upvotes on nil. Specifically it is a part of code #comment.upvotes.build in your /app/views/upvotes/_form.html.erb view.
You have to fix show action in you ArticlesController, by adding #comment (with contents) variable.
def show
#upvote = Upvote.new(params[:vote])
#article = Article.find(params[:id])
#comments = #article.comments.paginate(page: params[:page])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #article }
end
end
Also strange things are happening in UpvotesController, in create action.
#article = Article.find(params[:id])
#comment = #article.comments.find(params[:id])
#upvote = #comment.upvotes.build(params[:upvote])
Firstly you had fetched one #article using params[:id], then you had fetched all comments of that #article (throught association), where comments id is the same as #article id. Please review your code, it is inconsistent and will not work correctly.
Everything fixed and works fine now. Took a different approach and simply used Upvote.new instead of nesting it into the comments and building associations, edited my routes as well. Implemented Matthew Ford's idea
I would suspect you have many comments on the article page, the comment variable should be local e.g #article.comments.each do |comment| and then use the comment variable to build your upvote forms.
Thanks everybody for your help!
i update class_name
/models/tipotercero.rb
class Tipotercero < ActiveRecord::Base
has_many :terceroclasificaciones
has_many :terceros , :class_name => "Terceroclasificacion"
end
/models/tercero.rb
class Tercero < ActiveRecord::Base
has_many :ciudades
has_many :terceroclasificaciones
has_many :tipoterceros, :class_name => "Terceroclasificacion"
end
class Terceroclasificacion < ActiveRecord::Base
belongs_to :tercero
belongs_to :tipotercero
attr_accessor :tercero_id, :tipotercero_id
end
/views/terceros/_form.html.erb
<div class="field">
<% for tipotercero in Tipotercero.all %>
<div>
<%= check_box_tag "tercero[tipotercero_ids][]", tipotercero.id, #tercero.tipoterceros.include?(tipotercero) %>
<%= tipotercero.nombre %>
</div>
<% end %>
</div>
the error is
uninitialized constant Tercero::terceroclasificacion
I have tried to follow the post
Rails 3 has_many :through Form but I could not find the error in my application
add terceros controller
/controllers/terceros_controller.rb
class TercerosController < ApplicationController
# GET /terceros
# GET /terceros.xml
def index
#terceros = Tercero.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #terceros }
end
end
# GET /terceros/1
# GET /terceros/1.xml
def show
#tercero = Tercero.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #tercero }
end
end
# GET /terceros/new
# GET /terceros/new.xml
def new
#tercero = Tercero.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #tercero }
end
end
# GET /terceros/1/edit
def edit
#tercero = Tercero.find(params[:id])
end
# POST /terceros
# POST /terceros.xml
def create
#tercero = Tercero.new(params[:tercero])
# #tercero.attributes = {'tipotercero_ids' => []}.merge(params[:tercero] || {})
respond_to do |format|
if #tercero.save
format.html { redirect_to(#tercero, :notice => 'Tercero was successfully created.') }
format.xml { render :xml => #tercero, :status => :created, :location => #tercero }
else
format.html { render :action => "new" }
format.xml { render :xml => #tercero.errors, :status => :unprocessable_entity }
end
end
end
# PUT /terceros/1
# PUT /terceros/1.xml
def update
#tercero = Tercero.find(params[:id])
respond_to do |format|
if #tercero.update_attributes(params[:tercero])
format.html { redirect_to(#tercero, :notice => 'Tercero was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #tercero.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /terceros/1
# DELETE /terceros/1.xml
def destroy
#tercero = Tercero.find(params[:id])
#tercero.destroy
respond_to do |format|
format.html { redirect_to(terceros_url) }
format.xml { head :ok }
end
end
end
edit 3
the page error is:
NameError in Terceros#new
Showing /home/andres/desarrollos/rubyonrails/proyecto/app/views/terceros/_form.html.erb where line #92 raised:
uninitialized constant Tercero::terceroclasificacion
Extracted source (around line #92):
89: <div class="field">
90: <% for tipotercero in Tipotercero.all %>
91: <div>
92: <%= check_box_tag "tercero[tipotercero_ids][]", tipotercero.id, #tercero.tipoterceros.include?(tipotercero) %>
93: <%= tipotercero.nombre %>
94: </div>
95: <% end %>
Trace of template inclusion: app/views/terceros/new.html.erb
4 edit
error to create tercero is:
NoMethodError in TercerosController#create
undefined method `type_cast' for nil:NilClass
Rails.root: /home/andres/desarrollos/rubyonrails/proyecto
Application Trace | Framework Trace | Full Trace
app/controllers/terceros_controller.rb:44:in `new'
app/controllers/terceros_controller.rb:44:in `create'
Request
Parameters:
{"tercero"=>{"identificacion"=>"1110465574",
"empresa"=>"hogar",
"tipo_identificacion"=>"2",
"direccion1"=>"dierccion",
"nombre"=>"carlos andres",
"direccion2"=>"123",
"ciudad_id"=>"2",
"telefono_fijo"=>"132233",
"telefono_movil"=>"123123",
"fecha_nacimiento(1i)"=>"2013",
"observaciones"=>"",
"fecha_nacimiento(2i)"=>"10",
"fecha_nacimiento(3i)"=>"17",
"representante_legal"=>"",
"tipotercero_ids"=>["1",
"2",
"3"],
"apellido1"=>"colonia",
"apellido2"=>"riveros",
"pagina_web"=>""},
"commit"=>"Crear Tercero",
"authenticity_token"=>"SeUoILctpNr9t6Lx8wSoHVTO5mjk0qJfnzJsb9Jtzao=",
"utf8"=>"✓"}
Sometimes other model cannot be derived from the association name just by Rails conventions and such strange errors occurs. You can pass class name like this:
has_many :terceroclasificaciones, :class_name => "Terceroclasificacion"
And btw, you can make your life easier just by using English names for your models and stuff. You do localization in your view anyways.
Try to remove this
attr_accessor :tercero_id, :tipotercero_id
I think you want it to be an attr_accessible but not attr_accessor