Rails association : update association parameter (make admin/remove admin) - ruby-on-rails

So I have a User model, and a Group model which has several users thanks to the GroupUserAssociation model. Here's how my relationships are defined:
class Group < ActiveRecord::Base
has_many :group_users, :class_name => 'GroupUserAssociation', :foreign_key => :group_id
has_many :group_admins, :class_name => 'GroupUserAssociation', :foreign_key => :group_id, :conditions => ['level = 1']
has_many :group_not_admins, :class_name => 'GroupUserAssociation', :foreign_key => :group_id, :conditions => ['level = 0']
has_many :users, :through => :group_users, :source => :user
has_many :admins, :through => :group_admins, :source => :user
has_many :not_admins, :through => :group_not_admins, :source => :user
end
If I want to add/remove users to group, there is an elegant way to write it (elegant because it doesn't involves the GroupUserAssociation object):
Group.first.users << User.first # Adds to group
Group.first.users.delete(User.first) # Removed from group
But if I do
Group.first.admins << User.first
Group.first.admins.delete(User.first)
it also deletes the association (hence has the same effect as the first lines).
Is there an elegant way (without handling the GroupUserAssociation object to promote/demote admin (= to update GroupUserAssociation.level from 1 to 0) ?
I could do
Group.first.users.delete(User.first) # Removed from group
Group.first.admins << User.first
But that would mean 2 times commiting to DB which is not really good...
I read there are some nice things for this in Rails 4, but unfortunately I'm using Rails 3.2...
Thanks

We do this using this code:
#config/routes.rb
resources :entries do
post :category
delete ":category_id", to: :category, as: "remove_category"
end
#Categories
def category
entry = #entry = Entry.find(params[:entry_id])
category = #category = Category.find(params[:category_id])
#Actions
entry.categories << category if request.post? && !entry.categories.include?(category)
entry.categories.delete(category) if request.delete?
#Return
respond_to do |format|
format.html { redirect_to collection_path }
format.js
end
end

Related

Has_many through - One Sided Uniqueness in the database

I have a three models:
class Feed < ActiveRecord::Base
has_many :filters, :dependent => :destroy
has_many :keywords, :through => :filters, :uniq => true
end
class Filter < ActiveRecord::Base
belongs_to :feed
belongs_to :keyword
validates_uniqueness_of :keyword_id, :scope => :feed_id
end
class Keyword < ActiveRecord::Base
has_many :filters, :dependent => :destroy
has_many :feeds, :through => :filters
end
What I want is to have only unique entries in the database for keywords. For example, if two feeds both have a keyword 'hello', there should be two filters (one for each feed) both pointing to the same keyword.
What I am having trouble with is the controller code. Perhaps I am looking for too simple a solution, but I figure there must be an easy way to do this. This is what I have in my create action so far:
def create
#feed = Feed.find(params[:feed_id])
#keyword = #feed.keywords.create(params[:keyword])
redirect_to feed_keywords_path(#feed), notice: 'Keyword added successfully.'
end
With this controller code, the previous example would result in a duplicate keyword in the database, one for each feed/filter. Is there a straight-forward solution to this or do I need to do a check beforehand to see if there is already a keyword and in that case just create the filter?
Use a dynamic finder find_or_create_by :
def create
#feed = Feed.find(params[:feed_id])
#keyword = Keyword.find_or_create_by_keyword(params[:keyword]) # I assume here that you have a column 'keyword' in your 'keywords' table
#feed.keywords << #keyword unless #feed.keywords.all.include?(#keyword)
redirect_to feed_keywords_path(#feed), notice: 'Keyword added successfully.'
end

has_many with two has_one's

I have a User model that has_many parents.
I want that user model to have one father and one mother.
So my class Parent belongs_to user
Currently I have
class User < ActiveRecord::Base
has_many :parents
has_one :father, :class_name => 'Parent', :foreign_key => 'user_id', :conditions => {:type => 'male'}
has_one :mother, :class_name => 'Parent', :foreign_key => 'user_id', :conditions => {:type => 'female'}
end
class Parent < ActiveRecord::Base
belongs_to :user
end
The problem is in my controller.
...
def edit
#user = User.find(params[:id])
#user.mother = Parent.new(:type => 'female')
#user.father = Parent.new(:type => 'male')
...
When I go into the edit, it creates and throws the 2 parents into the database without even having changed anything in the form. For example, when I click edit on a user, I go to the edit page. When I look into the database, they're already created.
My form looks like so:
= form_for #user do |f|
= f.fields_for :father do |father_form|
etc...
= f.fields_for :mother do |mother_form|
etc...
I've tried doing something alone the lines of this in my controller:
...
#user.parents.build(:type => 'male')
#user.parents.build(:type => 'female')
...
But the form doesn't show up.
Any help would be greatly appreciated.
Try to use
#user.build_father(:type => 'male')
#user.build_mother(:type => 'female')
instead of
#user.mother = Parent.new(:type => 'female')
#user.father = Parent.new(:type => 'male')
in your action

DRY scope methods

I am using Ruby on Rails 3.0.7 and I would like to DRY (Don't Repeat Yourself) my scope methods.
In the model file I have:
class Articles::Category < ActiveRecord::Base
scope :article_related_to, lambda { |user| where('articles_categories_article_relationships.user_id = ?', user.id) }
scope :comment_related_to, lambda { |user| where('comments_articles_article_category_relationships.user_id = ?', user.id) }
has_many :comment_article_category_relationships
has_many :comments,
:class_name => 'Comments::Articles::ArticleCategoryRelationship',
:through => :comment_article_category_relationships,
:source => :comment
has_many :article_relationships
:class_name => 'Articles::Categories::ArticleRelationship',
has_many :articles,
:through => :article_relationships,
:source => :article
end
By using the above code I can do this:
#comment.article_categories.comment_related_to(#current_user)
#comment.article_categories.article_related_to(#current_user)
How can I "DRY" scopes methods in order to make possible for both :article_related_to and :comment_related_to to use something like the following
#comment.article_categories.related_to(#current_user)
# In order to pass the correct "context" 'article' or 'comment' I thought
# something like
#
# #comment.article_categories.related_to(#current_user, 'article')
# #comment.article_categories.related_to(#current_user, 'comment')
#
# but, maybe, there is a way to retrieve automatically that "context" so to
# write only one "DRYed" scope method.
?
The best I can offer is the following:
scope :related_to, lambda { |user, context|
tbl = context == :article ? :articles_categories_article_relationships
: :comments_articles_article_category_relationships
where("#{tbl}.user_id = ?", user.id)
}
That gives you the #comment.article_categories.related_to(#current_user, :article) like you suggested. But I'm in agreement with Max Williams. This obfuscates your code unnecessarily with no real gain.
If you are really eager to obfuscate your code further you can do this:
def self.method_missing(method, *args)
if method =~ /^(.*)_related_to$/
related_to(*args, $1)
else
super
end
end
def self.related_to(user, context)
through = reflections[context.to_s.pluralize.to_sym].options[:through]
tbl = reflections[through].options[:class_name].underscore.pluralize.gsub('/', '_')
where("#{tbl}.user_id = ?", user.id)
end
Please note that I believe your associations have a couple of typos. Probably should be:
has_many :comment_article_category_relationships,
:class_name => 'Comments::Articles::ArticleCategoryRelationship'
has_many :comments,
:through => :comment_article_category_relationships,
:source => :comment
has_many :article_relationships,
:class_name => 'Articles::Categories::ArticleRelationship'
has_many :articles,
:through => :article_relationships,
:source => :article

has_many _through not working in Rails 3 after upgrade from Rails 2

I have the following in my controller:
#campaign = Campaign.where(:id => params[:id])
#companies = #campaign.companies.sort { |a,b| a.name <=> b.name` }
The second line gives me an unknown method for companies and it worked fine before.
This is in my campaign model:
has_many :companies, :through => :contacts, :uniq => true
I tried the following and it still didn't fix it:
has_many :companies, :through => :contacts, :uniq => true, :source => :company
#campaign = Campaign.where(:id => params[:id])
returns an array of results (probably just one item, but still an array). The "No Method Error" you're receiving is because the Array class doesn't have a companies method.
You either want to call .first on the result set:
#campaign = Campaign.where(:id => params[:id]).first
Or just use .find:
#campaign = Campaign.find(params[:id])

Rails XML :include

Here are my models:
class User < ActiveRecord::Base
...
belongs_to :picture, :foreign_key => 'picture_id',
:class_name => 'UploadedFile',
:dependent => :destroy
has_many :enrolled_groups, :through => :interests
...
end
class Group < ActiveRecord::Base
has_many :enrolled_users, :through => :interests,
:source => :user
end
I want to get an XML feed for my Groups with the enrolled users and their picture information.
The following line works fine (just with the enrolled users):
render :xml => #group.to_xml(:include => [:enrolled_users] )
How can I also include the picture info in the feed? I tried a bunch of things but can't figure it out... any idea?
If doing .to_xml(:include => [:enrolled_users, :picture]) doesn't work, then the hacky way would be to add (in User class):
def attributes
super.merge(:picture => picture)
end
You should be able to access nested resources by something like this:
render :xml => #group.to_xml(:include => [{:enrolled_users => :picture}])
I couldn't get it to work with the other proposed solutions. The way I ended up doing it was as follow:
In the controller:
render :xml => #group.to_xml(:include => {:enrolled_users => {:methods => :picture_url}})
In the model:
def picture_url
HOST+picture.public_filename(:avatar)
end

Resources