has_many through form and adding attribute to join table - ruby-on-rails

This is my first stakoverflow question and fairly new to Rails. I've searched through previous similar questions but can't seem to figure this one out. I have a has_many through relationship with Users and Accounts and I have an extra boolean attribute "account_admin" on the UserAccount model (join table). I'm trying to figure out how to set this when I create a new Account.
User:
class User < ActiveRecord::Base
has_many :user_accounts, :dependent => :destroy
has_many :accounts, :through => :user_accounts
..
end
Account:
class Account < ActiveRecord::Base
has_many :user_accounts, :dependent => :destroy
has_many :users, :through => :user_accounts
end
UserAccount:
class UserAccount < ActiveRecord::Base
belongs_to :user
belongs_to :account
# includes account_admin boolean
end
I would like to be able to create an account, assign users to that account, and designate an account_admin all in one form. I have this so far which allows me to select the users in an account but I'm not sure how to set the account_admin boolean here as well.
= simple_form_for #account do |f|
= f.input :name
= f.input :description
= f.association :users, label_method: :to_label, :as => :check_boxes
= f.button :submit
Appreciate any tips. Thanks

Account model:
class Account < ActiveRecord::Base
attr_accessible :name, :description, :user_accounts_attributes
has_many :user_accounts, :dependent => :destroy
has_many :users, :through => :user_accounts
has_many :user_without_accounts, :class_name => 'User', :finder_sql => Proc.new {
%Q{
SELECT *
FROM users where id NOT IN (Select user_id from user_accounts where account_id = #{id})
}
}
accepts_nested_attributes_for :user_accounts, reject_if: proc { |attributes| attributes['user_id'] == '0' }
def without_accounts
new_record? ? User.all : user_without_accounts
end
end
In form:
= simple_form_for #account do |f|
= f.input :name
= f.input :description
- #account.user_accounts.each do |user_account|
= f.simple_fields_for :user_accounts, user_account do |assignment|
= assignment.check_box :user_id
= assignment.label :user_id, user_account.user.name rescue raise user_account.inspect
= assignment.input :account_admin
%hr
- #account.without_accounts.each do |user|
= f.simple_fields_for :user_accounts, #account.user_accounts.build do |assignment|
= assignment.check_box :user_id
= assignment.label :user_id, user.name
= assignment.input :account_admin
%hr
= f.button :submit

Related

Rails 5.1, search scope within a group

I have a little issue with a search scope.
Users can join groups by providing a token. User can also create spendings and they can share them in a group. We are in the show view of a group. This is where I loop through spendings that each user has in this group. It looks something like that :
<% group_spendings_paginate_search.each do |groupspending| %>
<%= groupspending.member.user.firstname %>
<%= groupspending.spending.title %>
<%= groupspending.spending.description %>
<%= '%.02f' % groupspending.spending.amount %>
<%= groupspending.spending.currency.symb %>
<%= groupspending.created_at.strftime("%d/%m/%Y") %>
<% end %>
The group_spendings_paginate_search comes from a helper which represent this :
def group_spendings_paginate_search
#search.scope.order('created_at DESC').paginate(:page => params[:page], :per_page => 10)
end
The search form looks like this and is just above the loop :
<%= form_tag group_path(#group), method: :get do %>
<%= date_field_tag "search[date_from]", #search.date_from %>
<%= date_field_tag "search[date_to]", #search.date_to %>
<%= select_tag "search[user_id]", options_from_collection_for_select(#group.users.all, :id, :firstname, params[:user_id]), include_blank: "All Users" %>
<%= select_tag "search[currency_id]", options_from_collection_for_select(Currency.all, :id, :name, params[:currency_id]), include_blank: "All Currencies" %>
<%= submit_tag "Search", name: nil %>
<% end %>
Note that here I had to go for Currency.all which is not the currencies the user are using. They may only use euro for example and here the all list is going to pop, which is also not very cool. Would prefer to show only the currencies the users are using according to the group and spending. But hey it's already complicated enough so for now I'll keep it simple and come back on that later. But if anybody has a idea, feel free.
My controller :
def show
#search = GroupspendingSearch.new(params[:search])
end
And finally my GroupspendingSearch.rb
class GroupspendingSearch
attr_reader :date_from, :date_to, :user_id, :currency_id
def initialize(params)
params ||= {}
#date_from = parsed_date(params[:date_from], 1.month.ago.to_date)
#date_to = parsed_date(params[:date_to], Date.tomorrow)
#user_id = params[:user_id]
#currency_id = params[:currency_id]
end
def scope
launch = Groupspending.where('groupspendings.created_at BETWEEN ? AND ?', #date_from, #date_to)
launch = launch.where(member_id: find_member) if find_member.exists?
launch = launch.where(spending_id: find_currency) if find_currency.exists?
launch
end
private
def parsed_date(date_string, default)
Date.parse(date_string)
rescue ArgumentError, TypeError
default
end
def find_member
Member.where(user_id: #user_id)
end
def find_currency
Spending.where(currency_id: #currency_id)
end
end
The scope as is is actually working. Only problem is that it goes for all the Groupspending and not #group.groupspendings. It means that if I go in any group I will see all the spendings and I want to avoid that of course. Also I had to be smart about how I find the user_id and the currency_id. Using the table Groupspending gives me only member_id and spending_id. With the group_id or token in this model everything would be way easier...
Basically I don't know how to specify in the scope to look for the #group if this makes any sense.
I thought about a couple of things. The first is in the controller and specify a other param like so #search = GroupspendingSearch.new(params[:search],#group) and add it in the model def initialize(params, group) but to be honnest I don't really know what I'm doing so yea...
I'm dry here, anybody to help ? Maybe I'm totally wrong here and there is a other approach.
Models relations (don't pay attention to link and notification, it's a other story ^^) :
class Currency < ApplicationRecord
has_many :spendings, dependent: :destroy
has_many :users, through: :spendings
end
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
has_many :grouplinks, through: :members
has_many :spendings, through: :groupspendings
has_many :groupspendings, through: :members
has_many :notifications, dependent: :destroy
def to_param
auth_token
end
end
class Groupspending < ApplicationRecord
belongs_to :spending
belongs_to :member
end
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
has_many :grouplinks, dependent: :destroy
has_many :groupspendings, dependent: :destroy
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
class Spending < ApplicationRecord
belongs_to :user
belongs_to :currency
has_many :groupspendings, dependent: :destroy
end
class User < ApplicationRecord
has_secure_password
has_many :spendings, dependent: :destroy
has_many :currencies, through: :spendings
has_many :links, dependent: :destroy
has_many :notifications, dependent: :destroy
has_many :members, :dependent => :destroy
has_many :groups, :through => :members
has_one :owned_group, foreign_key: "owner_id", class_name: "Group"
end
EDIT :
Well I just realized that actually the scope isn't correct... I look for member_id: find_member and this goes for all the member with a member_id: X. Which means that if a user is a member of many groups well... it shows multiple entries. I'm getting crazy ^^
Ok I think I got it but I want to be sure that there is no mistake or security issues...
Here is what I changed :
Helper
def group_spendings_paginate_search
m = Member.where(group_id: #group.id)
gs = Groupspending.where(member_id: m)
#search.scope.where(member_id: gs.all.map(&:member_id) ).order('created_at DESC').paginate(:page => params[:page], :per_page => 10)
end
I didn't change the view and didn't change the GroupspendingSearch either. With this new helper I retrieve only the members within the group which "blocks" the search query to that. I guess...
I don't know if it's the best, and I don't know if it's safe. In the URL if I try to change the user_id to someone else, if he/she is not part of the group it doesn't show up. So it looks ok. Of course I have to secure the show view by restricting only to the members of the group. But appart from that, would that be ok ?

How to limit an association list based on a related model

I know that I should know this, but I cannot seem to figure it out at all and I'm still new to development...
So I have four models...
Appointments
class Appointment < ActiveRecord::Base
belongs_to :user
belongs_to :profile
belongs_to :location
end
Profiles
class Profile < ActiveRecord::Base
belongs_to :user
has_many :appointments
has_many :profile_locations
has_many :locations, through: :profile_locations
accepts_nested_attributes_for :profile_locations, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :locations, reject_if: :all_blank, allow_destroy: true
end
profile_locations
class ProfileLocation < ActiveRecord::Base
belongs_to :profile
belongs_to :location
belongs_to :location_type
accepts_nested_attributes_for :location
end
and locations
class Location < ActiveRecord::Base
has_many :profile_locations
has_many :profiles, through: :profile_locations
has_many :appointments
end
On the create appointments page, I already have an associated profile on the record. I also have an association field on my simple_form for locations that I want to be able to assign to the appointment based on those tied to the profile..
I was trying something like this, but cannot seem to getting working.
%td= f.association :location, :as => :collection_select, collection: Location.where( location.profile_location.profile_id: #profile.id ), label_method: :address_1, value_method: :id, include_blank: false, :input_html => {:class => "input-small"}, :label => "Select The Location"
Am I missing something here or is there an easier way to query this? Any guidance on any of this would be helpful.
If you are are using simple_form you should be creating collection_input like this:
%td= f.input :location, collection: Location.joins(:profile_location).where(profile_locations: { profile_id: #profile.id })
Thanks ksarunas.... I needed a minor tweak, but got it running!
%td= f.association :location, :as => :collection_select, collection: Location.includes(:profile_locations).where(profile_locations: { profile_id: #appointment.profile_id })
Was getting an error trying to pull in the #profile.id and had to pluralize the profile_locationS in both places.

Rails 4 new record from different model

I am currently working on a rails based application to manage orders. (Rails 4.1.5 and ActiveAdmin)
I have these models:
class Customer < ActiveRecord::Base
has_many :estimates
has_many :orders
accepts_nested_attributes_for :estimates, :allow_destroy => true
accepts_nested_attributes_for :orders, :allow_destroy => true
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items, as: :cartable
accepts_nested_attributes_for :line_items, :allow_destroy => true
end
class Estimate < ActiveRecord::Base
belongs_to :customer
has_many :line_items, as: :cartable
accepts_nested_attributes_for :line_items, :allow_destroy => true
end
What I want to do is to create a new Order based on the Estimate record. The things work if I create a new order and show the edit page with:
member_action :confirm, :method => :get do
old_estimate = Estimate.find(params[:id])
new_estimate = Order.create(old_estimate.attributes.merge({:id => nil, :created_at =>
nil,:updated_at => nil}))
old_estimate.line_items.each do |li|
new_estimate.line_items.create(li.attributes.merge({:id => nil, :created_at => nil,
:updated_at => nil}))
end
redirect_to edit_customer_order_path(new_estimate.customer, new_estimate)
end
but I would like to use the "new" action and create the record only after it has been edited and confirmed.
I tried to use
redirect_to new_customer_order_path(old_estimate.customer, old_estimate.attributes)
and it will render the new form but without any parameters in it.
The params are in the URL but I got an "Unpermitted parameters:" in the log. All the params are permitted under Active Admin(either under other.rb and estimate.rb) as:
permit_params :id, :customer_id, :title, :edd, :total,
line_items_attributes: [:id, :cartable_id, :cartable_type, :product_type, :source_lang, :dest_lang, :unit_price, :total, :_destroy]
Anyone have any suggestion?
You can override the new action to set some default value in the form:
controller do
def new
record = YourActiveRecordModel.new #or find, if you pass id
record.some_attribute = some_value
new!
end
end
The corresponding input will be filled.

Nested Forms error on unknown attribute Id in Rails 3

I'm attempting the nested forms based on Ryan Bates' #196 Railscasts Nested Forms 1. I'm using Nested_form and Simple_form gems. Maybe code is competing with gems?
The error that won't seem to go away is
ActiveRecord::UnknownAttributeError at /surveys/new
unknown attribute: customer_id
This occurs when I'm on the Survey page and submitting "New Survey".
I've got a Customer model and a Contact model which are associated to Survey.
It's referring to the last line in Survey Controller:
def new
12 #survey = Survey.new
13 3.times do
14 customer = #survey.customers.build
15 2.times { customer.contacts.build }
Solutions I tried include doing migrations adding the CustomerId to the Survey table or the Customer table. I've currently removed all references to customer_id.
Here's survey.rb
class Survey < ActiveRecord::Base
has_many :contacts
has_many :customers, :dependent => :destroy
accepts_nested_attributes_for :customers, :reject_if => lambda { |a| a[:content].blank? } :allow_destroy => true
attr_accessible :name, :customers_attributes
end
Here's customer.rb
class Customer < ActiveRecord::Base
belongs_to :survey
has_many :contacts
attr_accessible :company, :first_name, :last_name, :contacts_attributes, :customer_id
accepts_nested_attributes_for :contacts, :reject_if => lambda { |a| a[:content].blank? } :allow_destroy => true
end
here is contact.rb
class Contact < ActiveRecord::Base
attr_accessible :mobile_phone, :email
belongs_to :customer
end
and here's my form.html.haml
= simple_nested_form_for #survey do |f|
= f.error_notification
= f.input :name
= f.fields_for :customers do |builder|
= render "customer_fields", :f => builder
%p= f.submit "Submit"
Any help for a newbie? Thank you!!

Complex virtual attributes creation/update

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!

Resources