I have a issue with foreign keys.
I have 3 tables : Users, Members and Groups.
Basically I want the user to be able to create groups. The user provide a name, a description and on create a token is generated. Then other users can join this group via the members table. The members table has user_id and group_id. User can join the group providing the group_id which is not very cool, instead I would like the users to join the group providing the token.
Everything is working except the fact that they can join with the token. My question is how can I use the token instead of the group_id ?
I already tried to add foreign keys like this but nothing is going on in the schema :
add_foreign_key :members, :groups, column: :group_id, primary_key: :auth_token
add_foreign_key :members, :groups, column: :auth_token, primary_key: :auth_token
I also tried to add foreign key in my model :
belongs_to :group, foreign_key: "group_auth_token"
has_many :members, foreign_key: "group_auth_token", class_name: "Group"
Here are my actual models in case :
Group.rb
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :users, :through => :members
end
User.rb
class User < ApplicationRecord
has_secure_password
has_many :members, :dependent => :destroy
has_many :groups, :through => :members
end
Member.rb
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
I'm really confused with all this, I'm pretty sure the answer is obvious but I'm just lost, anybody to help me out ?
Thanks
EDIT : It looks like it's not clear enough so I'm adding a few things. When the user create the group this is what's happening in my controller :
def create
#group = Group.new group_params
#group.owner_id = current_user.id if current_user
if #group.save
#user = current_user.id if current_user
Member.create(group_id: "#{#group.id}", user_id: "#{#user}")
flash[:success] = "The Group has been created."
redirect_to group_path(#group)
else
render 'new'
end
end
The Member.create is to add the User automatically in the group. If anybody want's to join a group this is what is going on in my member controller :
def create
#member = Member.new member_params
#member.user_id = current_user.id if current_user
if #member.save
flash[:success] = "You Joined the Group."
redirect_to mygroups_path
else
flash.now[:error] = "Something went wrong. Are you in the group already ?"
render 'new'
end
end
And of course there is a form to create a new member (join the group) where the user provide the group_id (member_params) in the controller.
I want to be able to use the token instead of the group_id.
EDIT 2 :
So I have a error with your code jvillian. I paste it in the MembersController. In my form I changed the :group_id by :auth_token and this is my form :
<%= form_for #member do |f| %>
<%= f.label :auth_token %>
<%= f.text_field :auth_token %>
<%= f.submit "Join the Group" %>
<% end %>
This code may not be exactly correct, but it should be in the right direction.
def create
if #group = Group.find_by(auth_token: params[:auth_token]) && current_user
if #group.users << current_user
flash[:success] = "You Joined the Group."
redirect_to mygroups_path
else
flash.now[:error] = "Something went wrong. Are you in the group already ?"
render 'new'
end
end
end
I put in:
params[:token]
Naturally, that may not be quite right. Adjust to fit your own params.
This bit:
if #group.users << current_user
may not be correct as I forget what the shovel operator returns. But, you can look that up in the guide.
TO CORRECT YOUR FORM
Presumably, you're doing something like:
class MembersController < ApplicationController
def new
#member = Member.new
end
...
end
So, when you do this:
<%= form_for #member do |f| %>
<%= f.label :auth_token %>
<%= f.text_field :auth_token %>
<%= f.submit "Join the Group" %>
<% end %>
form_for can access your #member instance.
BUT, when you do this:
<%= f.text_field :auth_token %>
You're getting an error because form_for is trying to access the .auth_token attribute on #member which, naturally, doesn't exist. But remember, all you're trying to do here is submit a form that has a token. So, instead, do something like:
<%= form_for #member do |f| %>
<%= label_tag 'auth_token', 'Token' %>
<%= text_field_tag 'auth_token' %>
<%= f.submit "Join the Group" %>
<% end %>
In this way, the auth_token field is no longer directly derived from #member and you're no longer trying to access a non-existent method on #member.
Your params will look something like:
Parameters: {"auth_token"=>"whatever the user entered"}
If you want the auth_token to be nested inside group, like this:
Parameters: {"group"=>{"auth_token"=>"whatever the user entered"}}
Then do:
<%= form_for #member do |f| %>
<%= label_tag 'group[auth_token]', 'Token' %>
<%= text_field_tag 'group[auth_token]' %>
<%= f.submit "Join the Group" %>
<% end %>
Your text is abit confusing, but maybe this setup of the references are what you are looking for?
add_reference :members, :group_auth_token, foreign_key: { to_table: :groups }, references: :auth_token
Related
I am working on similar app.I have 3 models: User, Group and Relation.I want to make a form where a logged in user can create a group and invite a college(other user registered in db).I am using has_many through association.Here are these models:
class Group < ApplicationRecord
has_many :relations
has_many :users, through: :relations
accepts_nested_attributes_for :relations
end
class Relation < ApplicationRecord
belongs_to :group
belongs_to :user
end
class User < ApplicationRecord
attr_accessor :remember_token
has_many :transactions, dependent: :destroy
has_many :relations
has_many :groups, through: :relations
<some validation >
end
My GroupsController
class GroupsController < ApplicationController
def new
#group = Group.new
#group.relations.build
end
def create
#group = Group.new(groups_params)
if #group.save
flash[:success] = "Group has been created!"
redirect_to root_path
else
flash[:danger] = "Something went wrong!"
render 'groups/new'
end
end
private
def groups_params
params.require(:group).permit(:group_name, :group_from, :group_to,
relations_attributes: Relation.attribute_names.map(&:to_sym) )
#Relation.attribute_names.map(&:to_sym) this grabs all the column names
#of the relations tabel, puts it in the array and maps each element with hash
#[:id, :group_id, :user_id, :created_at, :updated_at]
end
end
And my view for "new" action
<%= provide(:title, "New Group") %>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for #group do |group| %>
<%= group.label :group_name, "Group name"%>
<%= group.text_field :group_name %>
<%= group.label :group_from, "Group from"%>
<%= group.date_field :group_from %>
<%= group.label :group_to, "Group to"%>
<%= group.date_field :group_to %>
<!-- :relations is a symbol for assosiation between the group and user -->
<%= group.fields_for :relations do |relations_form| %>
<%= relations_form.label :user_id, "Member #1" %>
<%= relations_form.text_field :user_id %>
<% end %>
<%= group.submit "Create a group!" %>
<% end %>
</div>
</div>
screenshot: https://i.stack.imgur.com/u1LDX.png
With this code a logged in user is able to create group record and relation record simultaneously but it has to pass an id of user it wants to invite to a group(for example: "7" not "John" - the name of the user with id 7)
What I want to achieve is to get the id of the user names pass in the "Member #1" field.Example:
1.A logged in user puts in the "Member #1" field: "John"
2.Some function to get the id of the "John" - modify params
3.if "John" exists in users tabel then save the group -> create a group record and relation record.
I think I have to modify nested params, but I do not know how to do this.
Thanks for any help,
Lukas
Instead of an input text field, you should try out a select dropdown where you can choose the existing members. Like this it will show the name to the user but it will select the ID which will be sent to the controller.
I'm trying to create a new object with its associated records in the same form but would like the associated records to use find_or_create_by instead of just create (as the associated model records may and most of the time already will exist). I have spent the last two days digging through every post and article that I can find related to this subject trying to get this to work but still my form tries to create the new object only, not search for existing.
Models
#order.rb
has_many :order_owners, dependent: :destroy, inverse_of: :order
has_many :owners, through: :order_owners
accepts_nested_attributes_for :order_owners
#owner.rb
has_many :order_owners, dependent: :destroy
has_many :orders, through: :order_owners
validates :name, presence: true, uniqueness: { case_sensitive: false }
#order_owner.rb
belongs_to :owner
belongs_to :order
accepts_nested_attributes_for :owner
Form
orders/new.html.erb
<%= bootstrap_form_for(#orders, layout: :horizontal, label_col: "col-sm-2", control_col: "col-sm-6") do |f| %>
<%= render 'shared/error_messages', object: f.object %>
...
<%= f.fields_for :order_owners do |orderowner| %>
<%= render 'orders/new_order_owners', f: orderowner, render_partial: 'orders/new_order_owners' %>
<% end %>
...
<%= f.form_group do %>
<%= f.submit "Create Order", class: "btn btn-primary" %>
<%= link_to_add_association fa_icon("plus", text: "Add Owner"), f, :order_owners,
class: "btn btn-outline pull-right #{orderBtnDisable(#properties)}", partial: "orders/new_order_owners", id: "newOrderOwnerAdd" %>
<% end %>
<% end %>
orders/new_order_owners partial
<div class="m-t-md m-b-md border-bottom form-horizontal nested-fields">
<%= link_to_remove_association(fa_icon("remove", text: ""), f, { class: "btn btn-danger btn-outline pull-right" }) %>
<% f.object.build_owner unless f.object.owner %>
<%= f.fields_for :owner do |owner| %>
<%= owner.select :name, options_from_collection_for_select(#owners, "name", "name"),
{ label: "Name:", include_blank: true }, { id: "orderPropOwnerSelect", data: { placeholder: "Select an existing Owner or type a new one.."} } %>
<% end %>
</div>
Controller
orders/new
def new
#order = Order.new
#order.build_property
#order.order_owners.build.build_owner
#properties = Property.find_by_id(params[:property_id])
if #properties
#owners = #properties.owners
else
#owners = []
end
respond_to do |format|
format.html
format.js
end
end
orders/create
def create
#properties = Property.find(params[:order][:property_id])
#order = #properties.orders.create(order_params)
respond_to do |format|
format.html { if #order.save
if params[:order][:owners_attributes]
order_prop_owner_check(#order, #properties)
end
flash[:success] = "Order created successfully!"
redirect_to property_order_path(#properties, #order)
else
#properties
#owner = #properties.owner
render 'new'
end
}
format.js {
if #order.save
flash.now[:success] = "Order Updated Successfully!"
else
flash.now[:danger] = #order.errors.full_messages.join(", ")
end
}
end
end
So as you can see in the new action, I instantiate the new Order, build its associated property (its what the Order belongs_to), build the new order_owner relationship, and build the owner for that relationship. Then on submit it creates the order via #properties.orders.create(order_params).
The error that I get is "Order owners owner name already exists." so clearly its not looking up an owner by name. I have tried:
Redefining autosave_associated_records_for_owner in order.rb and order_owner.rb, using both belongs_to and has_many variations, but it seems like they never get called so I must be doing something wrong. (I have tried variations of almost every answer I could find on SO)
before_add: callback on both has_many :owners, through: :order_owners and has_many :order_owners in order.rb.
Extending has_many :owners and has_many :owners, through: :order_owners in order.rb as well as belongs_to :owner in order_order.rb
I've also tried different variations of calling associations and such within the form so I must be just misunderstanding something. I'm also using Cocoon to manage the nested forms but I've talked to the author on unrelated issues and Cocoon is essentially just a view helper for nested forms so the solution must something in the models/controller.
Any and all ideas welcome. Thanks in advance.
P.s. I left code in the controller actions that may/may not pertain to this exact post but I wanted to show the entire action for completeness. If it matters, I manually set the owners select via AJAX when a property is selected in another field. Basically it just looks up the property and adds existing owners to the owners select.
The owner is nested inside the order. So when you call order.save, it runs all validations (including owner's). If you want to use find_or_create_by you need to do it inside a before_save, so you can make modifications to the owner before the validation hits.
#order.rb
before_save :find_or_create_owner
def find_or_create_owner
self.order_owners.each do |order_owner|
order_owner.owner = Owner.find_or_create_by(name: final_owner.name)
end
end
Further customization may be needed depending on your form and business logic, but that's the main concept.
I have two models connected through a join model. The join model stores an additional attribute value, which is a boolean A Role has many Permissions through RolePermission, and RolePermission stores whether that Role can perform the action indicated by that Permission.
Every Role should have a RolePermission record for every Permission (of which there are ~10).
I'm having trouble creating my form, however. I want there to be a checkbox for each Permission, which is used to indicate the boolean value of the value attribute for RolePermission.
models
class Role < ApplicationRecord
has_many :role_permissions, dependent: :destroy
has_many :permissions, through: :role_permissions
accepts_nested_attributes_for :role_permissions
end
class RolePermission < ApplicationRecord
belongs_to :role
belongs_to :permission
end
class Permission < ApplicationRecord
has_many :role_permissions
has_many :roles, through: :role_permissions
end
table columns
Roles
name: string
description: string
Permissions
name: string
description: string
RolePermissions
role: references
permission: references
value: boolean
views/roles/_form.html.erb
<%= form_for [#chronicle, #role], url: url do |f| %>
...
<%= f.fields_for :role_permissions, Permission.all do |ff| %>
???
<%= ff.label :name %>
<%= ff.check_box :value %>
???
<% end %>
<%= f.submit text, class: 'btn btn-primary btn-block' %>
<% end %>
controllers/roles_controller.rb
class RolesController < ApplicationController
...
def create
#chronicle = Chronicle.find(params[:chronicle_id])
#role = #chronicle.roles.build(role_params)
???
#role_permissions = #role.role_permissions.build
???
if #chronicle.save
flash[:success] = 'Role successfully created.'
redirect_to chronicle_role_url(#chronicle, #role)
else
render 'new'
end
end
private
def role_params
params.require(:role).permit(:name, :description, role_permission_attributes: [] )
end
end
end
I still believe in the concept without the Role_permission model in the middle. Each role should have their own permissions (has_many), but the permission name and description are being reused when each role has their permission created.
Here is what i did.
I created a hash with the values for name and description in the private section of the role-controller
# roles_controller.rb
def permission_values
{
"Can edit" => "This is not good if you want to party",
"Can show" => "Yes, yes, yes. Go ahead",
"Can hike" => "Oh my! A hike-role?",
"And so on" => "What do we have here?"
}
end
Then when i create the Role and the permissions i use .build with the permission_values.
Couldn't do it with seeds.rb because the Role has to be created before assigning permissions
#roles_controller.rb
def new
#role = Role.new
permission_values.each do |titel, desc|
#role.permissions.build(name: titel, description: desc)
end
end
And in the form i use .object and .hidden_field to show and create the values.
# _form.html.erb
...
<%= f.fields_for :permissions do |ff|%>
<h4><%= ff.object.name %></h4>
<%= ff.hidden_field :name %>
<p><%= ff.object.description %></p>
<%= ff.hidden_field :description %>
<%= ff.check_box :permitted %>
<% end %>
And there you have it!
Now each user will have the same permissions displayed, but they can check permitted if they want and save it with their Role.
In the show, something like:
# show.html.erb
# I would make a helper-method
# And some index-symbol-power!
<%= "Hey! I'm permitted to do this!" if #role.permissions.find_by_name("Can
edit").permitted? %>
Note:
Remember the :id in the permission_attributes in the role_params, so the permissions doesn't duplicate on edit.
========================================================================
Is the RolePermission model necessary?
Let me know if i have misunderstood something. Why not put a boolean attribute on the Permission model?
table columns:
Roles
name: string
description: string
Permissions
referenced: role
name: string
description: string
permitted: boolean
Now you can have a checkbox field in the field_for without any problems
#In form_for [#chronicle, #role], url: url do |f|
...
<%= f.fields_for :permissions do |ff| %>
<%= ff.label :permitted %>
<%= ff.check_box :permitted %>
... <!-- description and name -->
And your controller
Remember not to call Permission.all in the view, but use .build in the action new in the RolesController.
def new
#Find #chronicle
#role = Role.new
10.times { #role.permissions.build }
end
def create
# If you set up your accepts_nested_attributes_for :permissions
# (delete the rest of role_permission stuff) in role.rb,
# and update your role_params with the permission_attributes everything should
be
# working fine. Find your role and save it.
end
And then you can do something like:
if role.permissions.first.permitted?
# DO SOMETHING CRAZY
end
I have three model classes related to each other.
class Student < ActiveRecord::Base
has_many :marks
belongs_to :group
accepts_nested_attributes_for :marks,
reject_if: proc { |attributes| attributes['rate'].blank?},
allow_destroy: true
end
This class describes a student that has many marks and I want to create a Student record along with his marks.
class Mark < ActiveRecord::Base
belongs_to :student, dependent: :destroy
belongs_to :subject
end
Marks are related both to the Subject and a Student.
class Subject < ActiveRecord::Base
belongs_to :group
has_many :marks
end
When I try to create the nested fields of marks in loop labeling them with subject names and passing into in it's subject_id via a loop a problem comes up - only the last nested field of marks is saved correctly, whilst other fields are ignored. Here's my form view code:
<%= form_for([#group, #student]) do |f| %>
<%= f.text_field :student_name %>
<%=f.label 'Student`s name'%><br>
<%= f.text_field :student_surname %>
<%=f.label 'Student`s surname'%><br>
<%=f.check_box :is_payer%>
<%=f.label 'Payer'%>
<%= f.fields_for :marks, #student.marks do |ff|%>
<%#group.subjects.each do |subject| %><br>
<%=ff.label subject.subject_full_name%><br>
<%=ff.text_field :rate %>
<%=ff.hidden_field :subject_id, :value => subject.id%><br>
<%end%>
<% end %>
<%= f.submit 'Add student'%>
<% end %>
Here`s my controller code:
class StudentsController<ApplicationController
before_action :authenticate_admin!
def new
#student = Student.new
#student.marks.build
#group = Group.find(params[:group_id])
#group.student_sort
end
def create
#group = Group.find(params[:group_id])
#student = #group.students.new(student_params)
if #student.save
redirect_to new_group_student_path
flash[:notice] = 'Студента успішно додано!'
else
redirect_to new_group_student_path
flash[:alert] = 'При створенні були деякі помилки!'
end
end
private
def student_params
params.require(:student).permit(:student_name, :student_surname, :is_payer, marks_attributes: [:id, :rate, :subject_id, :_destroy])
end
end
How can I fix it?
#student.marks.build
This line will reserve an object Mark.
If you want multi marks, May be you need something like this in new action :
#group.subjects.each do |subject|
#student.marks.build(:subject=> subject)
end
Hope useful for you.
My models:
brand.rb
has_many :products
has_many :votes
belongs_to :user
accepts_nested_attributes_for :products, :allow_destroy => true
product.rb
belongs_to :user
belongs_to :brand
vote.rb
belongs_to :brand
belongs_to :user
routes.rb
resources :brands do
resources :products
end
My goal: Create 2 records (product and vote) on existing Brand record in 1 form, on brand/show page.
My solution:
brand/show.html.erb
<% form_for([#brand, #brand.send(:product).klass.new]) do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :price %>
<%= f.text_field :price %>
<%= fields_for :votes, #brand.votes.new do |builder| %>
<%= builder.label :rating %>
<%= builder.text_field :rating %>
<% end %>
<%= f.submit %>
<% end %>
products_controller.rb
def create
if Brand.exists?(:id => params[:brand_id])
#review = Review.new(review_params)
#vote = Vote.new(votes_params)
#review.user_id = #vote.user_id = current_user.id
#review.brand_id = #vote.brands_id = params[:brand_id]
if #vote.valid? && #review.valid?
#vote.save
#review.save
redirect_to brands_path
else
flash[:errors][:vote] = #vote.errors
flash[:errors][:review] = #review.errors
redirect_to brands_path
end
end
end
private
def product_params
params.require(:review).permit(:title, :price)
end
def votes_params
params.require(:votes).permit(:rating)
end
Is this right way of solving my task? Can I use it like that?
This is how I would refactor your create method:
def create
brand = Brand.find(params[:brand_id]) # no test to know if Brand exists, if it does not it means the user gave a wrong Brand id, then a 404 error should be rendered
#product = brand.products.create(products_params.merge({user_id: current_user.id}) # we can directly create this instance
#vote = brand.votes.create(votes_params) # we can directly create this instance
# we add the errors in the flash if they exist
flash[:errors][:vote] = #vote.errors if #vote.errors.present?
flash[:errors][:product] = #product.errors if #product.errors.present?
redirect_to brands_path # since we want to redirect to brands_path if the creation succeeded or failed, we don't need to use it twice in the code
end
Also, little improvements:
#brand.send(:product).klass.new
# can become
#brand.products.new # produces a initialized Product instance
fields_for :votes, #brand.votes.new
# can become
f.fields_for #brand.votes.new
# notice the usage of `f` builder to generate the fields_for
# it will nest the params in `params[:brand][:votes_attributes][0]`
# brand.rb
accepts_nested_attributes_for :products, :allow_destroy => true
# add the following:
accepts_nested_attributes_for :votes, :allow_destroy => true
You will obviously have to update your strong params accordingly, but this is the easy part ;-)
I'd change the following logic:
#review.user_id = #vote.user_id = current_user.id
#review.server_id = #vote.server_id = params[:server_id]
Just add the current_user.id and params[:server_id] to the product_params and votes_params respectively.
Also, don't see the need for using instance variables for vote/review.
Other than that the saving two models seems OK to me.