I'm still a new to Rails, so I'm finding this so frustrating. I'm trying to make a small application as part of a personal project that lets users create and join each others groups.
I've looked on StackOverflow and found two similar questions with answers that don't quite address my scenario (I've even tried using their code and still can't figure out how to go about this).
Those SO links are here:
How do I create a join action between a group and a user?
creating a join action between user and group correctly
What I'm trying to do is figure out the controller actions and erb code that lets users create and join groups. Here are two posts on SO that I've read repeatedly, I tried using their code at one point, but kept getting a
First argument in form cannot contain nil or be empty
for
<%= form_for(#membership) do |f| %>
That error came from when I was using the membership_controller code in the second SO post I listed. Here are the models I have so far, which I wrote with some help from SO as well.
user.rb (model)
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :groups, :through => :memberships
membership.rb (model)
class Membership < ActiveRecord::Base
attr_accessible :user_id, :group_id
belongs_to :user
belongs_to :group
group.rb (model)
class Group < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :users, :through => :memberships
I honestly can't figure out how to connect these three models, I've seen examples, tried to think of it for myself for more than a few hours now.
If you can help at all, I will greatly appreciate it. I'm just pulling my hair out trying to get something that I know is simple to do in Rails to work properly.
I'll just detail what I would do:
#config/routes.rb
resources :groups, only: [:show], shallow: true do
resources :memberships, only: [:new] #-> domain.com/2/memberships/new
end
This will send you to the memberships controller, which you can look like this:
#app/controllers/memberships_controller.rb
Class MembershipsController < ApplicationController
def new
#group = Group.find params[:group_id]
#membership = Membership.new({group: group})
end
def create
#group = Group.find params[:group_id]
#membership = Membership.new(membership_params)
end
private
def membership_params
params.require(:membership).merge(group_id: params[:group_id], user_id: current_user.id)
end
end
This will allow you to create the following form:
#app/views/memberships/new.html.erb
<% if user_signed_in? %>
<% if current_user.groups.include?(#group) %>
You're already a member of this group!
<% else %>
<%= form_for [#group, #membership] do |f| %>
<%= f.submit "Join" %>
<% end %>
<% end %>
<% end %>
--
You'll have your models set up with as has_many :through association (allowing you to directly affect the join model):
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through: :memberships
end
#app/models/membership.rb
Class Membership < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
#app/models/group.rb
Class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
This is not the most efficient, or probably "correct" way of doing this, but should give you something to work with
The error message implies that #membership is nil or []. You should check the following lines to see why that happened. Possibly #group does not exist
#group = Group.find_by_name(:group)
#membership = current_user.memberships.build(:group_id => #group.group_id)
Related
I'm trying to build a method that pulls an array of arrays, much like nested loops in the view.
I am trying to build a method on my User model that does this:
#past_parties = User.parties
<%= #past_parties.each do |past_party| %>
<%= past_party.guests("email").uniq.each do |guest| %>
<%= guest.name %> <%= guest.email %>
<% end %>
<% end %>
These are my models:
class User < ActiveRecord::Base
has_many :hosts, dependent: :destroy
has_many :parties, through: :hosts
def past_guests
self.parties.guests
end
end
class Host < ActiveRecord::Base
belongs_to :user
has_many :parties, dependent: :destroy
has_many :guests, through: :parties
end
class Party < ActiveRecord::Base
belongs_to :host
has_many :guests, dependent: :destroy
end
class Guest < ActiveRecord::Base
belongs_to :party
end
I can get the loop to work in the view, but when I try to build it as a method (so I can call current_user.past_guests, if current_user is an instance of User.), I get this error
undefined method `guests' for #<ActiveRecord::Associations::CollectionProxy []>
How do I get all of the guests that a user has ever invited without building the loop in the view?
The problem is that you're trying to access an array from another array:
self.parties.guests
self.parties returns an #<ActiveRecord::Associations::CollectionProxy []>, so if you want to get the guests of the parties you have to loop over the elements.
But since you want only the guests, you can simply change your user class to:
class User < ActiveRecord::Base
has_many :hosts, dependent: :destroy
has_many :parties, through: :hosts
has_many :guests, through: :parties
# Call your guests
def past_guests
self.guests
end
end
Each party is going to have collection of guests. You need to do this in your method definition:
def past_guests
self.parties.collect(&:guests)
end
I am having issue with saving a has_many through relation with nested attributes. Due to complexity and requirment in the application the relation is as follows
Table structure,
agreements:
id
agreement_rooms:
id
agreement_id
room_id
details:
id
agreement_rooms_id
For more clarification, agreement_rooms table is related to many other models which will be having agreement_rooms_id in them.
Rails Associations,
class Agreement < ActiveRecord::Base
has_many :details,:through => :agreement_rooms
accepts_nested_attributes_for :details
end
class AgreementRoom < ActiveRecord::Base
has_many :details
end
class Detail < ActiveRecord::Base
belongs_to :agreement_room
accepts_nested_attributes_for :agreement_room
end
When i try to create a agreements record with details hash in it, i get the following error,
Agreement.last.details.create()
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection: Cannot modify association 'agreement#details' because the source reflection class 'Detail' is associated to 'agreementRoom' via :has_many.
I am not sure how to get this nested attributed working with has_many through relation for the above example. Please help out to figure the issue.
Thanks in advance.
#app/models/aggreement.rb
class Agreement < ActiveRecord::Base
has_many :agreement_rooms
accepts_nested_attributes_for :agreement_rooms
end
#app/models/agreement_room.rb
class AgreementRoom < ActiveRecord::Base
belongs_to :agreement
belongs_to :room
has_many :details
accepts_nested_attributes_for :details
end
#app/models/room.rb
class Room < ActiveRecord::Base
has_many :agreement_rooms
has_many :agreements, through: :agreement_rooms
end
#app/models/detail.rb
class Detail < ActiveRecord::Base
belongs_to :agreement_room
end
--
#app/controllers/agreements_controller.rb
class AgreementsController < ApplicationController
def new
#agreement = Agreement.new
#agreement.agreement_rooms.build.details.build
end
def create
#agreement = Agreement.new agreement_params
#agreement.save
end
private
def agreement_params
params.require(:agreement).permit(:agreement, :param, agreement_rooms_attributes: [ details_attributes: [:x] ])
end
end
#app/views/agreements/new.html.erb
<%= form_for #agreement do |f| %>
<%= f.fields_for :agreement_rooms do |ar| %>
<%= ar.fields_for :details do |d| %>
<%= d.text_field :x %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
you need to define both associations:
class Agreement < ActiveRecord::Base
has_and_belongs_to_many :agreement_rooms # or has_many if you prefer
has_many :details,:through => :agreement_rooms
accepts_nested_attributes_for :details
end
check the docs
As i said before the model association design we has was not proper and due to poor maintenance it has to be in the same way, atleast for now. So i had to write a dirty patch to fix it.
Its simply skipping nested attributes for this specific model alone, so it can be saved separately by passing the master record id to this record.
As its a dirty solution i'm not marking it as the answer. Just added it hoping someone can have a solution if needed.
Thanks for the help
I have 3 models, Type, Category & Product. I am building an ecommerce platform and on the main products page I am able to list all of the many products of a particular category (Mens,Womens, Tshirts, Hats).
What I would like to do now is loop through all the product types tied to a specific category. The product types would be for the Mens category(Tanks, Tees, Shirts), and Womens category would have (Dresses, Tanks, Shirts), Hats would be (Flexfit, Trucker, Camper) and Hoodies would be (Pullover, Zipup).
A sample output would be
For Mens => Tanks, Tees, Shirts.
I am not sure if this would be a belongs to has many relationship between type and category, so the code in Type.rb might not be correct. I would like to know how to query the database to find all types of products in a category and also how to input them inside my seeds file.
Thanks!
Here is my code.
Category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :products, through: :categorizations
has_many :types
end
Product.rb
class Product < ActiveRecord::Base
has_many :options, dependent: :destroy
has_many :images, dependent: :destroy
has_many :categorizations
has_many :categories, through: :categorizations
has_many :types
def image_url
self.images.first.url
end
def has_image?
self.images.exists?
end
end
Type.rb
class Type < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
categories_controller.rb
class CategoriesController < ApplicationController
def show
#products = Product.includes(:categories).where('categories.name' => params[:name])
ap #products
render :layout => 'application_categories'
end
end
Check out the documentation and the actual code for Spree. The domain model is probably a lot more than what you are looking for but at least you'll understand what a full-blown e-commerce platform looks like for dealing with product taxonomies. http://guides.spreecommerce.com/developer/products.html
Ancestry
This sounds like a job for the Ancestry gem:
#app/models/product.rb
class Product < ActiveRecord::Base
belongs_to :category
end
#app/models/category.rb
class Category < ActiveRecord::Base
has_many :products
has_ancestry
end
Here's how it works:
When you have a hierarchy setup in your database (through either the ancestry or closure_tree gems), your objects have a series of "methods" appended to them, which give you the ability to call things such as object.children
The importance of this will become apparent in a second
Nesting
You'll want to ensure you can filter your products by category:
#config/routes.rb
resources :products, except: :show do
collection do
resources :categories, path: "" only: :show #-> domain.com/products/:id
end
end
This will definitely conflict with your products#show action (ignore it for now). From this, you'll then be able to use the following:
<%= link_to "Hats", products_categories_path("hats") %>
This will route a request to the following controller:
#app/controllers/categories_controller.rb
class CategoriesController < ApplicationController
def show
#category = Category.find params[:id]
end
end
#app/views/categories/show.html.erb
<%= render partial: "category", locals: { category: #category } %>
<%= render partial: "category", collection: #category.children, as: :category if #category.has_children? %>
#app/views/categories/_category.html.erb
<%= category.products.each do |product| %>
<%= link_to product.name, product %>
<% end %>
Extensions
This will give you the ability to call as many categories as you want in your views.
Admittedly, the structure I've outline above is somewhat constrained, but it's equally very important in giving you the scope & ability to show categories as part of a tree
If you need any more specifics, I'll be glad to give you some refactoring!
There has to be a better way to do this. My Favorite model belongs to User while Applicant belongs to both Gig and User. I am trying to efficiently determine whether a user has applied for Gig that was favorited (<% if #application.present? %>).
I tried chaining the collection by using something like #favorites.each.gig to no avail. While the below index action for Favorites seems to work, it's really verbose and inefficient. What is a more succinct way of doing this?
def index
#favorites = Favorite.where(:candidate_id => current_candidate)
#applications = Applicant.where(:candidate_id => current_candidate)
#favorites.each do |favorite|
#applications.each do |application|
if favorite.gig.id == application.id
#application = application
end
end
end
end
class User
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :candidate
belongs_to :gig
end
class Applicant < ActiveRecord::Base
belongs_to :gig
belongs_to :candidate
end
class Candidate < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Gig < ActiveRecord::Base
belongs_to :employer
has_many :applicants
has_many :favorites
has_many :users, :through => :applicants
end
For lack of a better answer, here's my idea:
--
User
Your user model should be structured as such (I just highlighted foreign keys, which I imagine you'd have anyway):
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants, foreign_key: "candidate_id"
has_many :favorites, foreign_key: "candidate_id"
end
This means you'll be able to call:
current_candidate.favorites
current_candidate.applicants
This will remove the need for your #applications and #favorites queries
--
Favorite
You basically want to return a boolean of whether applicant is part of the favorite model or not. In essence, for each favorite the candidate has made, you'll be able to check if it's got an application
I would do this by setting an instance method on your favorites method using an ActiveRecord Association Extension, like so:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :favorites do
def applied?
self.applicant.exists? proxy_association.owner.gig.id
end
end
end
This will allow you to call:
<%= for favorite in current_candidate.favorites do %>
<%= if favorite.applied? %>
<% end %>
This is untested & highly speculative. I hope it gives you some ideas, though!
I basically followed the ROR guide, http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association, to create the relationship models as shown below.
Because of the through association, I figured that #user.trips would give you both the trips that the user created and the trips that belong to the user. However, when I do #user.trips.count in console, the result was only the number of trips that the users created; the trips that belonged to the user through the 'group' association was not counted.
Question: How do I get my view to display both the trips that the user created and the trips that the user belongs to through 'group'?
user/show.html.erb
<% unless #user.all_trips.empty? %>
<% #user.all_trips.each do |trip| %>
<!-- Content -->
<% end %>
<% end %>
user.rb
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:source => :trip
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
self.trips | self.group_trips
end
end
trip.rb
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :traveldeal
has_many :groups
has_many :users, :through => :groups
end
group.rb
class Group < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
Thanks!
Edit: Modified code per TSherif's partial solution.
Edit 2: Fixed up the all_trips method. Everything appears to work for me at this point.
Oh! I think I get what you're trying to do and why it's a problem. I was wondering why has_many :trips was called twice. But from what I understand, you have two different User-Trip relationships. These two can't have the same name, otherwise one will hide the other. Try something like this:
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:class_name => "Trip"
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
Trip.joins(:groups).where({:user_id => self.id} | {:groups => {:user_id => self.id}})
end
end
Or if you're using an older version of Rails that doesn't have MetaWhere:
def all_trips
Trip.joins(:groups).where("(trips.user_id = ?) OR (groups.user_id = ?)", self.id, self.id)
end