Updating child model with same data as parent - ruby-on-rails

I have four models related to each other as below:
class User < ActiveRecord::Base
has_many :clients
has_many :default_prices
end
class DefaultPrice < ActiveRecord::Base
has_many :client_prices
has_many :clients, :through => :user
belongs_to :user
end
class Client < ActiveRecord::Base
belongs_to :user
has_many :client_prices
before_create do
user.default_prices.each do |default_price|
client_prices.build("price" => default_price.price, "visit_type" => default_price.visit_type, "default_price_id" => default_price.id)
end
end
end
class ClientPrice < ActiveRecord::Base
belongs_to :client
belongs_to :default_price
end
Right now when a new client is created by the user, the user's default prices are applied to the client's client_prices table. How can I have new client_prices (for each existing client) created when new default_prices are created by the user? Also, how can I have the client_prices update when the default prices are changed? Each client prices has an default_price_id column that relates to the default price, if that helps.

class DefaultPrice < ActiveRecord::Base
before_create :new_client_price
before_update :update_clients_price
private
def new_client_price
clients.each do |c|
self.client_prices.create(:price => self.price, :visit_type => self.visit_type, :client_id => c.id)
end
end
def update_clients_price
self.client_prices.each do |p|
p.price = self.price
p.visit_type = self.visit_type
p.save
end
end

Related

Rails Factory Girl: How to setup has_many/through association with other model being created from nested attributes

I've been struggling with setting up a has_many/through relationship when one model object is being created through nested attributes using Factory Girl.
class CommercialInvoice < ActiveRecord::Base
has_many :shipment_commercial_invoices, :dependent => :destroy
has_many :shipments, :through => :shipment_commercial_invoices
end
class Shipment < ActiveRecord::Base
has_many :shipment_commercial_invoices, :dependent => :destroy
has_many :commercial_invoices, :through => :shipment_commercial_invoices
end
class ShipmentCommercialInvoices < ActiveRecord::Base
attr_accessible :job_id, :detail_id
belongs_to :shipment
belongs_to :commercial_invoice
end
The Shipment model is created using nested attributes inside another model supplier_invoice
class SupplierInvoice < ActiveRecord::Base
has_many :shipments, :dependent => :destroy
accepts_nested_attributes_for :shipments, :allow_destroy => true
end
class Shipment < ActiveRecord::Base
belongs_to :supplier_invoice
end
My Factories
factory :supplier_invoice do do
shipments_attributes do
shipments_attributes = []
2.times do # 2 shipments per supplier invoice
shipments_attributes << attributes_with_foreign_keys(:shipment)
end
shipments_attributes
end
end
factory :commercial_invoice do
after(:create) do |commercial_invoice, evaluator|
commercial_invoice.shipments << FactoryGirl.create(:supplier_invoice).shipments
end
end
factory :shipments_commercial_invoice do
shipment
commercial_invoice
comm_invoiced true # A boolean attribute that i want to be true
end
Now the problem in whenever i create a new commercial invoice, the associated shipment_commercial_invoice's attribute comm_invoiced is always false. I am not able to figure out what am i doing wrong. Is there any problem with how i defined factories? Thanks in advance.

Getting values through relational table

In my app I have a page which render all the projects in the database. What I want is to be able to filter the results, for example by showing only the once in which the user is a member.
Below is from my projects controller, where I want to do exactly that (show only projects where the user is a member). What do is to first get all the projects from 'ProjectUser' where the user_id is found. Then I want to use this array to retrieve all the relevant projects from the table 'Projects', with the use of #user_is_member.project_id.
This does not work because it only give me ONE project, not all.
How can I change the code so I accomplish what I want?
The code:
#user_is_member = ProjectsUser.where(:user_id => current_user.id)
#user_is_member.each do |member|
#projects = Project.where(:id => member.project_id)
end
Tables:
projects_users:
project_id
user_id
projects:
id
...non relevant fields...
Models:
User model:
class User < ActiveRecord::Base
has_many :project_users
has_and_belongs_to_many :projects
end
Project model:
class Project < ActiveRecord::Base
has_and_belongs_to_many :users # => , :class_name => 'User'
belongs_to :user
end
ProjectUser model:
class ProjectsUser < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
Note: current_user is the currently logged in user (which has access to all the user fields, e.g "id")
This is not a good way to associate the models. If you want to associate user to project and make one user to admin/creator then you should associate your models as:
User
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :password
has_secure_password
has_many :projects_users
has_many :projects, :through => :projects_users
end
Project
class Project < ActiveRecord::Base
has_many :projects_users
has_many :users, :through => :projects_users
end
ProjectsUser
class ProjectsUser < ActiveRecord::Base
attr_accessible :role
belongs_to :user
belongs_to :project
end
I have added an extra column/attribute to your ProjectsUser Model that will have a string value (admin/member). And now you can get the members for a project by doing #project.users.where(:role => 'member') and admin by #project.users.where(:role => 'admin').first.
If you don't want to change your way then in your controller do something like:
#user_is_member = ProjectsUser.where(:user_id => current_user.id)
project_ids = []
#user_is_member.each do |member|
project_ids << member.project_id
end
#projects = Project.where(:id => project_ids)
If you want all the projects for the current_user, you can just do:
#projects = current_user.projects
I am assuming you have something like:
class User < ActiveRecord::Base
has_many :project_users
has_many :projects, :through => :project_users
end
class ProjectUser < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
class Project < ActiveRecord::Base
has_many :project_users
has_many :users, :through => :project_users
end
The problem is that you have no project_id for some user which is somehow deleted.
Check your database that which project_id is nil by putting puts like statements in a loop.
Alternatively, you can use :dependent => :destroy on your relationship to get rid of such inconsistent data. It usually happens while you are testing and somehow delete any record but forget to delete the foreign key association relation for other table.
This can be accomplished like following:
#user_is_member = ProjectsUser.where(:user_id => current_user.id)
#user_is_member.each do |member|
puts "Checking the project id existence for each user member"+member.project_id.to_s
end

Rails: Prioritize/sort collection of records

I have a Post model that belongs to a single category and author. Users may create "favorites" for categories and authors. How can I most efficiently query a list of all posts, but with the visitor's preferred categories and/or authors sorted to the top?
class Post < ActiveRecord::Base
belongs_to :category
belongs_to :author
end
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :category # favorite category
belongs_to :author # favorite author
end
class User < ActiveRecord::Base
has_many :favorites
end
class User < ActiveRecord::Base
has_many :favorites
has_many :favorite_categories, :through => :favorites, :source => :category
has_many :favorite_authors, :through => :favorites, :source => :author
end
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :category # favorite category
belongs_to :author # favorite author
end
class Post < ActiveRecord::Base
belongs_to :category
belongs_to :author
named_scope :order_by_user, lambda {|user| where(
:category_id => user.favorite_categories.map(&:id),
:author_id => user.favorite_authors.map(&:id)
)}
end
user = User.first
posts = Post.order_by_user(user)
Alternate: less number of queries, but user model fetches data from Favorite
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :category # favorite category
belongs_to :author # favorite author
end
class User < ActiveRecord::Base
has_many :favorites
def favorite_category_ids
Favorite.where(:user_id => self.id).select(:category_id).map(&:category_id).compact
end
def favorite_author_ids
Favorite.where(:user_id => self.id).select(:author_id).map(&:author_id).compact
end
end
class Post < ActiveRecord::Base
belongs_to :category
belongs_to :author
named_scope :order_by_user, lambda {|user| where(
:category_id => user.favorite_category_ids,
:author_id => user.favorite_author_ids
)}
end
user = User.first
posts = Post.order_by_user(user)
This code is not tested, but gives the idea.

Naming error in controller

I get this error when trying to create a record in my joins table
NameError in
SubscriptionsController#new
uninitialized constant
Channel::ChannelsUser
Subscriptions Controller
class SubscriptionsController < ApplicationController
helper_method :current_user_session, :current_user
filter_parameter_logging :password, :password_confirmation
def new
#channel = Channel.find(params[:channel_id])
#user = current_user
#channel.subscribers << #user
#channel.save
flash[:notice] = "You have subscribed to: " +#channel.name
redirect_to #channel
end
end
end
User Model
class User < ActiveRecord::Base
acts_as_authentic
ROLES = %w[admin moderator subscriber]
#Each user can subscribe to many channels
has_many :channels_users
has_many :subscriptions, :class_name => "Channel", :through => :channels_users
#Each user who is a moderator can moderate many channels
has_many :channel_mods
has_many :channels, :through => :channel_mods
#Each user can receive many messages
has_many :messages_users , :dependent => :destroy
has_many :reciepts , :class_name => "User", :through => :messages_users
#Filter users by role(s)
named_scope :with_role, lambda { |role| {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0 "} }
def roles
ROLES.reject { |r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? }
end
def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum
end
def role_symbols
role.map do |role|
role.name.underscore.to_sym
end
end
end
Channel Model
class Channel < ActiveRecord::Base
#Each channel owns many or no messages
has_many :messages
#Each channel is own by one moderator
has_many :channel_mods
has_many :moderators, :class_name =>'User', :through =>:channel_mod
#Each channel can have and belong to many or no users
has_many :channels_users
has_many :subscribers, :class_name => 'Users' , :through => :channels_users
end
ChannelsUsers model
class ChannelsUsers < ActiveRecord::Base
belongs_to :user
belongs_to :channel
end
This would read much nicer if you change the model to ChannelUser. Here are the corresponding relationships:
class Channel < ActiveRecord::Base
has_many :channel_users
has_many :users, :through => :channel_users
end
class User < ActiveRecord::Base
has_many :channel_users
has_many :channels, :through => :channel_users
end
class ChannelUser < ActiveRecord::Base
belongs_to :channel
belongs_to :user
end
Your join table would then be called channel_users. I think you named it channels_users initially because that's the setup for a has_and_belongs_to_many join table. But since you're using has_many :through, you're free to name the table as you like.
I wrote a blog article earlier this year that walks through all the options in detail:
Basic Many-to-Many Associations in Rails
I hope this helps!
Your channel user class name is a plural. It is supposed to be singular.
So either you can change to this:
class ChannelsUser < ActiveRecord::Base
belongs_to :user
belongs_to :channel
end
or change this line in User and Channel model:
has_many :channels_users
to
has_many :channels_users, :class_name => 'ChannelsUsers'
Rails will use the methods like String#classify and String#underscore to detect classes and relationships.
If you want to play around with the names, in the console try out various combinations:
>> "channels_users".classify
=> "ChannelsUser"
>> "ChannelsUser".underscore
=> "channels_user"

Recursive :include in Rails ActiveRecord

Say I have these models
class Project < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
end
So that I can do
p = Project.find(1, :include => :comments)
p.comments.collect(&:user).collect(&:name) # this executes select for each user
How do I say I want to also include comment's user?
I believe :include => {:comments => :user} should work.

Resources