I asked this question differently, but deleted it to attempt more clarity.
I have an Article model. It has_many contacts, through: :article_contacts.
I have a Contact model. It has_many articles, through: :article_contacts.
What I need is each Contact object to be unique, but be able to be associated with different articles. My last question led to people showing me how to display only unique contacts, or to validate the join model, but that's not what I need.
I need, for example, tables like the following:
Article
id: 1, name: "Whatever", content: "Whatever"
id: 2, name: "Again, whatever", content: "Whateva"
Contact
id: 1, email: "email#email.com"
id: 2, email: "secondemail#email.com"
ArticleContact
id: 1, article_id: 1, contact_id: 1
id: 2, article_id: 1, contact_id: 2
id: 3, article_id: 2, contact_id: 1
So, when I build the association in my Article controller in the new action and I call #article.save in the create action, I get an insert of the new article, an insert of the contact and an insert of the article_relationship. Great.
BUT, on Article 2, if I add the same email to the contact form, I do not want to create another Contact with the same email. But I do want (as you see with the third ID in ArticleContact above) to create. But each call to #article.save does it. I've tried first_or_initialize and << into the collection, but it always creates multiple times and if I have a validation, it means I can't create the ArticleContact relationship because contact is unique.
I may eventually have a drop down with contacts, but I suspect that will be long, so I'd rather simply enter an email into a form and have the code check that it is unique and if so, just create the join relationship using the existing ID.
This must be easier than I am conceiving, but I can't find a single article that demonstrates this anywhere.
Updated with code per request, though, again, I don't have the code to only create a unique contact and associate the relationship with an existing one. That's what I'm missing so this code will only show you what I already know works and not how to get to what I want :).
articlecontroller:
def new
#article = #business.articles.build
authorize #business
#article.attachments.build
#article.contacts.build
end
def create
#article = #business.articles.new(article_params)
authorize #business
respond_to do |format|
if #article.save
format.html { redirect_to business_article_path(#business, #article), notice: "Knowledge created." }
else
format.html { render 'new' }
end
end
end
Models have the standard has_many :thruogh and belongs to. I can show it, but they are the right way. View is just a standard simple_form building the contact:
<%= f.simple_fields_for :contacts, class: "form-inline" do |contact| %>
<%= contact.input :first_name, label: "Contact First Name" %>
<%= contact.input :last_name, label: "Contact Last Name" %>
<%= contact.input :email, label: "Contact Email" %>
<% end %>
You should enforce uniqueness, just in case:
# contact.rb
class Contact
...
validates_uniqueness_of :email
...
end
But in your controller you could do:
# articles_controller.rb
def create
...
# we can rely on it's uniqueness
contacts << Contact.find_or_create_by(email: param[:email)
...
end
Or whatever best fits your needs.
Hope this helps!
give your ArticleContact a validation with a scope.
validates :article_id, unique {scope: :contact_id}
this will prevent you from having ArticleContacts that are exactly the same.
when you create a contact, try:
contact = Contact.where(email: "e#mail.com:").first_or_create
in contact.rb
validates :email, uniqueness: true
You could try this in the create method
verified_contacts = []
#article.contacts.each do |contact|
existing_contact = Contact.find_by(email: contact.email)
verified_contacts << (existing_contact || contact)
end
#article.contacts = verified_contacts
Related
I have three models: List, Food, and Quantity. List and Food are associated through Quantity via has_many :through. The model association is doing what I want, but when I test, there is an error.
test_valid_list_creation_information#ListsCreateTest (1434538267.92s)
ActionView::Template::Error: ActionView::Template::Error: Couldn't find Food with 'id'=14
app/views/lists/show.html.erb:11:in `block in _app_views_lists_show_html_erb__3286583530286700438_40342200'
app/views/lists/show.html.erb:10:in `_app_views_lists_show_html_erb__3286583530286700438_40342200'
test/integration/lists_create_test.rb:17:in `block (2 levels) in <class:ListsCreateTest>'
test/integration/lists_create_test.rb:16:in `block in <class:ListsCreateTest>'
app/views/lists/show.html.erb:11:in `block in _app_views_lists_show_html_erb__3286583530286700438_40342200'
app/views/lists/show.html.erb:10:in `_app_views_lists_show_html_erb__3286583530286700438_40342200'
test/integration/lists_create_test.rb:17:in `block (2 levels) in <class:ListsCreateTest>'
test/integration/lists_create_test.rb:16:in `block in <class:ListsCreateTest>'
My aim is to create a new Quantity (associated with that list) each time a list is created. Each Quantity has amount, food_id, and list_id.
list_id should equal the id of the list that was just created.
food_id should equal the id of a random food that already exists.
amount should be a random integer.
In the error, the number 14 ("Food with 'id'=14) is generated by randomly selecting a number from 1 to Food.count. Food.count equals the number of food objects in test/fixtures/foods.yml, so the foods are definitely recognized, at least when I run Food.count. So why wouldn't food with 'id'=14 exist?
I believe there is something wrong with either the Lists controller, the fixtures, or the integration test. Whatever is causing the test to fail doesn't seem to affect performance (everything works in the console and server/user interface), but I am trying to understand TDD and write good tests, so I will appreciate any guidance.
Lists model:
class List < ActiveRecord::Base
has_many :quantities
has_many :foods, :through => :quantities
validates :days, presence: true
validates :name, uniqueness: { case_sensitive: false }
after_save do
Quantity.create(food_id: rand(Food.count), list_id: self.id, amount: rand(6))
end
end
Quantities fixture:
one:
food: grape
list: weekend
amount: 1
two:
food: banana
list: weekend
amount: 1
Note: the Quantities fixture was previously organized as follows ...
one:
food_id: 1
list_id: 1
amount: 1
... and it seems to make no difference.
lists_create integration test:
require 'test_helper'
class ListsCreateTest < ActionDispatch::IntegrationTest
test "invalid list creation information" do
get addlist_path
assert_no_difference 'List.count' do
post lists_path, list: { days: "a",
name: "a" * 141 }
end
assert_template 'lists/new'
end
test "valid list creation information" do
get addlist_path
assert_difference 'List.count', 1 do
post_via_redirect lists_path, list: {
days: 2,
name: "example list"
}
end
assert_template 'lists/show'
end
end
And app/views/lists/show.html.erb referenced in the error:
<% provide(:title, #list.name) %>
<div class="row"><aside class="col-md-4"><section class="user_info">
<h1> <%= #list.name %></h1>
<p><%= #list.days %> day(s)</p><p>
<% Quantity.where(:list_id => #list.id).each do |f| %>
<%= "#{f.amount} #{Food.find(f.food_id).name}" %>
<% end %>
</p></section></aside></div><%= link_to "edit the properties of this list", edit_list_path %>
Thank you for any advice or references. Please let me know if you need other code or information that you consider relevant. I am hoping to accomplish this all using fixtures and not another method such as FactoryGirl, even if it means a little extra code.
Rails 4.2.3, Cloud9. Development database = SQLite3, production database = postgres heroku.
Besides being very weird to create a random value in the after_save callback (which I think you're doing as an exercise, but anyway it's better to use good practices from the start), you should never use rand(Model.count) to get a sample record. There's two main problems:
The rand(upper_bound) method returns a number between zero and the upper_bound argument, but there's no guarantee that zero is the first created id. I'm using PostgreSQL and the first model has the id 1. You can specify a range (rand(1..upper_bound)), but anyway you're gambling on the way the current database works.
You're assuming that all the records exist in a sequential order at any given time, which is not always true. If you delete a record and it's id is randomly chosen, you'll get an error. The library also can use any strategy to create the fixtures, so it's better not to assume anything about how it works.
If you really need to choose randomly a record, I'd recommend simply using the array's sample method: Food.all.sample. It's slow, but it works. If you need to optimize, there's other options.
Now, I'd really recommend to avoid random values at all costs, using them only when necessary. It's difficult to test, and difficult to track bugs. Also, I'd avoid creating a relation inside a callback, it grows rapidly into a unmanageable mess.
I am posting an answer because after implementing the suggestions, my error is gone and I think I have a better understanding of what's going on.
Previously, I had Quantities created in the List model upon creation of a List using a relation. The relation is now in the controller, not the model.
List model without relation:
class List < ActiveRecord::Base
has_many :quantities
has_many :foods, :through => :quantities
validates :days, presence: true
validates :name, uniqueness: { case_sensitive: false }
end
Quantities fixture and lists_create integration test are unchanged.
Previously this show.html.erb contained a query. Now, it has only #quantities, which is defined in the Lists controller. The query is in the controller, not the view.
app/views/lists/show.html.erb:
<% provide(:title, #list.name) %>
<div class="row"><aside class="col-md-4"><section class="user_info">
<h1> <%= #list.name %></h1>
<p><%= #list.days %> day(s)</p>
<p><%= #quantities %></p>
</section></aside></div><%= link_to "edit the properties of this list", edit_list_path %>
The List controller with the query in the show method (to filter for quantities that have the proper list_id) and the relation in the create method (to create new quantities upon list creation).
class ListsController < ApplicationController
def show
#list = List.find(params[:id])
#quantities = []
Quantity.where(:list_id => #list.id).each do |f|
#quantities.push("#{f.amount} #{Food.find(f.food_id).name}")
end
end
# ...
def create
#list = List.new(list_params)
if #list.save
flash[:success] = "A list has been created!"
#a = Food.all.sample.id
#b = Food.all.sample.id
Quantity.create(food_id: #a, list_id: #list.id, amount: rand(6))
if (#a != #b)
Quantity.create(food_id: #b, list_id: #list.id, amount: rand(6))
end
redirect_to #list
else
render 'new'
end
end
# ...
end
If I understand correctly, I was misusing the model and view and inappropriately using rand with Food.count.
Please let me know if you think I've missed anything or if you can recommend anything to improve my code. Thank you #mrodrigues, #jonathan, and #vamsi for your help!
here is my code:
Perk not save on multiple select,when multiple true/false. perk save and habtm working.
class Perk < ActiveRecord::Base
has_and_belongs_to_many :companies
end
class Company < ActiveRecord::Base
has_and_belongs_to_many :perks
end
view perk/new.html.erb
<%= select_tag "company_id", options_from_collection_for_select(Company.all, 'id', 'name',#perk.companies.map{ |j| j.id }), :multiple => true %>
<%= f.text_field :name %>
Controller's code:
def new
#perk = Perk.new
respond_with(#perk)
end
def create
#perk = Perk.new(perk_params)
#companies = Company.where(:id => params[:company_id])
#perk << #companies
respond_with(#perk)
end
Your select_tag should return an array of company_ids:
<%= select_tag "company_ids[]", options_from_collection_for_select(Company.all, 'id', 'name',#perk.companies.map{ |j| j.id }), :multiple => true %>
http://apidock.com/rails/ActionView/Helpers/FormTagHelper/select_tag#691-sending-an-array-of-multiple-options
Then, in your controller, reference the company_ids param:
#companies = Company.where(:id => params[:company_ids])
(I assume that you've intentionally left out the #perk.save call in your create action... Otherwise, that should be included as well. Model.new doesn't store the record.)
It sounds like you may not have included company_id in the perk_params method in your controller. Rails four uses strong pramas this means you need to state the params you are allowing to be set.However it is difficult to say for sure without seeing more of the code.
In your controller you should see a method like this (there may be more options that just :name):
def perk_params
params.require(:perk).permit(:name)
end
You should try adding :company_id to it so it looks something like this:
def perk_params
params.require(:perk).permit(:name, :company_id)
end
if there are other params int your method leave them in and just added :company_id
EDIT to original answer
The above will only work on a one-to-many or one-to-one because you are using has_and_belongs_to_many you will need to add companies: [] to the end of your params list like this
def perk_params
params.require(:perk).permit(:name, companies: [] )
end
or like this
def perk_params
params.require(:perk).permit(:name, companies_ids: [] )
end
See these links for more details:
http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html
http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters
I have a text field in my database called departments where i want to store the list of departments. The user will enter the name of departments with comma separation. For example:
department1, deaprtment2, department3
I want this value to be stored as array when the user submits the form. Also, i want the list of departments to show as a drop-down. Finally, while updating the table , the department field should also be editable as before(update by entering texts separated by commas).
EDIT:
I have added this to my model:
class Org < ActiveRecord::Base
serialize :department, Array
attr_accessible :name, :department
before_validation :update_department
validates :name, presence: true
def update_department
if department_changed? and department.is_a?(String)
self.department = self.department.split(',').collect(&:strip)
end
end
end
and the view:
<%= f.text_area :department, :cols => "10", :rows => "10" %>
now Whenever i try to sign up, the department field already has [] present and when i try to update the department is already ["[department1", "department2]"].
I want [] to be removed while signing up and only department1, department2 to show up when updating.
Please Help.
The best way to do this would be via your models. I am assuming that you have a model called Org and another called Department and that you have defined a has many relationship between the two. All you then need to do is in your Org model add the following code:
def department_list
departments.collect { |d| d.department_name }.join(', ')
end
def department_list=(text)
if id && text
departments.destroy_all
text.split(',').each do |d|
departments.create(department_name: d.strip.capitalize)
end
end
end
Then in your view add a text box using #org.department_list.
EDIT:
Based on your expanded question, you have department field in an org model that you want to store and show as an array and but edit as a simple text field. My thoughts on this was that I don't like the idea of storing department data a field in org, it is a one to many relationship so department should be a separate model. I would remove the department field from org. Then create a migration to create a departments table. It should look something like this:
class CreateDeparments < ActiveRecord::Migration
def change
create_table :departments do |t|
t.integer :org_id
t.string :department_name
t.timestamps
end
end
end
Next in the Department model add this line of code:
belongs_to :org
In the org model add the following:
has_many :departments, dependent: :destroy
def department_list
departments.collect { |d| d.department_name }.join(', ')
end
def department_list=(text)
if id && text
departments.destroy_all
text.split(',').each do |d|
departments.create(department_name: d.strip.capitalize)
end
end
end
In your controllers and views you now have the following:
#org = Org.first
# List of departments as an array for a select
#org.departments
# A comma separated string for text boxes
#org.department_list
The department_list method can now be used to display the list in a text box and also be used to post and changes back. So you your view code just becomes this:
<%= f.text_area :department_list, :cols => "10", :rows => "10" %>
You will probably need to amend your org controller by changing the create to something like this:
def create
#org = Org.new(params[:org])
respond_to do |format|
if #org.save
#org.department_list = params[:org][:department_list]
format.html { redirect_to org_url,
notice: "#{#org.name} was successfully created" }
format.json { render json: #org,
status: :created, location: #org }
else
format.html { render action: "new" }
format.json { render json: #org.errors, status: :unprocessable_entity }
end
end
end
If you are still stuck I have a complete webiste on github that you can look through. For you it is orgs and departments and on mysite it is people and skills or people and credits. This is the link:
https://github.com/davesexton/CKCASTING
I have a landlord and comment class.
Landlord has 1:N comments.
When a landlord is created it creates a comment on the same form (nested).
When the form is submitted, users_controller#create is called.
I want to check the database if the landlord with the same name, city and state already exists and add the comment to that landlord instead of creating a new one.
def create
#check if a landlord of the same name already exists and add comments to that
if Landlord.find_by_name(params[:name]) && Landlord.find_by_city(params[:city])&& Landlord.find_by_province(params[:province])
#landlord_exists = Landlord.find_by_name(params[:name]) && Landlord.find_by_city(params[:city])&& Landlord.find_by_province(params[:province])
#landlord_exists.comments.build
#landlord_exists.comments[0].setIP request.remote_ip
#landlord_exists.save
else
#landlord = Landlord.new(params[:landlord])
#landlord.comments[0].setIP request.remote_ip
if #landlord.save
flash[:success] = "Thank you for submitting a Landlord"
redirect_to landlords_path
else
end
end
end
update # 1 down to
def create
#landlord = Landlord.where(:name => params[:name], :city => params[:city], :province => params[:province]).first_or_create!
#landlord.comments[0].setIP request.remote_ip
if #landlord.save
redirect_to landlords_path
else
end
end
The line
#landlord = Landlord.where(:name => params[:name], :city => params[:city], :province => params[:province]).first_or_create!
Seems to be returning a nil object thus throwing errors when setIP is called.
What would be causing this? I have tried it in the terminal and it worked fine although I was using hardcoded values.
The simplest way to do it would be to use ActiveRecord::Relation's first_or_create! method. This will perform a query based on the params entered in the form, and create a record if no matches are found, meaning you can get of the if ... else conditionals:
#landlord = Landlord.where(params[:landlord]).first_or_create!
#landlord.comments.build
# etc ...
i have a opponents model, and a team model, i want to be able to create opponents on the fly, and have them assigned to a team id
at present within my model i have, which is creating the opponent but with a null team_id
def opponent_name
opponent.try(:name)
end
def opponent_name=(name)
self.opponent = Opponent.find_or_create_by_name_and_team_id(name,self.team_id) if name.present?
end
and in my view i am calling this method with the following
.row
.columns.large-2
= f.label :opponent_name, :class =>'left inline'
.columns.large-4
= f.text_field :opponent_name, data: {autocomplete_source: Opponent.order(:name).map(&:name)}
Shouldn't it be:
def opponent_name=(name)
self.opponent = Opponent.find_or_create_by_name_and_team_id(name,self.id) if name.present?
end
if this is a function in the Team model?