Rails 3 - Nested Form and has_many through association - ruby-on-rails-3

Hi people, long time not been here. But I'm back because I need your help again please. I have a rails 3.0.9 app, and I'm working with nested forms and has_many through association. When i create an instance, it works great. The problems come when I try to edit. Here is an example for a better explanation. (table names and attributes are just for explaining)
Table Client
id
company_name
address
Table Worker
id
first_name
last name
Table Contact
id
client_id
worker_id
my models looks like these
class Worker < ActiveRecord::Base
has_many :contacts, :dependent => :destroy
has_many :clients, :through => :contacts, :foreign_key => 'client_id'
end
class Client < ActiveRecord::Base
has_many :contacts, :foreign_key => "client_id",:dependent => :destroy
has_many :workers, :through => :contacts, :foreign_key => 'worker_id'
accepts_nested_attributes_for :workers, :allow_destroy => false
end
class Contact < ActiveRecord::Base
belongs_to :worker, :foreign_key => "worker_id"
belongs_to :client, :foreign_key => "client_id"
end
Then in my form for create a client, I can create many workers, and rails make the association and creates the instances for the contacts table (by using nested forms).
The thing is, if I want to edit a client by removing a contact, the contact is not removed. As you can see I put this line in the clients model
accepts_nested_attributes_for :workers, :allow_destroy => false
I set the allow_destroy to false, because I don't want to delete the worker itself, I just want to remove the contact tuple.
Does anybody know how can I solve this?? Hope you can help me... Thanks

Related

How to get single attribute from array in a has_many :through association (User/Friend model)

In my User model I have the following:
class User < ActiveRecord::Base
has_many :friendships, dependent: :destroy
has_many :friends, :class_name => "User", :foreign_key => "friend_id"
has_many :pending_friends,
:through => :friendships,
:conditions => "status = 'pending'",
:foreign_key => "user_id",
:source => :friend
has_many :requested_friends,
:through => :friendships,
:source => :friend,
:conditions => "status = 'requested'"
def friends
direct_friends | inverse_friends
end
In my Friendship model I have the following:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
In my view I have created an array of each of the users' friends, shown below. This code works and populates the array with all the data from the friends' "User" database model attributes.
User.first.friends
However, I want to be able to call the users' friend's name's. So, for example, shouldn't I be able to do something like this?
User.first.friends.map(&:name)
How do I get an array containing just the friend's name's, instead of all the friend's user attributes? I would also appreciate if anyone could tell me why .first is used (I got that from here: Rails calling User record from Friends model), as it doesn't just get the first instance of the User's friends (it gets all the instances). And why does just doing:
User.friends
return an empty array?
Try method pluck:
User.first.friends.pluck(:name)
You should use first method to retrieve one object from table. The User is table with a lot of users.

Many to many relationship with metadata stored in the mapping table in activerecord

I have two tables with a many to many relationship, through a third table. In the third table is a piece of data I need to assign when I build the relationships between the two tables, how can I use ActiveRecords build method to assign that?
Here is code to show what I mean:
class Company < Contact
has_many :contact_companies
has_many :people, :through => :contact_companies
accepts_nested_attributes_for :people, :allow_destroy => true
accepts_nested_attributes_for :contact_companies
end
class Person < Contact
has_many :contact_companies
has_many :companies, :through => :contact_companies
accepts_nested_attributes_for :companies, :allow_destroy => true
accepts_nested_attributes_for :contact_companies
end
class ContactCompany < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
ContactCompany contains a data member called "position". What I want to do is something like:
c = Person.new
c.companies.build(:name => Faker::Company.name, :position => positions.sample)
EDIT:
When I try the code above I get "unknown attribute: position".
The c.companies.build line is attempting to build a Company object which does not have the position attribute (the ContactCompany does) hence the error. It looks like you are trying to set attributes on two different models, so you'll have to make sure you are setting the appropriate attribute on the right model:
# you can chain these calls but I separated them for readability
cc = c.contact_companies.build(:position => positions.sample)
cc.build_company(:name => Faker::Company.name)

ActiveRecord Find with condition based on associated record having a value in a list

With the following Rails models:
class Group < ActiveRecord::Base
has_many :group_locations, :dependent => :restrict
has_many :locations, :through => :group_locations, :dependent => :restrict
end
class Location < ActiveRecord::Base
has_many :group_locations, :dependent => :destroy
has_many :groups, :through => :group_locations
end
class GroupLocation < ActiveRecord::Base
belongs_to :group
belongs_to :location
end
I am trying to Find all of the locations that are associated with at least one of several groups contained in the string "group_list" (e.g. "1,2,3,4,5"). If it was a field from the Location record, I would specify a condition of "*field in (#{group_list})*". But how do I accomplish my goal when I want to have at least one of the location's "group_location" whose "group_id" is in the list (or, alternatively, one "group" whose group_id is in the list).
I know how to do it with pure SQL, but how do you do it with Rails?
#taro You are right. Started by adding the code
joins(:group_locations).where("group_id in (?)", group_id_array)
Then I proceeded to define a scope just to make it a nice package:
scope :locations_in_groups, lambda { |grparray| joins(:group_locations).where("group_id in (?)", grparray) }
Thanks for your help.

Rails 3 - Model Association Problems

I have a project where an User can own a Project and make part of a Project as a Team.
My models are like that:
class User
has_many :projects, :foreign_key => "owner_id"
has_many :project_memberships, :foreign_key => "member_id"
has_many :shared_projects, :class_name => "Project", :through => :project_memberships, :foreign_key => "member_id"
end
class Project
belongs_to :owner, :class_name => "User"
has_many :project_memberships
has_many :members, :class_name => "User", :through => "project_memberships", :foreign_key => "member_id"
end
My question is: How can I create/delete etc a new Project so an User can own it since I'm not using nested resources?
Here is my Project Controller:
def new
#project = Project.new
end
def create
#owner = User.find(params[:user_id])
#project= #owner.projects.build(params[:project])
...
end
Thanks in advance.
If I understand your question correctly, you need to store current signed in user ID in session or use some authentication gem (like devise) which will do it for you.
Devise provides helper method current_user which returns an instance of User model. So you could do like so:
def create
#project= current_user.projects.build(params[:project])
...
end
Update
If you pass user_id through form, you allow anyone to create project with another user's id. Actions that create something, that belong to current user should be constrained to current user on the serverside

Setting up a polymorphic has_many :through relationship

rails g model Article name:string
rails g model Category name:string
rails g model Tag name:string taggable_id:integer taggable_type:string category_id:integer
I have created my models as shown in the preceding code. Articles will be one of many models which can have tags. The category model will contain all categories which may be assigned. The tag model will be a polymorphic join-table which represents tagged relationships.
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :categories, :through => :taggable
end
class Category < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :articles, :through => :taggable
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
I can't seem to get this to work, I can do it non polymorphic, but I must have something wrong with the polymorphic part. Any ideas?
Edit: Still not getting this right:
class Article < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :categories, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Category < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :articles, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
To create a polymorphic has_many :through, you must first create your models. We will use'Article,' 'Category,' and 'Tag' where 'Tag' is the join-model and Article is one of many objects which can be "tagged" with a category.
First you create your 'Article' and 'Category' models. These are basic models which do not need any special attention, just yet:
rails g model Article name:string
rails g model Category name:string
Now, we will create our polymorphic join-table:
rails g model Tag taggable_id:integer taggable_type:string category_id:integer
The join-table joins together two tables, or in our case one table to many others via polymorphic behavior. It does this by storing the ID from two separate tables. This creates a link. Our 'Category' table will always be a 'Category' so we include 'category_id.' The tables it links to vary, so we add an item 'taggable_id' which holds the id of any taggable item. Then, we use 'taggable_type' to complete the link allowing the link to know what it is linked to, such as an article.
Now, we need to set up our models:
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable, :dependent => :destroy
has_many :categories, :through => :tags
end
class Category < ActiveRecord::Base
has_many :tags, :dependent => :destroy
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
After this, setup your database using:
rake db:migrate
That's it! Now, you can setup your database with real data:
Category.create :name => "Food"
Article.create :name => "Picking the right restaurant."
Article.create :name => "The perfect cherry pie!"
Article.create :name => "Foods to avoid when in a hurry!"
Category.create :name => "Kitchen"
Article.create :name => "The buyers guide to great refrigeration units."
Article.create :name => "The best stove for your money."
Category.create :name => "Beverages"
Article.create :name => "How to: Make your own soda."
Article.create :name => "How to: Fermenting fruit."
Now you have a few categories and various articles. They are not categorized using tags, however. So, we will need to do that:
a = Tag.new
a.taggable = Article.find_by_name("Picking the right restaurant.")
a.category = Category.find_by_name("Food")
a.save
You could then repeat this for each, this will link your categories and articles. After doing this you will be able to access each article's categories and each categorie's articles:
Article.first.categories
Category.first.articles
Notes:
1)Whenever you want to delete an item that is linked by a link-model make sure to use "destroy." When you destroy a linked object, it will also destroy the link. This ensures that there are no bad or dead links. This is why we use ':dependent => :destroy'
2)When setting up our 'Article' model, which is one our 'taggable' models, it must be linked using :as. Since in the preceeding example we used 'taggable_type' and 'taggable_id' we use :as => :taggable. This helps rails know how to store the values in the database.
3)When linking categories to articles, we use:
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
This tells the category model that it should have many :articles through :tags. The source is :taggable, for the same reason as above. The source-type is "Article" because a model will automatically set taggable_type to its own name.
You simply cannot make the join table polymorphic, at least Rails does not support this out of the box. The solution is (taken from Obie's Rails 3 way):
If you really need it, has_many :through is possible with polymorphic associations, but only by specifying exactly what type of polymorphic associations you want. To do so you must use the :source_type option. In most cases you will have to use the :source option, since the association name will not match the interface name used for the polymorphic association:
class User < ActiveRecord::Base
has_many :comments
has_many :commented_timesheets, :through => :comments, :source => :commentable,
:source_type => "Timesheet"
has_many :commented_billable_weeks, :through => :comments, :source => :commentable,
:source_type => "BillableWeek"
It's verbose and the whole scheme loses its elegance if you go this route, but it works:
User.first.commented_timesheets
I hope I helped!