ActiveRecord poly Appended Array vs Concatenated Array - ruby-on-rails

Why does the connections table get updated when I call #user.connections for the following?
Connection Model
class Connection < ActiveRecord::Base
belongs_to :left_nodeable, :polymorphic => true
belongs_to :right_nodeable, :polymorphic => true
# Statuses:
PENDING = 0
ACCEPTED = 1
named_scope :pending, :conditions => { :connection_status => PENDING }
named_scope :accepted, :conditions => { :connection_status => ACCEPTED }
end
User Model
class User < ActiveRecord::Base
has_many :left_connections, :as => :left_nodeable, :class_name => 'Connection', :conditions => {:left_nodeable_type => 'User', :right_nodeable_type => 'User'}
has_many :right_connections, :as => :right_nodeable, :class_name => 'Connection', :conditions => {:right_nodeable_type => 'User', :left_nodeable_type => 'User'}
def connections
self.left_connections << self.right_connections
end
end
If I use:
def connections
self.left_connections + self.right_connections
end
Then the model works ok but I cannot use any of my named_scope methods.
So I guess my questions boils down to...
What is the difference between the "<<" and "+" operator on an ActiveRecord? Why does using "<<" change the database, and using "+" cause named_scope methods to fail?

The model is updated because left_connections is updated with the << method. This makes left_connections = left_connections + right_connections.
arr = [1,2]
arr << [3,4]
arr #=> [1,2,3,4]
-------------------------
arr = [1,2]
arr + [3,4] #=> [1,2,3,4]
arr #=> [1,2]
self.left_connections + self.right_connections is the correct way to return a concatenation. As for your named_scope methods, I couldn't tell you why they're failing without seeing them.

Related

How to write this MYSQL query with multiple JOIN and OR in RoR ActiveRecord?

rails --version
2.3.16
ruby --version
1.8.7
Models:
class AToB
belongs_to :a
belongs_to :b
default_scope :include => [:a, :b]
end
class A
has_many :a_to_bs
has_many :bs, :through => :a_to_bs
named_scope :twos, :conditions => { :var => 2 }
named_scope :buzzed, :conditions => { :fizz => ['buzz'] }
end
class B
has_many :a_to_bs
has_many :as, :through => :a_to_bs
end
MYSQL query:
SELECT COUNT(DISTINCT a.id), COUNT(DISTINCT c.id)
FROM a_to_b
INNER JOIN a on a.id = a_to_b.a_id
INNER JOIN b on b.id = a_to_b.b_id
WHERE (a.var = 2 AND a.fizz in ('buzz') AND
(b.foo = TRUE OR b.bar = TRUE OR (b.moo = TRUE AND a_to_b.goo = FALSE))
)
Will also need this variation
SELECT COUNT(DISTINCT a.id), COUNT(DISTINCT c.id)
FROM a_to_b
INNER JOIN a on a.id = a_to_b.a_id
INNER JOIN b on b.id = a_to_b.b_id
WHERE (a.var = 2 AND a.fizz in ('buzz') AND
NOT (b.foo = TRUE OR b.bar = TRUE OR (b.moo = TRUE AND a_to_b.goo = FALSE))
)
I've already googled countless simpler examples, read rails docs, etc. to no avail.
Let me try answering this question. For example your models are as follow in Rails:
#a.rb
class A < ActiveRecord::Base
attr_accessible :var, :fizz
has_many :a_to_bs
end
#b.rb
class B < ActiveRecord::Base
attr_accessible :foo, :bar, :moo
has_many :a_to_bs
end
#a_to_b.rb
class AToB < ActiveRecord::Base
attr_accessible :goo, :a_id, :b_id, :a, :b
belongs_to :a
belongs_to :b
end
A typical query for the first one would be:
records = AToB.where("a.var = ? AND a.fizz in (?) AND (b.foo = ? OR b.bar = ? OR
(b.moo = ? AND a_to_b.goo = ?))", 2, 'buzz', true, true, true, false)
And for the second one it would be:
records = AToB.where("a.var = ? AND a.fizz in (?) AND NOT (b.foo = ? OR b.bar = ? OR
(b.moo = ? AND a_to_b.goo = ?))", 2, 'buzz', true, true, true, false)
The answer is true assuming the queries you mentioned are correct.
This kinda worked:
scope = A.twos.buzzed
scope.count(:joins => { :b => :a_to_b }, :conditions => "b.foo = TRUE OR b.bar = TRUE OR (b.moo = TRUE AND a_to_b.goo = FALSE)")
Going to test a bit more before I'm sure.

Ruby on Rails: Is it right how I coded my Category-Tree (Is it ruby-like?)

I am new to Rails and just got my category tree working. Now I am not sure if what I have done is "ruby conform" or "ruby-like". I come from PHP and have to change some of my habits but this is not easy. I just want to check if I am on the right way.
The structure is done by the scaffold-command so I guess its correct.
So obvisoulythere is a model-class which is called Category and inherits from ActiveRecord::Base. This model has the following attributes/database fields:
*id,media,image,small_image,clicks,parent,active,description,name,created_at,updated_at*
This is the content of my model-class:
class Category < ActiveRecord::Base
has_many :articles,
:foreign_key => 'category'
belongs_to :parent_object,
:foreign_key => "parent",
:class_name => "Category"
has_many :children,
:foreign_key => "parent",
:class_name => "Category",
:order => "name ASC",
:dependent => :delete_all
#tree = Hash.new
#treepart = Hash.new
def self.category_tree
#root_categories = self.find(:all, :conditions => ["parent = ?", 0])
if #root_categories.length >= 1
#root_categories.each do |level|
#tree[level.id] = level.child_loop(level)
end
#tree
end
end
def child_loop(child)
#treepart = { :category => child }
#treepart[:children] = Hash.new
child.children.each do |child|
#treepart[:children][child.id] = child.child_loop(child)
end
#treepart
end
end
Categories can be nested therefore I have integrated a self-relating belongs_to and has_many function. I have called the parent *:parent_object* because only :parent does not work. Maybe it is in conflict with the attribute-name.
In the model I collect all categories with the method *category_tree* and *child_loop*. After this call I get an image of the category-tree in form of a hash.
Category.category_tree
I do this directly in the Articles´ *_form.html.erb* and pass it to my helper which is generating the html. Here is the call from the from-template:
<%= build_category_tree(Category.category_tree).html_safe %>
The helper is rendering as follows:
module CategoriesHelper
def build_category_tree(object_tree)
tree = object_tree
#treestring = "<ul>" + self.level_loop(tree) + "</ul>"
end
def level_loop(level)
#levelstring = ''
if !level.nil?
level.each do |id,item|
if item.has_key?(:category) && !item.nil?
#levelstring += "<li>" + item[:category].name + "</li>"
#levelstring += "<ul>" + self.level_loop(item[:children]) + "</ul>"
end
end
end
# in the end, return string to prevent a nil return
#levelstring += ""
end
end
Is this the ruby-way to code, can I shorten or change something completely?
Thanks for your help

Share methods between named scopes

I have a bunch of named scopes and have a method within one of them that I would like to share between the other named scopes. I've sort of accomplished this by using define_method and a lambda. However, there is still some repeated code and I'm wondering is there a better approach?
Here's a simplified example of what I've got. Assume I have a table of projects and each project has many users.
Within the User model I have...
filter_by_name = lambda { |name| detect {|user| user.name == name} }
named_scope :active, :conditions => {:active => true} do
define_method :filter_by_name, filter_by_name
end
named_scope :inactive, :conditions => {:active => false} do
define_method :filter_by_name, filter_by_name
end
named_scope :have_logged_in, :conditions => {:logged_in => true} do
define_method :filter_by_name, filter_by_name
end
Then I would use it like...
active_users = Project.find(1).users.active
some_users = active_users.filter_by_name ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"
logged_in_users = Project.find(1).users.logged_in
more_users = logged_in_users.filter_by_name "John"
Here's an entirely different solution that is probably more in spirit with what the question was asking for.
named_scope takes a block, which could be any Proc. So if you create a lambda/Proc which defines the filter_by_name method, you can pass it as the last argument to a named_scope.
filter_by_name = lambda { |name| detect {|user| user.name == name} }
add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }
named_scope(:active, :conditions => {:active => true}, &add_filter_by_name)
named_scope(:inactive, :conditions => {:active => false}, &add_filter_by_name)
named_scope(:have_logged_in, :conditions => {:logged_in => true}, &add_filter_by_name)
This will do what you're looking for. If you still think it's too repetitive, you can combine it with the techniques in mrjake2's solution to define many named scopes at once. Something like this:
method_params = {
:active => { :active => true },
:inactive => { :active => false },
:have_logged_in => { :logged_in => true }
}
filter_by_name = lambda { |name| detect {|user| user.name == name} }
add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }
method_params.keys.each do |method_name|
send(:named_scope method_name, :conditions => method_params[method_name],
&add_filter_by_name)
end
Named scopes can be chained, so you're making this harder on your self than you need to.
The following when defined in the user model will get you what you want:
class User < ActiveRecord::Base
...
named_scope :filter_by_name, lambda { |name|
{:conditions => { :name => name} }
}
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true}
end
Then the following snippets will work:
active_users = Project.find(1).users.active
some_users = active_users.filter_by_name( ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"
logged_in_users = Project.find(1).users.have_logged_in
more_users = logged_in_users.filter_by_name "John"
I see that you're using detect, probably to avoid excess hits to the DB. But your examples don't use it properly. Detect only returns the first element in a list that the block returns true for. In the above example, some_users will only be a single record, the first user that is named either "Pete" or "Alan". If you're looking to get all users named "Pete" or "Alan" then you want select. And if you want select then you're better off using a named scope.
Named scopes when evaluated return a special object that contains the components necessary to build the SQL statement to generate the results, chaining other named scopes still doesn't execute the statement. Not until you try to access methods on the result set, such as calling each or map.
I would probably use a bit of metaprogramming:
method_params = {
:active => { :active => true },
:inactive => { :active => false },
:have_logged_in => { :logged_in => true }
}
method_params.keys.each do |method_name|
send :named_scope method_name, :conditions => method_params[method_name] do
define_method :filter_by_name, filter_by_name
end
end
This way if you wanted to add more finders in the future, you could just add the method name and conditions to the methods_param hash.
You can also do this with a second named scope.
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true}
named_scope :filter_by_name, lambda {|name| :conditions => ["first_name = ? OR last_name = ?", name, name]}
Then you can do #project.users.active.filter_by_name('Francis').
If you really need to do this with Enumerable#detect, I would define the filter_by_name method in a module which can then extend the named scopes:
with_options(:extend => FilterUsersByName) do |fubn|
fubn.named_scope :active, :conditions => {:active => true}
fubn.named_scope :inactive, :conditions => {:active => false}
fubn.named_scope :have_logged_in, :conditions => {:logged_in => true}
end
module FilterUsersByName
def filter_by_name(name)
detect {|user| user.name == name}
end
end
This adds the filter_by_name method to the class returned by all three named scopes.

Rails has_many conditions

c = "(f.profile_id = #{self.id} OR f.friend_id = #{self.id})"
c += AND + "(CASE WHEN f.profile_id=#{self.id} THEN f.friend_id ELSE f.profile_id END = p.id)"
c += AND + "(CASE WHEN f.profile_id=#{self.id} THEN f.profile_rejected ELSE f.friend_rejected END = 1)"
c += AND + "(p.banned = 0)"
I need this to be used in a has_many relationship like this:
has_many :removed_friends, :conditions => ???
how do i set there the self.id?, or how do i pass there the id?
Then i want to use the will_paginate plugin:
#profile.removed_friends.paginate(:page => 1, :per_page => 20)
Thanks for your help
EDIT:
class Profile < ActiveRecord::Base
has_many :friendships
has_many :removed_friends, :class_name => 'Profile', :through => :friendships, :conditions =>
"(friendships.profile_id = #{self.id} OR friendships.friend_id = #{self.id})"
"AND (CASE WHEN friendships.profile_id=#{self.id} THEN friendships.profile_rejected ELSE friendships.friend_rejected END = 1)" +
"AND (p.banned = 0)"
end
class Friendship < ActiveRecord::Base
belongs_to :profile
belongs_to :removed_friend, :class_name => 'Profile', :foreign_key => "(CASE WHEN friendships.profile_id = #{self.id} THEN friend_id ELSE profile_id END)"
end
Use single quotes to enclose the condition:
class Profile < ActiveRecord::Base
has_many :friendships
has_many :removed_friends, :class_name => 'Profile', :through => :friendships,
:conditions => '
( friendships.profile_id = #{self.id} OR
friendships.friend_id = #{self.id}
) AND
(CASE WHEN friendships.profile_id=#{self.id}
THEN friendships.profile_rejected
ELSE friendships.friend_rejected
END = 1
) AND
(p.banned = 0)'
end
You might want to break this down into a series of named scopes that can be applied in stages instead of all at once. As an example, extract the banned part:
class Friend < ActiveRecord::Base
named_scope :banned, lambda { |*banned| {
:conditions => { :banned => banned.empty? ? 1 : (banned.first ? 1 : 0) }
}}
end
#profile.friends.removed.banned(false).paginate(:page => 1, :per_page => 20)
Using heavy-duty conditions in relationships is bound to cause trouble. If possible, try denormalizing the table, creating derivative columns that have "easy" versions of the data, or other things to make querying it easier.
You really have two relationships here. You have:
A rejected friendship from the profile_id side
A rejected friendship from the friend_id side
I don't know why both sides can reject a friendship, and maybe you need to look at your model for a little bit here (which side is requesting it? Would it be better to consider that the requestor CANCELLED the request instead of saying it was rejected from the profile side?)
At any rate, I would model this as the two separate relationships that they are:
class Profile
has_many :rejected_friendships, :conditions => 'friendships.profile_rejected = 1'
has_many :canceled_friendships, :foreign_key => 'friend_id', :conditions => 'friendships.friend_rejected = 1'
named_scope :banned, lambda do |*banned|
{ :conditions => {:banned => banned.empty? ? 1 : (banned.first ? 1 : 0) } }
end
has_many :rejected_friends, :class_name => 'Profile', :through => :rejected_friendships
has_many :canceled_friends, :class_name => 'Profile', :through => :canceled_friendships
def removed_friends
(self.rejected_friends.banned(false).all + self.canceled_friends.banned(false).all).uniq
end
end
This is somewhat undesirable because removed_friends is not a relationship anymore so you can't do things like Profile.removed_friends.find(:all, :conditions => {:name => "bleh"}) anymore, but this is a pretty complicated case. That condition is quite complex.

Retrieve all association's attributes of an AR model?

What do you think is the most optimal way to retrieve all attributes for all the associations an AR model has?
i.e: let's say we have the model Target.
class Target < ActiveRecord::Base
has_many :countries
has_many :cities
has_many :towns
has_many :colleges
has_many :tags
accepts_nested_attributes_for :countries, :cities, ...
end
I'd like to retrieve all the association's attributes by calling a method on a Target instance:
target.associations_attributes
>> { :countries => { "1" => { :name => "United States", :code => "US", :id => 1 },
"2" => { :name => "Canada", :code => "CA", :id => 2 } },
:cities => { "1" => { :name => "New York", :region_id => 1, :id => 1 } },
:regions => { ... },
:colleges => { ... }, ....
}
Currently I make this work by iterating on each association, and then on each model of the association, But it's kind of expensive, How do you think I can optimize this?
Just a note: I realized you can't call target.countries_attributes on has_many associations with nested_attributes, one_to_one associations allow to call target.country_attributes
I'm not clear on what you mean with iterating on all associations. Are you already using reflections?
Still curious if there's a neater way, but this is what I could come up with, which more or less results in the hash you're showing in your example:
class Target < ActiveRecord::Base
has_many :tags
def associations_attributes
# Get a list of symbols of the association names in this class
association_names = self.class.reflect_on_all_associations.collect { |r| r.name }
# Fetch myself again, but include all associations
me = self.class.find self.id, :include => association_names
# Collect an array of pairs, which we can use to build the hash we want
pairs = association_names.collect do |association_name|
# Get the association object(s)
object_or_array = me.send(association_name)
# Build the single pair for this association
if object_or_array.is_a? Array
# If this is a has_many or the like, use the same array-of-pairs trick
# to build a hash of "id => attributes"
association_pairs = object_or_array.collect { |o| [o.id, o.attributes] }
[association_name, Hash[*association_pairs.flatten(1)]]
else
# has_one, belongs_to, etc.
[association_name, object_or_array.attributes]
end
end
# Build the final hash
Hash[*pairs.flatten(1)]
end
end
And here's an irb session through script/console to show how it works. First, some environment:
>> t = Target.create! :name => 'foobar'
=> #<Target id: 1, name: "foobar">
>> t.tags.create! :name => 'blueish'
=> #<Tag id: 1, name: "blueish", target_id: 1>
>> t.tags.create! :name => 'friendly'
=> #<Tag id: 2, name: "friendly", target_id: 1>
>> t.tags
=> [#<Tag id: 1, name: "blueish", target_id: 1>, #<Tag id: 2, name: "friendly", target_id: 1>]
And here's the output from the new method:
>> t.associations_attributes
=> {:tags=>{1=>{"id"=>1, "name"=>"blueish", "target_id"=>1}, 2=>{"id"=>2, "name"=>"friendly", "target_id"=>1}}}
try this with exception handling:
class Target < ActiveRecord::Base
def associations_attributes
tmp = {}
self.class.reflections.symbolize_keys.keys.each do |key|
begin
data = self.send(key) || {}
if data.is_a?(ActiveRecord::Base)
tmp[key] = data.attributes.symbolize_keys!
else
mapped_data = data.map { |item| item.attributes.symbolize_keys! }
tmp[key] = mapped_data.each_with_index.to_h.invert
end
rescue Exception => e
tmp[key] = e.message
end
end
tmp
end
end
This is updated version of Stéphan Kochen's code for Rails 4.2
def associations_attributes
association_names = self.class.reflect_on_all_associations.collect { |r| r.name }
me = self.class.includes(association_names).find self.id
pairs = association_names.collect do |association_name|
object_or_array = me.send(association_name)
if object_or_array.is_a? ActiveRecord::Associations::CollectionProxy
association_pairs = object_or_array.collect { |o| [o.id, o.attributes] }
[association_name, Hash[*association_pairs.flatten(1)]]
else
[association_name, object_or_array.attributes]
end
end
Hash[*pairs.flatten(1)]
end

Resources