Retactoring advanced has_many example - ruby-on-rails

My user model has three relations for the same message model, and is using raw SQL :/ Is there a better more rails way to achieve the same result?
Could the foreign key be changed dynamically? e.g User.messages.sent (foreign key = author_id) and User.messages.received (foreign key = recipient ) I have been trying to move some of the logic into scopes in the message model, but the user.id is not available from the message model...
Any thoughts?
Table layout:
create_table "messages", :force => true do |t|
t.string "subject"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "author_id"
t.integer "recipient_id"
t.boolean "author_deleted", :default => false
t.boolean "recipient_deleted", :default => false
end
This is my relations for my user model:
has_many :messages_received, :foreign_key => "recipient_id", :class_name => "Message", :conditions => ['recipient_deleted = ?', false]
has_many :messages_sent, :foreign_key => "author_id", :class_name => "Message", :conditions => ['author_deleted = ?', false]
has_many :messages_deleted, :class_name => "Message", :finder_sql => 'SELECT * FROM Messages WHERE
author_id = #{self.id} AND author_deleted = true OR
recipient_id = #{self.id} AND recipient_deleted = true'
Best regards.
Asbjørn Morell

Yes, use a named_scope for sorting between deleted and not deleted messages.
class User < ActiveRecord::Base
has_many :messages_received, :foreign_key => 'recipient_id'
has_many :messages_sent, :foreign_key => 'author_id'
end
class Messages < ActiveRecord::Base
named_scope :deleted, :conditions => 'author_deleted = TRUE OR recipient_deleted = TRUE'
named_scope :not_deleted, :conditions => 'author_deleted = FALSE OR recipient_deleted = FALSE'
end
# Example user
user = User.first
user.messages_received.deleted
user.messages_received.not_deleted
user.messages_sent.deleted
user.messages_sent.not_deleted
Alternatively, you could go one step further and simplfy the association by using the user_id as the foreign key and specifying the message type.
create_table "messages", :force => true do |t|
t.string "subject"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.string "message_type"
t.integer "user_id"
t.boolean "deleted", :default => false
end
class User < ActiveRecord::Base
has_many :messages
end
class Messages < ActiveRecord::Base
MESSAGE_TYPES = %w[Recipient Author]
belongs_to :user
named_scope :recipient, :conditions => {:message_type => 'Recipient'}
named_scope :author, :conditions => {:message_type => 'Author'}
named_scope :deleted, :conditions => {:deleted => true}
named_scope :not_deleted, :conditions => {:deleted => false}
# Convenience class methods
def self.sent
author.not_deleted
end
def self.received
recipient.not_deleted
end
end
# Example usage
user = User.first
user.messages.sent
user.messages.received
user.messages.deleted
This approach is advantagoues because:
One less column.
Extendable. Adding an additional message type in the future is trivial (Eg: Drafts).

Related

Derived Activerecord Ruby Class

I am totally new to ruby. I am trying to make a RESTful service for task tracking application. I researched and found Sinatra better for the job than rails. So I am using Sinatra and ActiveRecord. I am following Up and Running With Sinatra and ActiveRecord. I will be creating the client application in .NET using Restsharp. But this is all about server side.
This is the migration I have created
class CreateTasksPeopleDocumentsAndComments < ActiveRecord::Migration
def self.up
create_table :tasks do |t|
t.string :name
t.string :task_status
t.string :task_type
t.text :description
t.text :analysis
t.text :investigation
t.integer :developer_id
t.integer :reviewer_id
t.date :open_date
t.date :analysis_date
t.date :promotion_date
t.date :review_date
t.date :correction_date
t.date :collection_date
t.date :closed_date
t.date :modified_date
t.date :target_date
end
create_table :people do |t|
t.string :name
t.string :trigram
t.string :state
t.string :level
end
create_table :documents do |t|
t.string :name
t.binary :data
t.string :path
t.integer :task_id
t.integer :developer_id
end
create_table :comments do |t|
t.text :comment
t.datetime :comment_timestamp
t.integer :person_id
t.integer :task_id
t.integer :comment_id
end
end
def self.down
drop_tables :tasks
drop_tables :people
drop_tables :documents
drop_tables :comments
end
end
And the Main App.rb
class Person < ActiveRecord::Base
end
class Developer < Person
has_many :tasks
end
class Reviewer < Person
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :developer
belongs_to :reviewer
has_many :documents
end
class Document < ActiveRecord::Base
belongs_to :task
belongs_to :developer
end
class Comment < ActiveRecord::Base
belongs_to :task
has_many :comments
end
get '/' do
"Hola World!"
end
get '/tasks' do
Task.all.to_json
end
get '/people' do
Person.all.to_json
end
get '/person/:id' do
Person.where(["id = ?", params[:id]]).to_json
end
get '/task/:id' do
Task.where(["id = ?", params[:id]]).to_json
end
get '/document/:id' do
Document.where(["id = ?", params[:id]]).to_json
end
get '/task/:id/documents' do
# Task.where(["id = ?", params[:id]]).document.all.to_json
# Document.where(["task_id = ?", params[:id]]).all.to_json
Task.find(params[:id]).documents.all.to_json
end
get '/make' do
person1 = Person.create(
:name => "AEonAX",
:trigram =>"anx",
:state => "Active",
:level => "Master"
)
person2 = Person.create(
:name => "XEonAX",
:trigram =>"xnx",
:state => "Inactive",
:level => "User"
)
person3 = Person.create(
:name => "ZEonAX",
:trigram =>"znx",
:state => "Active",
:level => "User"
)
person4 = Person.create(
:name => "LEonAX",
:trigram =>"lnx",
:state => "Inactive",
:level => "Master"
)
task1 = Task.create(
:name => "IR-000001V0R2000",
:description => "The Very First Incident Report",
:task_status => "Opened",
:task_type => "Internal",
:developer_id => person2.id,
:reviewer_id => person1.id
)
task2 = Task.create(
:name => "IR-000002V0R2000",
:description => "Second Incident Report",
:task_status => "Tech. Anal.",
:task_type => "External",
:developer_id => person2.id,
:reviewer_id => person1.id
)
task3 = Task.create(
:name => "IR-000003V0R2000",
:description => "Another Incident Report",
:task_status => "Under Corr.",
:task_type => "External",
:developer_id => person3.id,
:reviewer_id => person1.id
)
document1 = Document.create(
:name => "FirstDoku",
:path => "\\XEON-NB\Test\FiddlerRoot.cer",
:task_id => task1.id,
:developer_id => task1.developer.id,
:data => Task.all.to_json #SomeBinaryData
)
end
Currently this code only reads data. I have not started writing of data.
Basically the relations are like for a task there will be a developer and reviewer. It will have documents attached to it. It will also have comments.
Comments can be on the task or in reply to a comment.
As you can see I have declared a Person class and derived Developer and Reviewer from it. Is it the right way? Any other suggestions are welcome. Even suggestions to use other frameworks are accepted.

How to query intermediate table?

I have two classes in ruby:-
class Role < ActiveRecord::Base
# attr_accessible :title, :body
acts_as_authorization_role :subject_class_name => 'User', :join_table_name => "roles_users"
end
and
class User < ActiveRecord::Base
acts_as_authentic
acts_as_authorization_object :role_class_name => 'Role', :subject_class_name => 'User'
acts_as_authorization_subject :association_name => :roles , :join_table_name => 'roles_users'
has_one:employee_detail ,:foreign_key => "User_id"
end
and migration files are:-
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :username
t.string :email
t.string :crypted_password
t.string :password_salt
t.string :persistence_token
t.timestamps
end
end
end
class CreateRoles < ActiveRecord::Migration
def change
create_table "roles" , :force => true do |t|
t.string :name , :limit => 40
t.string :authorizable_type, :limit => 40
t.integer :authorizable_id
t.timestamps
end
end
end
class RolesUsers < ActiveRecord::Migration
def change
create_table "roles_users", :id => false, :force => true do |t|
t.references :user
t.references :role
end
end
end
i have user id and want to retrieve role id, but not able to query intermediate table. can anyone provide some solution for this. thnks
It's clear that your user is going to have multiple roles
You can define an habtm relation in user for for role and get retrive roles roles like following
class User < ActiveRecord::Base
acts_as_authentic
acts_as_authorization_object :role_class_name => 'Role', :subject_class_name => 'User'
acts_as_authorization_subject :association_name => :roles , :join_table_name => 'roles_users'
has_one:employee_detail ,:foreign_key => "User_id"
has_and_belongs_to_many :roles
end
And retrieve roles for a user like below
user = User.find user_id
user.roles # this will give you roles array
role_ids = user.roles.map { |r| r.id } # if you only want array of ids

Rails - How to associate models in one direction only

Ahoy guys,
I'm new to Rails, and I feel like I'm definitely missing something crucial here, because it seems like this should be an easily solvable problem.
I've set up a Page model and a Coord model (with help from the getting started tutorial), and Coord successfully belongs_to Page. I'm trying to apply similar logic to make another model, Comment, belong to Coord, and only belong to Page via Coord.
Do I use :through for an association that (I think) only needs to link in one direction? As in Page < Coord < Comment?
At the moment I have:
class Page < ActiveRecord::Base
attr_accessible :description, :name
has_many :coords
has_many :comments, :through => :coords
end
Coord model:
class Coord < ActiveRecord::Base
belongs_to :page
has_many :comments
attr_accessible :coordinates, :x, :y
validates :x, :presence => true
validates :y, :presence => true
end
Then the Comment model:
class Comment < ActiveRecord::Base
belongs_to :coord
belongs_to :page
attr_accessible :body
end
I still keep getting errors about comments being an undefined method, or an association not being defined. Apologies if this is a common question, I don't personally know anyone who knows Rails, and the documentation only has examples too far removed from mine (to my knowledge). Thanks
Edit: added DB schema
ActiveRecord::Schema.define(:version => 20120712170243) do
create_table "comments", :force => true do |t|
t.text "body"
t.integer "coord_id"
t.integer "page_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "comments", ["coord_id"], :name => "index_comments_on_coord_id"
add_index "comments", ["page_id"], :name => "index_comments_on_page_id"
create_table "coords", :force => true do |t|
t.string "coordinates"
t.integer "x"
t.integer "y"
t.integer "page_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "coords", ["page_id"], :name => "index_coords_on_page_id"
create_table "pages", :force => true do |t|
t.string "name"
t.string "description"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end
Page
class Page < ActiveRecord::Base
has_many :coords
has_many :comments, :through => :coords
end
Coord
class Coord < ActiveRecord::Base
belongs_to :page
has_many :comments
end
Comment
class Comment < ActiveRecord::Base
belongs_to :coord
has_one :page, :through => :coord
end
Using the above, you don't need page_id in the comments table.
Reference: A Guide to Active Record Associations

How to compare activerecord object to new object and update if nto equal in Rails 3?

I'm trying to figure out if there is a way to do this in Rails more efficiently.
There is kind of a long setup for the question, so please bear with me.
Let's say I have models Customer, Phone, Address
Here are sample migrations to give you an idea:
class CreatePhones < ActiveRecord::Migration
def self.up
create_table :phones do |t|
t.integer :country_prefix, :limit => 3
t.integer :area_prefix, :limit => 5
t.integer :number, :limit => 7
t.integer :category_id
t.references :phonable, :polymorphic => true
t.timestamps
end
end
end
class CreateAddress < ActiveRecord::Migration
def self.up
create_table :addresses do |t|
t.string :address_line_1
t.string :address_line_2
t.string :address_line_3
t.string :city
t.string :state
t.string :zip
t.string :country
t.string :attn
t.integer :category_id
t.references :addressable, :polymorphic => true
t.timestamps
end
end
end
class CreateCategories < ActiveRecord::Migration
def self.up
create_table :categories do |t|
t.string :name
t.string :code
t.integer :category_id # Every subcategory has a category: i.e. phone has work, fax,mobile
t.timestamps
end
end
end
class CreateCustomers < ActiveRecord::Migration
def self.up
create_table :customers do |t|
t.string :code , :limit => 20 , :null => false
t.string :name , :null => false
t.string :billing_name
t.integer :preferred_shipping_method_id
end
end
Here are models and relations:
class Customer < ActiveRecord::Base
belongs_to :preferred_shipping_method , :class_name => "Category", :foreign_key => :preferred_shipping_method_id
has_many :addresses, :as => :addressable, :include => :category, :dependent => :destroy
has_many :phones, :as => :phonable, :include => :category, :dependent => :destroy
end
class Category < ActiveRecord::Base
has_many :addresses
has_many :phones
has_many :customer_by_shipping_methods, :class_name => "Customer", :foreign_key => :preferred_shipping_method_id
has_many :subcategories, :class_name => "Category", :foreign_key => :category_id
belongs_to :category, :class_name => "Category"
end
class Address < ActiveRecord::Base
belongs_to :category
belongs_to :addressable, :polymorphic => true
end
class Phone < ActiveRecord::Base
belongs_to :category
belongs_to :phonable, :polymorphic => true
end
Here is a question.
Let's say I have a customer record with a bunch of phone (mobile, work) and addresses (billing, shipping).
old = Customer.where(:code => "ABC").first
Then I'm creating or importing (from a legacy DB) another customer object
new = Customer.new
new.code = "ABC"
new.phones.build(:number => "12345567")
etc.
Then I want to compare old customer info to the new customer info and based on that update old customer info.
Like so :
if old.eql?(new) # this should compare not only, name & code and such but also polymorphic associations
old.update_with(new) # this should update old info attributes with new if there is new info, or if update / add to one of the associations
old.save #
else
new.save
end
So the question is is there any CONVENTIONAL way in Rails 3 to do what I describe in comments?
Right now I'm overriding hash & eql? methods which is fine for comparison. But to update each attribute and each associated object and its attributes, is getting kind of involved. I was wondering if there is an easier way to do this then my way:
class Customer < ActiveRecord::Base
def hash
%{#{ name }#{ code }}.hash # There is a lot more here of course
end
def eql?(other)
hash == other.hash
end
def update_with(other)
name = other.name
code = other.code
etc ....
end
end
Ok it doesn't look like there is a standard solution so here is something I came up if anybody else is looking for it.
You can rename methods anyway you like, just put this in you lib folder in some .rb file. ( don't forget to mention it in environment.rb like so require 'custom_comparisons'
/lib/custom_comparisons.rb
module ActiveRecord
class Base
def comparison_hash
h = ""
self.attributes.each_pair do |key, value|
unless ["id", "updated_at", "created_at"].include?(key)
h << "#{value}"
end
end
h.hash
end
def eql_to?(other)
comparison_hash == other.comparison_hash
end
def differences_from?(other)
h = {}
self.attributes.each_pair do |key, value|
unless self.method_missing(key) == other.method_missing(key)
h[key.to_sym] = [self.method_missing(key), other.method_missing(key)]
end
end
h
end
end
end
This can be cleaned up a bit and I need to add association drill down but the solution is there.
This does the comparison and the shows the differences between objects. Now I can update attributes that need to be updated. Will add update method and drill down tomorrow.

Rails3 has_many :through and foreign_key not work in set :class_name

Hi, all
I need find friends activities:
SELECT `activities`.* FROM `activities`
INNER JOIN `friendships`
ON `activities`.user_id = `friendships`.friend_id
WHERE ((`friendships`.user_id = 1))
My add this code to User Model:
has_many :friends_activities,
:class_name => 'Activity',
:through => :friendships,
:foreign_key => :user_id,
:source => :friend
But Rails return is:
SELECT `activities`.* FROM `activities`
INNER JOIN `friendships`
ON `activities`.id = `friendships`.friend_id
WHERE ((`friendships`.user_id = 1))
I need :
`activities`.user_id = `friendships`.friend_id
But Now:
`activities`.id = `friendships`.friend_id
PS: My Tables:
create_table "activities", :force => true do |t|
t.integer "user_id"
t.integer "target_id"
t.string "target_type"
t.string "verb"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "friendships", :force => true do |t|
t.integer "user_id", :null => false
t.integer "friend_id", :null => false
t.string "state"
t.datetime "created_at"
t.datetime "updated_at"
end
I am not sure you can use the additional options on the has_many :through. According to the docs:
Specifies an association through which to perform the query. This can
be any other type of association, including other :through
associations. Options for :class_name, :primary_key and :foreign_key
are ignored, as the association uses the source reflection.
[emphasis mine]
Your last resort might be:
has_many :friends_activities,
:class_name => 'Activity',
:finder_sql => Proc.new {
%Q{
SELECT `activities`.* FROM `activities`
INNER JOIN `friendships`
ON `activities`.user_id = `friendships`.friend_id
WHERE ((`friendships`.user_id = #{id}))
}
}
I'm sure there is a more elegant way to achieve this, but in the meantime this should do the work.

Resources