How to refer to high-level model from nested one? - ruby-on-rails

I'm trying to make conditional validation based on Project status in Payment model. For example status can be "Talks" or "Active". What's the best way to do it considering the structure below?
class Project < ActiveRecord::Base
has_many :costs, :dependent => :destroy
has_many :payments, :through => :costs, :dependent => :destroy
accepts_nested_attributes_for :costs, :allow_destroy => true
end
class Cost < ActiveRecord::Base
has_many :payments, :dependent => :destroy
accepts_nested_attributes_for :payments, :allow_destroy => true
belongs_to :project
end
class Payment < ActiveRecord::Base
belongs_to :cost
validates_presence_of :value1, :if => :new?
validates_presence_of :value1, :if => :talks?
validates_presence_of :value2, :if => :active?
def new?
# if controller action is new
end
def talks?
# if project status is "Talks" (edit action)
end
def active?
# if project status is "Active" (edit action)
end
end

class Payment < ActiveRecord::Base
belongs_to :cost
has_one :project, :through => :cost
validates_presence_of :value1, :if => :new?
validates_presence_of :value1, :if => :talks?
validates_presence_of :value2, :if => :active?
def new?
self.new_record?
end
def talks?
project.status == "talks"
end
def active?
project.status == "active"
end
end

Related

Rails 5 Active Model Serializer display has many conditions in JSON

In my project I have those models and relationships:
class Course < ApplicationRecord
has_many :segments, inverse_of: :course, :dependent => :destroy, :autosave => true
accepts_nested_attributes_for :segments, :allow_destroy => true
end
class Segment < ApplicationRecord
validates :data ,presence: true, if: :segment_is_video?
validates :segment_type ,presence: true
validates_presence_of :course
belongs_to :course, inverse_of: :segments
has_many :questions
accepts_nested_attributes_for :questions, :allow_destroy => true
def segment_is_video?
segment_type == 'Video'
end
end
class Question < ApplicationRecord
validates_presence_of :segment
belongs_to :segment, inverse_of: :questions
end
I want to display the data field only if the type is Video, and I want to display questions array only if type field is Quiz. I'm using Active Model Serializer for that but it's not working. the data still displaying when the type is Quiz and the question array doesn't show at all.
this is my code for the serializers:
class CourseSerializer < ActiveModel::Serializer
attributes :id, :title, :author
has_many :segments
def root
'Course'
end
end
class SegmentSerializer < ActiveModel::Serializer
attributes :id, :unit_id, :unit_title, :name, :segment_type, :data
belongs_to :course
has_many :questions, if: -> { isQuiz }
def root
'Segments'
end
def include_data?
object.segment_type == 'Video'
end
def isQuiz
object.segment_type == 'Quiz'
end
end
class QuestionSerializer < ActiveModel::Serializer
attributes :id, :question, :answer1, :answer2, :answer3, :answer4, :correct
belongs_to :segment
def root
'Question'
end
end

How to turn this attr_writer attribute into an actual database column?

I have this Post model:
class Post < ActiveRecord::Base
attr_accessible :title, :content, :tag_names
has_many :taggings, :dependent => :destroy
has_many :tags, :through => :taggings
attr_writer :tag_names
after_save :assign_tags
before_create :init_sort_column
def tag_names
#tag_names || tags.map(&:name).join(" ")
end
private
def assign_tags
self.tags = []
return if #tag_names.blank?
#tag_names.split(" ").each do |name|
tag = Tag.find_or_create_by_name(name)
self.tags << tag unless tags.include?(tag)
end
end
def init_sort_column
self.content_changed_at = self.created_at || Time.now
end
end
and Tag model:
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :posts, :through => :taggings
has_many :subscriptions
has_many :subscribed_users, :source => :user, :through => :subscriptions
def tag_posts_count
"#{self.name} (#{self.posts.count})"
end
end
I would like to turn attr_writer :tag_names into an actual column in the database so I can do this: Post.find_by_tag_names("drinks").
How can I achieve this?
What do you think of turning around the associations?
Tag.find_by_name('drinks').posts
You should archive the same.

Preventing instances of this Post model from appearing twice (Rails)?

I have a Post model:
class Post < ActiveRecord::Base
belongs_to :user
has_many :taggings, :dependent => :destroy
has_many :tags, :through => :taggings
attr_writer :tag_names
after_save :assign_tags
before_create :init_sort_column
def tag_names
#tag_names || tags.map(&:name).join(" ")
end
private
def assign_tags
self.tags = []
return if #tag_names.blank?
#tag_names.split(" ").each do |name|
tag = Tag.find_or_create_by_name(name)
self.tags << tag unless tags.include?(tag)
end
end
end
a Tag model:
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :posts, :through => :taggings
has_many :subscriptions
#has_many :subscribed_users, :source => :user, :through => :subscriptions
end
and a User model:
class User < ActiveRecord::Base
(Code related to Devise)
has_many :posts, :dependent => :destroy
has_many :subscriptions
has_many :subscribed_tags, :source => :tag, :through => :subscriptions
has_many :subscribed_posts, :source => :posts, :through => :subscribed_tags
attr_writer :subscribed_tag_names
after_save :assign_subscribed_tags
def subscribed_tag_names
#subscribed_tag_names || subscribed_tags.map(&:name).join(' ')
end
private
def assign_subscribed_tags
#self.subscribed_tags = []
return if #subscribed_tag_names.blank?
#subscribed_tag_names.split(" ").each do |name|
subscribed_tag = Tag.find_or_create_by_name(name)
self.subscribed_tags << subscribed_tag unless subscribed_tags.include?(subscribed_tag)
end
end
end
In the index page users only see posts with tags they have subscribed to:
posts_controller.rb:
#posts = current_user.subscribed_posts.paginate(:page => params[:page],
:per_page => 5,
:order => params[:order_by])
Now say there is a post with the tags food and drinks, and the user has subscribed to these two tags. He will see the post twice; it seems like it is appearing once as a post tagged as food and then as a post tagged as drinks.
Is there a way of preventing posts like this from appearing twice?
Add :uniq => true as a parameter to the has_many in the User model:
has_many :subscribed_posts, :source => :posts, :through => :subscribed_tags, :uniq => true
The docs at http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many-label-Options says:
:uniq
If true, duplicates will be omitted from the collection. Useful
in conjunction with :through.

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

Passing two variables to separate table...associations problem

I have developed an application and I seem to be having some problems with my associations. I have the following:
class User < ActiveRecord::Base
acts_as_authentic
has_many :questions, :dependent => :destroy
has_many :sites , :dependent => :destroy
end
Questions
class Question < ActiveRecord::Base
has_many :sites, :dependent => :destroy
has_many :notes, :through => :sites
belongs_to :user
end
Sites (think of this as answers to questions)
class Site < ActiveRecord::Base
acts_as_voteable :vote_counter => true
belongs_to :question
belongs_to :user
has_many :notes, :dependent => :destroy
has_many :likes, :dependent => :destroy
has_attached_file :photo, :styles => { :small => "250x250>" }
validates_presence_of :name, :description
end
When a Site (answer) is created I am successfully passing the question_id to the Sites table but I can't figure out how to also pass the user_id. Here is my SitesController#create
def create
#question = Question.find(params[:question_id])
#site = #question.sites.create!(params[:site])
respond_to do |format|
format.html { redirect_to(#question) }
format.js
end
end
I'd think this would do the job
#question = current_user.questions.find params[:question_id]
if not, then just assign mannualy.
#site = #question.sites.build(params[:site])
#site.user = current_user
#site.save

Resources