Complex virtual attributes creation/update - ruby-on-rails

So basically, my app contains users (model User) who have friends (unilateral access), and who also own lists.
What I'm trying to achieve here is when creating a new list, to provide it with "accessors", picked from the user's friends.
My code is heavily inspired from the following railscast on virtual attributes.
So, here comes my User and UserAccessor model (just the relevant parts) :
class User < ActiveRecord::Base
has_many :lists, :dependent => :destroy
has_many :friendships, :dependent => :destroy
has_many :friends, :through => :friendships
end
class UserAccessor < ActiveRecord::Base
belongs_to :accessor, :class_name => "User"
belongs_to :accessible_list, :class_name => "List"
end
My List model :
class List < ActiveRecord::Base
has_many :items
belongs_to :user
has_many :user_accessors, :foreign_key => "accessible_list_id", :dependent => :destroy
has_many :accessors, :class_name => "User", :through => :user_accessors
validates :title, :presence => true, :length => { :minimum => 1 }
attr_writer :authorized_users
after_save :add_accessors
def authorized_users
#authorized_users || self.accessors.map(&:username).join(' ')
end
private
def add_accessors
if #authorized_users
accessors = #authorized_users.split(' ').map do |username|
user = User.find_by_username(username)
if user
if self.user.inverse_friends.include? user
self.user_accessors.build(:accessor_id => user.id).accessor
end
end
end
end
end
end
The form used to create or update the list is the following one :
= simple_form_for [#user, #list] do |f|
= f.input :title, :label => "Titre"
= f.input :authorized_users, :label => "Authorized users", :hint => "Separated by spaces"
%p
= f.button :submit
So my problem comes from the fact that I don't know exactly how to create/update the accessors, my code self.user_accessors.build(:accessor_id => user.id).accessor definitely not working to fill it correctly.
I'm still quite a noob in rails (and ruby in general…), so I hope what I put there was relevant enough for you to help me! Thanks in advance!

Related

Ruby on Rails has_many_through with validation and logic to ensure only one record in the join table which is updated as required

I have the Course model which has a number of has many through associations with the User model with join table CourseUser. The join table has an attribute type_str which specifies which role the user takes on. I have added validation to ensure that only one record is present in the join table for each course, user pair. The problem is ensuring that this record is updated if it is already present, rather than adding a new one which of course makes validation fail.
User class:
class User < ActiveRecord::Base
...
has_many :courses_enrolled_on, :through => :course_enrollees, :source => :course, :conditions => { :course_users => { :type_str => "enrollee" } }
has_many :course_users
has_many :courses, :through => :course_users, :source => :course, :readonly => true
end
Course class
class Course < ActiveRecord::Base
has_many :course_enrollees, :conditions => { :type_str => "enrollee" }, :class_name => CourseUser
has_many :enrollees, :through => :course_enrollees, :source => :user
has_many :course_users
has_many :users, :through => :course_users, :source => :user, :readonly => true
end
Course class:
class CourseUser < ActiveRecord::Base
belongs_to :course
belongs_to :user
validates_uniqueness_of :course_id, :scope => :user_id
end

Validation on associtions in rails with roles

So I have two ActiveRecord classes
class User < ActiveRecord::Base
has_many :buyer_deals, :class_name => "Deal", :foreign_key => :buyer_id
has_many :seller_deals, :class_name => "Deal", :foreign_key => :seller_id
validates_presence_of :name # THIS SHOULD ONLY BE RUN IF USER IS A SELLER
# IN THE DEAL
validates_presence_of :phone # THIS SHOULD ONLY BE RUN IF USER IS A BUYER
# IN THE DEAL
end
class Deal < ActiveRecord::Base
belongs_to :seller, :class_name => 'User'
belongs_to :buyer, :class_name => 'User'
validates_associated :seller
validates_associated :buyer
end
What I want to do is create a new deal with.
Deal.create(A NICE STRUCT WITH SELLER AND BUYER)
However I only want to run the name validation if the relation from the deal is a seller and the phone if the the relation from the deal is a seller, is this possible in rails, does not seem to find anything in the documentation.
You should be able to do this by adding a condition to you validation.
So, your User class would wind up looking something like...
class User < ActiveRecord::Base
has_many :buyer_deals, :class_name => "Deal", :foreign_key => :buyer_id
has_many :seller_deals, :class_name => "Deal", :foreign_key => :seller_id
validates_presence_of :name, :if => :has_an_active_seller_deal?
validates_presence_of :phone, :if => :has_an_active_buyer_deal?
def has_active_seller_deals?
seller_deals.count > 0
end
def has_active_buyer_deals?
buyer_deals.count > 0
end
end
An alternative to this would be to simply require all users to have a name and phone number on file at all times (no conditional validation), and only reveal it to other users with which they had active deals, and not as part of a user's public profile, thereby protecting the user's privacy when possible. This would probably be simpler.
You could put the validations in a callback:
before_save :check_user_type
private
def check_user_type
user_type = self.responds_to?(seller_id) ? :seller : :buyer
if user_type == :seller
validates_presence_of :name
else
validates_presence_of :phone
end

How do I validate the uniqueness of a has_many :through join model?

I have users and issues joined by a votership model. Users can vote on issues. They can either vote up or down (which is recorded in the votership model). First, I want to be able to prevent users from casting multiple votes in one direction. Second, I want to allow users to cast the opposite vote. So, if they voted up, they should still be able to vote down which will replace the up vote. Users should never be able to vote on an issue twice. Here are my files:
class Issue < ActiveRecord::Base
has_many :associations, :dependent => :destroy
has_many :users, :through => :associations
has_many :voterships, :dependent => :destroy
has_many :users, :through => :voterships
belongs_to :app
STATUS = ['Open', 'Closed']
validates :subject, :presence => true,
:length => { :maximum => 50 }
validates :description, :presence => true,
:length => { :maximum => 200 }
validates :type, :presence => true
validates :status, :presence => true
def cast_vote_up!(user_id, direction)
voterships.create!(:issue_id => self.id, :user_id => user_id,
:direction => direction)
end
end
class Votership < ActiveRecord::Base
belongs_to :user
belongs_to :issue
end
class VotershipsController < ApplicationController
def create
session[:return_to] = request.referrer
#issue = Issue.find(params[:votership][:issue_id])
#issue.cast_vote_up!(current_user.id, "up")
redirect_to session[:return_to]
end
end
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_accessible :email, :password, :password_confirmation
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email
has_many :associations, :dependent => :destroy
has_many :issues, :through => :associations
has_many :voterships, :dependent => :destroy
has_many :issues, :through => :voterships
end
You would put the uniqueness constraint on the Votership model. You don't need to put validations on the association itself.
class Votership < ActiveRecord::Base
belongs_to :user
belongs_to :issue
validates :issue_id, :uniqueness => {:scope=>:user_id}
end
This means a user can only have a single vote on a given issue (up or down).
Relationship models:
class Person
has_many :accounts
has_many :computers, through: :accounts
end
class Account
belongs_to :person
belongs_to :computer
scope :administrators, -> { where(role: 'administrator') }
end
class Computer
has_many :accounts
has_many :people, through: :accounts
end
This is how it is called
person.accounts.administrators.map(&:computer)
We can do this better using ActiveRecord::SpawnMethods#merge!
person.computers.merge(Account.administrators)
Ref: https://coderwall.com/p/9xk6ra/rails-filter-using-join-model-on-has_many-through

Polymorphic associations in Rails 3

I think I'm going crazy.
Let's say I have 3 models: Address, Warehouse, Category:
class Address < ActiveRecord::Base
belongs_to :category
belongs_to :addressable, :polymorphic => true
scope :billing_addresses , where(:categories => {:name => 'billing'}).joins(:category)
scope :shipping_addresses , where(:categories => {:name => 'shipping'}).joins(:category)
end
class Category < ActiveRecord::Base
has_many :addresses
has_many :subcategories, :class_name => "Category", :foreign_key => "category_id"
belongs_to :category, :class_name => "Category"
end
class Warehouse < ActiveRecord::Base
has_many :addresses, :as => :addressable
end
Address is polymorphic, because eventually I'll be using it to store addresses for clients, people, employees etc. Also each address can be of a certain type: billing, shipping, work, home, etc.
I'm trying to pull some information on a page.
#some_warehouse = Warehouse.first
Then in my view:
%b= #some_warehouse.name
%b= #some_warehouse.billing_address.address_line_1
Etc.
I end up doing a lookup for each line of information.
I tried to do things like
Warehouse.includes(:addresses).where(:name => "Ware1")
Warehouse.joins(:addresses).where(:name => "Ware1")
And various variations of that.
No matter what I don' I can't get rails to preload all the tables. What am I doing wrong?
Here are revised models, that do appropriate joins in sql and reduce number of quesries from 16 to 8, one for each piece of info, instead of multiples ones that also do lookup categories, etc.:
class Address < ActiveRecord::Base
belongs_to :category
belongs_to :addressable, :polymorphic => true
scope :billing_addresses , where(:categories => {:name => 'billing'}).includes(:category)
scope :shipping_addresses , where(:categories => {:name => 'shipping'}).includes(:category)
end
class Warehouse < ActiveRecord::Base
has_many :addresses, :as => :addressable, :include => :category, :dependent => :destroy
def billing_address
self.addresses.billing_addresses.first
end
def shipping_address
self.addresses.shipping_addresses.first
end
end
class Category < ActiveRecord::Base
has_many :addresses
has_many :subcategories, :class_name => "Category", :foreign_key => "category_id"
belongs_to :category, :class_name => "Category"
end
Sleep helps. Also not forgetting to reload console from time to time :-)
Maybe you want to use preload_associations?

Displaying pic for user through a question's answer

Ok, I am trying to display the profile pic of a user. The application I have set up allows users to create questions and answers (I am calling answers 'sites' in the code) the view in which I am trying to do so is in the /views/questions/show.html.erb file. It might also be of note that I am using the Paperclip gem. Here is the set up:
Associations
Users
class User < ActiveRecord::Base
has_many :questions, :dependent => :destroy
has_many :sites, :dependent => :destroy
has_many :notes, :dependent => :destroy
has_many :likes, :through => :sites , :dependent => :destroy
has_many :pics, :dependent => :destroy
has_many :likes, :dependent => :destroy
end
Questions
class Question < ActiveRecord::Base
has_many :sites, :dependent => :destroy
has_many :notes, :dependent => :destroy
has_many :likes, :dependent => :destroy
belongs_to :user
end
Answers (sites)
class Site < ActiveRecord::Base
belongs_to :question
belongs_to :user
has_many :notes, :dependent => :destroy
has_many :likes, :dependent => :destroy
has_attached_file :photo, :styles => { :small => "250x250>" }
end
Pics
class Pic < ActiveRecord::Base
has_attached_file :profile_pic, :styles => { :small => "100x100" }
belongs_to :user
end
The /views/questions/show.html.erb is rendering the partial /views/sites/_site.html.erb which is calling the Answer (site) with:
<% div_for site do %>
<%=h site.description %>
<% end %>
I have been trying to do things like:
<%=image_tag site.user.pic.profile_pic.url(:small) %>
<%=image_tag site.user.profile_pic.url(:small) %>
etc. But that is obviously wrong. My error directs me to the Questions#show action so I am imagining that I need to define something in there but not sure what. Is is possible to call the pic given the current associations, placement of the call, and if so what Controller additions do I need to make, and what line of code will call the pic?
UPDATE: Here is the QuestionsController#show code:
def show
#question = Question.find(params[:id])
#sites = #question.sites.all(:select => "sites.*, SUM(likes.like) as like_total",
:joins => "LEFT JOIN likes AS likes ON likes.site_id = sites.id",
:group => "sites.id",
:order => "like_total DESC")
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #question }
end
end
You have has_many association for the pics:
class User < ActiveRecord::Base
...
has_many :pics, :dependent => :destroy
end
But you are trying to act, as if there was only one pic:
<%=image_tag site.user.pic.profile_pic.url(:small) %>
So either take first picture (probably you should also check if it exists) :
<%=image_tag site.user.pics.first.profile_pic.url(:small) %>
Or change the association to has_one if user can have only one picture:
class User < ActiveRecord::Base
...
has_one :pic, :dependent => :destroy
end
<%=image_tag site.user.pic.profile_pic.url(:small) %>

Resources