rails has many through where query - sql

My model structure is set up like this,
class Conversation < ActiveRecord::Base
has_many :conversation_statuses, :dependent => :destroy
has_many :users, :through => :conversation_statuses
has_many :messages, :inverse_of => :conversation
accepts_nested_attributes_for :messages
end
class User < ActiveRecord::Base
has_many :conversation_statuses, :dependent => :destroy
has_many :conversations, :through => :conversation_statuses
has_many :messages, :inverse_of => :user
end
class ConversationStatus < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
end
I am trying to create a conversation only if the users don't have a conversation going. I have conversation set up so that more than 2 users can have a conversation. Right now my query is finding conversations including the user ids, but that may include conversations with large groups. My controller query looks like this,
class ConversationsController < ApplicationController
before_filter :set_user_ids_param, :only => :create
def create
#conversation = Conversation.joins(:users)
.where(:users => {:id => params[:conversation][:user_ids]})
.first_or_initialize
#conversation.attributes = conversation_params
if #conversation.save
render :json => { :html => render_new_conversation_form }
return
end
render :status => :bad_request, :json => {
:html => render_conversation_form
}
end
private
def conversation_params
params.require(:conversation).permit([
:user_ids => [],
:messages_attributes => [
:content,
:topic
]
])
end
def set_user_ids_param
return if params[:conversation].blank?
return if params[:conversation].blank? || params[:conversation][:user_ids].blank?
params[:conversation][:user_ids] = params[:conversation][:user_ids].split(',')
params[:conversation][:user_ids].push(current_user.id)
end
def render_new_conversation_form
render_to_string({
:partial => 'conversations/form',
:locals => {
:conversation => #conversation
}
})
end
def render_conversation_form
render_to_string({
:partial => 'conversations/form',
:locals => {
:conversation => #conversation
}
})
end
end
and my form looks like this
= simple_form_for conversation, :html => {:class => 'conversation-form'} do |form|
= form.input :user_ids, :as => :hidden, :input_html => {:class => 'user-ids'}
= form.simple_fields_for :messages do |message_fields|
= render 'messages/fields', :message_fields => message_fields
%button.button.radius.submit.no-margin
Send Message
%button.button.secondary.radius.cancel.no-margin
Cancel
Any ideas on the best way to handle this?

The method that will resolve your problem should be in class ConversationStatus, which will give you the conversation_id of the Conversation, if it exists.
You'll need two sets:
containing_all_users are the conversations that contain all the users in users_id
with_extra_users are the conversations that have more users than those in users_id
The conversation (if exists), is the result of the subtraction of those sets.
class ConversationStatus < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
def self.find_between(user_ids)
containing_all_users = group(:conversation_id)
.where(user_id: user_ids)
.having(['COUNT(user_id) = ?', user_ids.length])
.pluck(:conversation_id)
with_extra_users = group(:conversation_id)
.having(['COUNT(user_id) > ?', user_ids.length])
.pluck(:conversation_id)
(containing_all_users - with_extra_users).first
end
end
You'll get the conversation from the Conversation model. You should set the attributes on this method too:
class Conversation < ActiveRecord::Base
has_many :conversation_statuses, dependent: :destroy
has_many :users, through: :conversation_statuses
def self.find_between(user_ids, attributes = {})
conversation_id = ConversationStatus.find_between(user_ids)
Conversation.where(id: conversation_id).first_or_initialize(attributes)
end
end
On the controller you will call it like this:
#conversation = Conversation.find_between(params[:conversation][:user_ids],
conversation_params)

Related

Friendship has_many through model with multiple status'

Currently my User model has the following code:
has_many :notifications
has_many :friendships
has_many :friends, :through => :friendships, :conditions => { status: 'accepted' }
has_many :requested_friends, :through => :friendships, :source => :friend, :conditions => { status: 'requested' }
has_many :pending_friends, :through => :friendships, :source => :friend, :conditions => { status: 'pending' }
And my Friendship model is as follows:
belongs_to :user
belongs_to :friend, :class_name => "User"
def self.request(user, friend)
unless user == friend or Friendship.exists?(user_id: user, friend_id: friend)
transaction do
Friendship.create(:user => user, :friend => friend, :status => 'pending')
Friendship.create(:user => friend, :friend => user, :status => 'requested')
end
else
return "failed"
end
end
def self.accept(user, friend)
unless user == friend or Friendship.exists?(user_id: user, friend_id: friend)
transaction do
accepted_at = Time.now
accept_one_side(user, friend, accepted_at)
accept_one_side(friend, user, accepted_at)
end
else
return "failed"
end
end
def self.accept_one_side(user, friend, accepted_at)
request = find_by_user_id_and_friend_id(user, friend)
request.status = 'accepted'
request.accepted_at = accepted_at
request.save!
end
When I try to run my cucumber tests, however, I am getting this error:
SQLite3::SQLException: no such column: users.status: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users"."id" = "friendships"."friend_id" WHERE "users"."status" = 'accepted' AND "friendships"."user_id" = ? (ActionView::Template::Error)
I think that this means it is trying to only include inside in for example pending_friends, users which have the attribute status = "pending", where it should actually be including users who belong to friendships which have attribute status = "pending"
Is this right? How would I go about fixing this?
I can't find any documentation on use of :conditions with has_many, but based on the error that's being generated, I gather that the conditions specified are assumed to apply to the model that is subject of the has_many, not the target model or the model through which it is being reference.
I have updated to the following and this works:
has_many :notifications
has_many :friendships
has_many :accepted_friendships, :class_name => "Friendship", :conditions => {status: 'accepted'}
has_many :requested_friendships, :class_name => "Friendship", :conditions => {status: 'requested'}
has_many :pending_friendships, :class_name => "Friendship", :conditions => {status: 'pending'}
has_many :friends, :through => :accepted_friendships
has_many :requested_friends, :through => :requested_friendships, :source => :friend
has_many :pending_friends, :through => :pending_friendships, :source => :friend
If anyone has a different approach without having to create accepted_friendshis, requested_friendships, and pending_friendships, however, I would love to hear it!
status is a column for friendships table.
So when you are writing the code, then mention the table name or else it will take the table of the Current model.
has_many :friends, -> { where "friendships.status = 'accepted'" }, :through => :friendships

Nested Form: Can’t populate join table between parent and child if child exists / Couldn't find Child with ID=1 for ParentChildJoin with ID=

I cleaned up my code, it looks much nicer now, but still doesn’t work. It starts to be a pain…
I just can’t save a parent with an existing child in nested form with parent has_many childs :through joinmodel.
In my case a Project has_many contributing Teachers and many contributing Pupils, both are Join-Models to Users. A Project has_many Schools as well.
(May be I should better name the models Teacherize and Pupilize or ProjectTeacher and ProjectPupil.)
As long as all records are new it all works fine. As soon as I want to connect an existing User as new Teacher of a new Project I get the following error:
Couldn't find User with ID=1 for Teacher with ID=
(1 is the correct user ID)
The problem should be somewhere her in my helper to setup empty form fields:
At least I guess so...
module ProjectsHelper
def setup_project(project)
if project.teachers.length <= 0 # usually there is just one teacher, so add one if there isn't one
teacher = project.teachers.build(:role_in_project => 'master')
if user_signed_in?
#teacher = project.teachers.new(:role_in_project => 'master', :user => current_user)
teacher.user = current_user # associate first teacher with current_user
else
#teacher = project.teachers.build
teacher.user = User.new # associate first teacher with a new user instance
end
end
if project.project_schools.length <= 0 # usually there is just one school, so add one if there isn't one
project_school = project.project_schools.build
project_school.school = School.new
end
if project.pupils.length < 3 # There can be up to 3 people, so add a blank fieldset as long as there are less than 3
pupil = project.pupils.build
pupil.user = User.new
end
project
end
end
These are my params received:
{"utf8"=>"✓", "authenticity_token"=>"uCCMk/s3SpDfR7+fXcsCOHPvfvivBQv8pVFVhdh6iro=",
"project"=>{
"teachers_attributes"=>{
"0"=>{
"id"=>"",
"user_attributes"=>{
"id"=>"1",
"gender"=>"male",
"title"=>"",
"firstname"=>"Firstname1",
"name"=>"Lastname1",
"faculty"=>"",
"fon"=>"",
"fax"=>""}
}
},
"id"=>"",
"title"=>"First Project",
"description"=>"This is a foo bar project!",
"presentation_type"=>"experimentell",
"note"=>""
},
"commit"=>"Register Project",
"action"=>"create",
"controller"=>"projects"
}
The case isn’t too abstract; it has to be possible to achieve it.
It’s just to connect a new parent record with an existing child record!
In this article, which is very good, exactly the case is explaint:
http://rubysource.com/complex-rails-forms-with-nested-attributes
# app/helpers/form_helper
module FormHelper
def setup_user(user)
user.address ||= Address.new
(Interest.all - user.interests).each do |interest|
user.interest_users.build(:interest => interest)
end
user.interest_users.sort_by! {|x| x.interest.name }
user/tmp/clean-controllers.md.html
end
end
There the interest is existing and gets connected through a new record in interest_uesers.
Why do I get the error when trying to do the same thing?
project.teachers.build(:user => current_user)
I studied several articles and casts, but none of them connect existing childs.
http://railscasts.com/episodes/196-nested-model-form-revised
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Rails 3.1+ Nested Forms Issue: Can't mass-assign protected attributes
Trying to use accepts_nested_attributes_for and has_and_belongs_to_many but the join table is not being populated
Quote: “accepts_nested_fields_for is used to create and modify related objects in a form. It can be used to populate join table, which is kind of what you're trying to do. However, using accepts_nested_fields_for to populate the join table is impossible with a HABTM relationship.”
That’s what I wanna do! Populate the join table!
It starts to be frustrating and I’d be glad to get some help!
My Models
class Project < ActiveRecord::Base
attr_accessible :title, :description, :presentation_type, :note,
:project_schools_attributes, :schools_attributes, :teachers_attributes, :pupils_attributes,
:users_attributes
validates_presence_of :title
validates_presence_of :description
validates_presence_of :presentation_type
has_many :project_schools, :dependent => :destroy
accepts_nested_attributes_for :project_schools
has_many :schools, :through => :project_schools
#accepts_nested_attributes_for :schools, :reject_if => :all_blank
has_many :pupils, :dependent => :destroy
accepts_nested_attributes_for :pupils, :reject_if => :all_blank
has_many :users, :through => :pupils
has_many :teachers, :dependent => :destroy
accepts_nested_attributes_for :teachers, :reject_if => :all_blank
has_many :users, :through => :teachers
#accepts_nested_attributes_for :users, :reject_if => :all_blank
end
class ProjectSchool < ActiveRecord::Base
attr_accessible :role_in_project, :comment,
:school_attributes, :school_id, :project_id
belongs_to :school
accepts_nested_attributes_for :school
belongs_to :project
end
class School < ActiveRecord::Base
attr_accessible :email, :fax, :fon, :name, :place, :street, :type_of_school, :www, :zip
has_many :project_schools
has_many :projects, :through => :project_schools
has_many :users # in real live they are named teachers and pupils but in this case the association goes directly to a user_id, not to teacher/pupil model
validates_presence_of :name, :type_of_school, :street, :place, :zip, :fon
validates :email, :format => { :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }, :allow_blank => true
end
class Teacher < ActiveRecord::Base
attr_accessible :role_in_project, :user, :user_attributes, :project_id, :user_id
belongs_to :project
belongs_to :user
accepts_nested_attributes_for :user
serialize :role_in_project
end
class Pupil < ActiveRecord::Base
attr_accessible :classname, :user_attributes #, :project_id, :user_id
belongs_to :project
belongs_to :user
accepts_nested_attributes_for :user
end
class User < ActiveRecord::Base
serialize :roles
belongs_to :school
has_many :teachers
has_many :pupils
has_many :projects, :through => :teachers
has_many :projects, :through => :pupils
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :gender, :firstname, :name, :street, :place, :title, :faculty, :assignment,
:classname, :zip, :fon, :fax, :school_id, :roles,
:password, :added_by_user_id, :password_confirmation, :remember_me
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable#, :validatable
after_initialize :init
def init
# the default guest user
self.roles ||= ['default'] #will set the default value only if it's nil
end
end
My controller
class ProjectsController < ApplicationController
require 'axlsx'
before_filter :load_page, only: [:show, :index, :destroy]
def new
#project = Project.new
end
def create
#project = Project.new(params[:project])
respond_to do |format|
if #project.save
sign_in(:user, #project.teachers[0].user) unless user_signed_in?
# TODO: send mail
# save as excel file in dropbox
save_in_dropbox(#project)
format.html { redirect_to #project, notice: t('project.was_created') }
else
logger.debug #project.errors.inspect
format.html { render action: "new" }
end
end
end
end
projects/_form.html.haml
%h1
= t('project.register_headline')
= simple_form_for( setup_project(#project), :html => {:class => 'form-horizontal'} )do |f|
= f.error_notification
#teachers-wrapper.well
%span.jumpanchor#lehrkraft_anchor
%fieldset.form-inputs
= f.simple_fields_for :teachers do |teacher|
= render "teacher_fields", :f => teacher
.school-wrapper.well
%span.jumpanchor#schule_anchor
%h2
Informationen zur Schule
%fieldset.form-inputs
= f.simple_fields_for :project_schools do |project_school|
= render "school_fields", :f => project_school
.project-wrapper.well
%span.jumpanchor#projekt_anchor
%h2
Informationen zum Projekt der Schüler
%fieldset.form-inputs
= f.hidden_field :id
= f.input :title, :input_html => { :class => 'span6' }
= f.input :description, :input_html => { :class => 'span6', rows: 5 }
= f.input :presentation_type, collection: ['theoretisch', 'experimentell'], as: :radio_buttons, :class => 'controls-row', :input_html => { :class => 'inline' }
.clearfix
= f.input :note, :input_html => { :class => 'span6', rows: 3 }
.pupils-wrapper.well
%span.jumpanchor#schuler_anchor
%fieldset.form-inputs
= f.simple_fields_for :pupils do |pupil|
= render "pupil_fields", :f => pupil
projects/_teacher_fields.html.haml
= f.simple_fields_for :user do |user|
=# render "teacher_user_fields", :f => user
%h2
Betreuende Lehrkraft
- if user_signed_in?
= user.input :email, :disabled => true, :input_html => {:class => 'email_validation'}
- else
= user.input :email, :autofocus => true, :input_html => {:class => 'email_validation'}, :hint => 'Dies muß Ihre eigene E-Mailadresse sein!'
.details
=# user.hidden_field :id
= user.input :id
= user.input :gender, collection: [:female, :male]
= user.input :title
= user.input :firstname
= user.input :name
= user.input :faculty
= user.input :fon
= user.input :fax
It is Rails 3.2 with Ruby 1.9.3

A FactoryGirl factory for a many-to-many self-referenced model

I've got a self-referenced user model:
class User < ActiveRecord::Base
has_and_belongs_to_many :following,
:class_name => "User",
:foreign_key => 'follower_id',
:association_foreign_key => 'following_id',
:join_table => 'followers'
has_and_belongs_to_many :followers,
:class_name => "User",
:foreign_key => 'following_id',
:association_foreign_key => 'follower_id',
:join_table => 'followers'
...
end
I'm having trouble coming up with a FactoryGirl factory to test this behavior. I've tried some variations of
factory :user do
...
factory :following do
after(:create) do |user, evaluator|
create_list(:user, evaluator.followers_count, follower: user)
end
end
end
but had no luck with it. How would you do this? Thanks for any input.

display first result in included model, RoR3

The following code displays all featured events, and all associated images of these, inside my view.
I'd like to know how I can display just the first image of each featured event.
# event model
class Event < ActiveRecord::Base
attr_accessible :title, :start_date, :end_date, :content, :is_featured, :assets_attributes
has_many :assets, :order => 'asset_order ASC'
accepts_nested_attributes_for :assets, :allow_destroy => true
end
# asset model
class Asset < ActiveRecord::Base
belongs_to :event
belongs_to :static
has_attached_file :asset, :styles => { :large => "660x270#", :medium => "300x300#", :thumb => "100x100#" }
end
# event model
class Event < ActiveRecord::Base
attr_accessible :title, :start_date, :end_date, :content, :is_featured, :assets_attributes
has_many :assets, :order => 'asset_order ASC'
accepts_nested_attributes_for :assets, :allow_destroy => true
end
# static controller
def show
#events = Event.where(:is_featured => 1).includes(:assets)
#static = Static.where(:id => params[:id]).first
...
end
# static show view
- #events.each do |event|
- event.assets.each do |asset|
=image_tag asset.asset.url(:medium)
= event.title
= event.start_date.to_date
Are you looking for something like this?
# static show view
- #events.each_with_index do |event, index|
- event.assets.each do |asset|
=image_tag asset.asset.url(:medium) unless index > 0
= event.title
= event.start_date.to_date

Rails has_many find associated items

So, got 2 models:
class Match < ActiveRecord::Base
has_many :rounds
has_many :participations
has_many :players, :through => :participations
belongs_to :clan_1, :class_name => "Clan", :foreign_key => "clan_1_id"
belongs_to :clan_2, :class_name => "Clan", :foreign_key => "clan_2_id"
belongs_to :winner, :class_name => "Clan", :foreign_key => "winner_id"
belongs_to :league
belongs_to :tournament
validates :clan_1_id, :presence => true
validates :clan_2_id, :presence => true
scope :by_league, lambda { |league| where("league_id == ?",league.id) }
scope :by_tournament, lambda { |tournament| where("tournament_id == ?",tournament.id) }
scope :played, where("played is not NULL")
scope :not_played, where("played is NULL")
end
class Clan < ActiveRecord::Base
has_many :players
has_many :rounds_won, :class_name => "Round", :foreign_key => "winner_id"
has_many :rounds_blue, :class_name => "Round", :foreign_key => "clan_blue_id"
has_many :rounds_purple, :class_name => "Round", :foreign_key => "clan_purple_id"
has_many :matches_won, :class_name => "Match", :foreign_key => "winner_id"
has_and_belongs_to_many :leagues
has_and_belongs_to_many :tournaments
def matches
Match.where("clan_1_id = ? OR clan_2_id = ?",self.id, self.id)
end
def matches_lost
matches.where("winner_id != ?", self.id)
end
def matches_drawn
matches.played.where("winner_id is NULL")
end
end
and I want to fetch all clans, which taken part in match.
You're over thinking it. Rails makes it very easy for you to do this.
class Comment < ActiveRecord::Base
belongs_to :post
end
class Post < ActiveRecord::Base
has_many :comments
end
#post.comments
If you have a column in your comment table with "modelName"_id (eg post_id) rails with automatically hook up the foreign key.
All you have to do is call #model1.model2 assuming #model1 is an instance of the model1 object.
If you want to hook up the query yourself you could use the where() method.
#comments = Comment.where(:post_id => some_id)
If you alter your associations a little bit you can utilize includes() in scopes:
class Match < ActiveRecord::Base
belongs_to :clan_1, :class_name => "Clan"
belongs_to :clan_2, :class_name => "Clan"
end
class Clan < ActiveRecord::Base
has_many :one_matches, :class_name => 'Match', :foreign_key => :clan_1_id
has_many :two_matches, :class_name => 'Match', :foreign_key => :clan_2_id
end
Then you can add this scope:
class Clan < ActiveRecord::Base
scope :participated_in_match, includes(:one_matches, :two_matches).where("matches.id IS NOT NULL")
end
This isn't tested so please let me know if you get unexpected results.
Quite simply:
model_two_object = Model_2.first # For clarity only, change to suit your needs
model_two_object.models_1