DRY scope methods - ruby-on-rails

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

Related

Rails association : update association parameter (make admin/remove admin)

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

Nested models testing : Could not find table '*' Error.

I'm trying to run RSpec against a working large codebase (I'm relatively new to Rails), but it fails on this point; My bet that it has something to do with the FactoryGirl definitions.
Overview of the model :
class User < ActiveRecord::Base
# ...
has_many :friends, :conditions => {:approved => true}
has_many :friendships, :class_name => "User", :source => :friend, :through => :friends
# ...
The method to test :
# models/user.rb
def add_friend(user_id, friend_id)
#friendship = self.friends.new({:user_id => user_id, :friend_id => friend_id})
return false unless #friendship.save
end
The FactoryGirl factories :
FactoryGirl.define do
factory :user, :class => User do |f|
# ...
end
factory :friend, :class => Friend do |f|
f.user_id { Faker::Base.regexify(/\d{1,3}/)}
f.friend_id { Faker::Base.regexify(/\d{1,3}/)}
# ...
end
end
The Spec :
# specs/models/user_spec.rb
it "Adds friends" do
#current_user.add_friend(#current_user.id, #friend_1.id).should be_valid
end
The Error :
ActiveRecord::StatementInvalid: Could not find table 'friends'
Any feedback is highly welcome, Thanks.
You may need to run rake db:migrate RAILS_ENV=test.

rails 2.3 named_scope nested hash in conditions

Im really really new to ruby/ruby on rails and was given a model class that looks like this. I just want ask why is it giving me odd number list for Hash error when I try to call Ranks.search_word("Jagger")
Im using Rails 2.3.5/ActiveRecord 2.3.5
class Ranks < ActiveRecord::Base
set_table_name 'CM_GT_RANK'
set_primary_key 'rank_id'
has_one :character_atlas, :class_name => "CharAtlas", :foreign_key => "char_id_db"
has_one :player_records, :class_name => "PlayerRecord", :foreign_key => "char_id"
default_scope :joins => :character_atlas,
:order => "rank asc"
named_scope :search_word,
lambda{ |keyword|
{
if keyword.present?
{:conditions => { :CM_CHAR_ATLAS => {:char_name => keyword }} }
else
{}
end
}
}
end
You have an additional Pair of curly brackets which are not required. Try:
named_scope :search_word, lambda{ |keyword|
if keyword.present?
{:conditions => { :CM_CHAR_ATLAS => {:char_name => keyword }} }
else
{}
end
}

Where should I put the code?

I have the models User and StoredItem:
class UserData < ActiveRecord::Base
has_many :stored_items, :dependent => :destroy
end
class StoredItem < ActiveRecord::Base
belongs_to :user
named_scope :lookup, lambda { |id| { :conditions => ['qid = ?', id]}}
end
I need to have two methods to add and remove the items to StoredItem for current user. I put this code to User model:
class UserData < ActiveRecord::Base
has_many :stored_items, :dependent => :destroy
def save_item(params)
if(!self.stored_items.lookup(params[:qid]).exists?)
item = self.stored_items.new(:sid => params[:qid],
:name => params[:qti],
:url => params[:qur],
:group_id => params[:title],
:rating => Integer(params[:rating]))
item.save
end
end
def remove_item(qid)
item = self.stored_items.lookup(qid).first()
item.destroy
end
end
So here is the StoredItem controller:
def save_item
#user = UserData.find_by_login(session[:cuser])
#user.save_item(params)
# ...
end
Is it good architectural decision or it will be better to put this code to StoredItem model and pass the current user into it?
This is a good architectural decision. You need to keep it in the user since the User is the owner of the StoredItem. The user is responsible for its stored items, not the other way around.

Mongoid Twitter-style following, can't specify criteria/conditions for relationship array

I'm at my wit's end trying to handle these errors. Basically, I've created the following User and Relationship patterns, using Mongoid to handle my database. This seems like a near-carbon copy of the example at the bottom of the page here. I'm trying to call any of the following:
user1.relationships.find(:all, :conditions => {:rel_user => user_in_question, :rel_type => "following" })
user1.relationships.all(:conditions => {:rel_user => user_in_question, :rel_type => "following" })
user1.relationships.where(:rel_type => "following")
user1.relationships.following #with a named scope
These all seem to just return the entire relationships array; they don't search through by criteria. The find() method also throws an error saying that it only can take 1 argument. The im_following? method always returns true.
I'm not sure if it's better to post code in-line or from gist, so here are the gists:
user.rb
user_follow_spec.rb
relationship.rb
I would appreciate any help.
Rockmanioff, I have also came across the same issue. You might want to look at this as well. Mongoid plans on supporting this feature on their release candidate version. For now, we have to do things manually.
class User
include Mongoid::Document
include Mongoid::Timestamps
references_many :fans, :stored_as => :array, :class_name => 'User', :inverse_of => :fan_of
references_many :fan_of, :stored_as => :array, :class_name => 'User', :inverse_of => :fans
def become_fan_of user
fan_of << user
self.save
user.fans << self
user.save
end
def is_a_fan? user
fan_of_ids.include? user.id
end
def unfan user
fan_of_ids.delete user.id
self.save
user.fan_ids.delete self.id
user.save
end
...
end
In console, you can do:
User.first.become_fan_of User.last
User.first.is_a_fan? User.last
User.first.unfan User.last
In your case you might want to substitute "fan / fan_of" for "followers / following respectively". Hope this helps.
I'd advise you to simplify your relationships by using self-referential associations. Check out my answer to this question:
How-to: User has fans
I think this is pretty close to the association you want:
class User
include Mongoid::Document
references_many :following,
:class_name => 'User',
:stored_as => :array,
:inverse_of => :followed_by
references_many :followed_by,
:class_name => 'User',
:stored_as => :array,
:inverse_of => :following
end
# let's say we have users: al, ed, sports_star, movie_star
sports_star.followed_by << al
movie_star.followed_by << al
sports_star.followed_by << ed
movie_star.followed_by << ed
movie_star.followed_by # => al, ed
al.following # => sports_star, movie_star
Try this:
class User
# follows and followers
references_many :follows, :stored_as => :array , :inverse_of => :followers ,:class_name=>"User"
references_many :followers, :stored_as => :array , :inverse_of => :follows ,:class_name=>"User"
def followers
followers.map
end
end

Resources