Adding Multiple Categories to a Business Model in Rails - ruby-on-rails-3

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

Related

Ruby on Rails: SQL query is wrong while limiting user endorsements a month

I wanted to make that the user can endorse each user once a month and 3 users in total a month.
First of all, got a method in user model:
# Returns true if the current user is endorsing the other user.
def endorsing?(other_user)
endorsing.include?(other_user)
end
Want to slightly change it to check if the user already endorsed the user this month.
I belive it needs to look something like this:
def endorsing?(other_user)
endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).include?(other_user)
end
But that is obviously wrong cause it gives me following SQL query:
SELECT "users".* FROM "users" INNER JOIN "endorsements"
ON "users"."id" = "endorsements"."endorsed_id"
WHERE "endorsements"."endorser_id" = ? AND ("users"."created_at" BETWEEN '2016-01-01 00:00:00.000000' AND '2016-01-26 17:15:53.700307') [["endorser_id", 1]]
the "users"."created_at" should be "endorsements"."created_at"
How do I do that?
Same counts for the limit problem I've got:
def endorsement_count_within_limit?
if endorser.endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).count >= 3
errors.add(:base, "Exceeded endorse limit (3) this month")
end
end
Which gives:
SELECT COUNT(*) FROM "users" INNER JOIN "endorsements"
ON "users"."id" = "endorsements"."endorsed_id"
WHERE "endorsements"."endorser_id" = ? AND ("users"."created_at" BETWEEN '2016-01-01 00:00:00.000000' AND '2016-01-26 17:15:53.708638' [["endorser_id", 1]]
Same problem, users.created should be endorsements.created
I have no idea how to fix that, And bright ideas?
Below i'll paste my controllers, models and partials that use the methods to render forms for endorsing:
Endorsements model:
class Endorsement < ActiveRecord::Base
belongs_to :endorser, class_name: "User"
belongs_to :endorsed, class_name: "User"
validates :endorser_id, presence: true
validates :endorsed_id, presence: true
validates :comment, presence: true, length: { maximum: 140}
validate :endorsement_count_within_limit?, :on => :create
def endorsement_count_within_limit?
if endorser.endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).count >= 3
errors.add(:base, "Exceeded endorse limit (3) this month")
end
end
end
Endorsements controller:
class EndorsementsController < ApplicationController
before_action :logged_in_user
def new
end
def create
#user = User.find(params[:endorsed_id])
comment = params[:endorsement][:comment]
current_user.endorse(#user, comment)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Endorsement.find(params[:id]).endorsed
current_user.unendorse(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
User model:
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :active_endorsements, class_name: "Endorsement",
foreign_key: "endorser_id",
dependent: :destroy
has_many :passive_endorsements, class_name: "Endorsement",
foreign_key: "endorsed_id",
dependent: :destroy
has_many :endorsing, through: :active_endorsements, source: :endorsed
has_many :endorsers, through: :passive_endorsements, source: :endorser
.
.
.
# Endorses a user.
def endorse(other_user, comment)
active_endorsements.create(endorsed_id: other_user.id, comment: comment)
end
# Unendorses a user.
def unendorse(other_user)
active_endorsements.find_by(endorsed_id: other_user.id).destroy
end
# Returns true if the current user is endorsing the other user.
def endorsing?(other_user)
endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).include?(other_user)
end
private
.
.
.
end
Partials:
_endorse_form:
<% unless current_user?(#user) %>
<div id="endorse_form_<%= #user.id %>">
<% if current_user.endorsing?(#user) %>
<%= render partial: 'shared/unendorse' %>
<% else %>
<%= render partial: 'shared/endorse'%>
<% end %>
</div>
<% end %>
_endorse:
<%= form_for(current_user.active_endorsements.build, remote: true) do |f| %>
<% if f.object.endorsement_count_within_limit? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(f.object.errors.count, "error") %>.
</div>
<ul>
<% f.object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% else %>
<div><%= hidden_field_tag :endorsed_id, #user.id %></div>
<%= f.submit "Endorse", class: "btn btn-primary" %>
<%= f.text_field :comment, class: 'form-control' %>
<% end %>
<% end %>
_unendorse:
<%= form_for(current_user.active_endorsements.find_by(endorsed_id: #user.id),
html: { method: :delete },
remote: true) do |f| %>
<%= f.submit "Remove endorse", class: "btn" %>
<% end %>
If you need to look at other files it's available here with some parts missing:https://bitbucket.org/kramarz/pracainzynierska
try explicitly giving the columns for endorsments in your where statement
endorsing.where(%{
endorsments.created_at BETWEEN
'#{Time.zone.now.beginning_of_month.to_s(:db)}'
AND
'#{Time.zone.now.to_s(:db)}'
})
At first glance it looks like you need to specify active_endorsements or passive_endorsements where your examples are using endorsing, which references the endorsed user through the Endorsement.
This would all be a little less convoluted if the names were changed to better reflect what each object is. Of course, naming is much more difficult than it seems.
You might have better luck restructuring your relations like so:
class User < ActiveRecord::Base
has_many :outbound_endorsements, class_name: "Endorsement", foreign_key: "endorser_id", dependent: :destroy
has_many :inbound_endorsements, class_name: "Endorsement", foreign_key: "endorsed_id", dependent: :destroy
has_many :endorsed_users, through: :outbound_endorsements, source: :endorsed_user
has_many :endorsing_users, through: :inbound_endorsements, source: :endorsing_user
end
class Endorsement < ActiveRecord::Base
belongs_to :endorsing_user, class_name: "User"
belongs_to :endorsed_user, class_name: "User"
scope :current, -> { where(created_at: (Time.zone.now.beginning_of_month..Time.zone.now) }
end
As for the validation, this is more complex than validating data to be persisted by any single model. In these cases, it's really useful to put this logic in another object entirely:
class ValidatedEndorsement
attr_reader :endorsing_user, :endorsed_user
def initialize(endorsing_user, endorsed_user)
#endorsing_user, #endorsed_user = endorsing_user, endorsed_user
end
def valid?
# note: User -> Endorsement -> Scope -> Count = violation of the
# law of demeter; if this works well, refactor appropriately ;)
if #endorsing_user.outbound_endorsements.current.count >= 3
# no dice
return false
end
true
end
end
If this object is going to be used in response to a user request, it might be worth your while to consider making this object a FormObject that uses the underlying validations of an ActiveRecord model. This is easy to do with virtus, requiring only a few lines to change in the above ValidatedEndorsement.

Updating extra column in a has_many, :through table with Coffeescript

I've got a rather simple setup here: A Doc model, a Publication model, and an Article model.
doc.rb
class Doc < ActiveRecord::Base
attr_accessible :article_ids,:created_at, :updated_at, :title
has_many :publications, dependent: :destroy
has_many :articles, :through => :publications, :order => 'publications.position'
accepts_nested_attributes_for :articles, allow_destroy: false
accepts_nested_attributes_for :publications, allow_destroy: true
end
publication.rb
class Publication < ActiveRecord::Base
attr_accessible :doc_id, :article_id, :position
belongs_to :doc
belongs_to :article
acts_as_list
end
article.rb
class Article < ActiveRecord::Base
attr_accessible :body, :issue, :name, :page, :image, :article_print, :video, :id
has_many :publications
has_many :docs, :through => :publications
end
The Doc form lets users select and order a number of articles:
...
<% #articles.each do |article| %>
<span class="handle">[drag]</span>
<%= check_box_tag("doc[article_ids][]", article.id, #doc.articles.include?(article), :class => "article_chooser" ) %>
<a id="<%= article.id %>" class="name"><%= article.name %></a>
<% end %>
...
This Coffeescript saves the order on drag:
jQuery ->
$('#sort_articles').sortable(
axis: 'y'
handle: '.handle'
update: ->
$.post($(this).data('update-url'), $(this).sortable('serialize'))
);
And this is the sort method in docs_controller.rb:
def sort
Article.all.each_with_index do |id, index|
Publication.update_all({position: index + 1}, {id: id})
end
render nothing: true
end
I've followed this sortable lists Rails cast and it all works well until I update a Doc record because re-ordering isn't saved on update. I've come to the conclusion that it's because my sortable field is on the association table (i.e., publications), but because of the way the app works it has to be.
I've been doing some research here and found this question whose answer comes close, but because I've got a Coffeescript action saving the record in the first place it doesn't work.
Any help would be excellent, I'm really stuck.
Because I struggled for a long time with this I'm putting my stupidly simple solution here. Hopefully it helps someone else.
It's as simple as deleting the old join table records on update before the changes are saved, i.e.:
docs_controller.rb
def update
#doc = Doc.find(params[:id])
#doc.publications.destroy_all
respond_to do |format|
if #doc.update_attributes(params[:doc])
format.html { redirect_to share_url(#doc) }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #doc.errors, status: :unprocessable_entity }
end
end
end
Boom.

Rails validation count limit on has_many :through

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

Rails 3 - Building a nested resource within another nested resource (articles -> comments -> votes)

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!

Polymorphic Comments with Ancestry Problems

I am trying to roll together two Railscasts: http://railscasts.com/episodes/262-trees-with-ancestry and http://railscasts.com/episodes/154-polymorphic-association on my app.
My Models:
class Location < ActiveRecord::Base
has_many :comments, :as => :commentable, :dependent => :destroy
end
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
My Controllers:
class LocationsController < ApplicationController
def show
#location = Location.find(params[:id])
#comments = #location.comments.arrange(:order => :created_at)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #location }
end
end
end
class CommentsController < InheritedResources::Base
def index
#commentable = find_commentable
#comments = #commentable.comments.where(:company_id => session[:company_id])
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
#comment.user_id = session[:user_id]
#comment.company_id = session[:company_id]
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
In my locations show view I have this code:
<%= render #comments %>
<%= render "comments/form" %>
Which outputs properly. I have a _comment.html.erb file that renders each comment etc. and a _form.html.erb file that creates the form for a new comment.
The problem I have is that when I try <%= nested_comments #comments %> I get undefined method 'arrange'.
I did some Googling and the common solution to this was to add subtree before the arrange but that throws and undefined error also. I am guessing the polymorphic association is the problem here but I am at a loss as to how to fix it.
Dumb mistake... forgot to add the ancestry gem and required migration which I thought I had already done. The last place I checked was my model where I eventually discovered my error.