I'm importing into my Rails App via CSV. The import worked until I tried to create a new contact using one of the columns on my CSV. Here's the code that imports the data:
row = Hash[[header, spreadsheet.row(i)].transpose]
hotel = find_by_id(row["id"]) || new
hotel.attributes = row.to_hash.select { |k,v| allowed_attributes.include? k }
hotel_name = row["hotel_title"]
hotel_id = row["id"]
if row.to_hash["contact_name"]
hotel.contact = Contact.create(:hotel_id => row["id"], :name => row["contact_name"])
end
hotel.map = Map.create(:hotel_id => row["id"], :status => 0, :title => "Map Publication for #{hotel_name}")
hotel.app = App.create(:hotel_id => row["id"], :status => 0, :title => "Bespoke App for #{hotel_name}")
hotel.save!
When I run the import, I recieve the following error:
undefined method `contact=' for #
The Contacts belong to the Hotel, and the Hotel has_many Contacts. I have created Contacts using the App, but I'm struggling to get it working using this import method. I'm not sure what I'm doing wrong here, as the Map and App models are both being created (w/ the ID of the Hotel) fine after import. The import now fails when I include the code relating to the contact_name.
class Contact < ActiveRecord::Base
belongs_to :hotel
end
class Hotel < ActiveRecord::Base
has_many :contacts
end
You should use << operator, as it's plural association:
hotel.contacts << Contact.create(name: row['contact_name'])
Note that I removed :hotel_id key from parameters, as it would be redundant (it's already set via association).
You can do it also from the other end of association:
Contact.create(name: row['contact_name'], hotel: hotel)
Just remove hotel.contact =. You don't need it, as you are explicitly providing :hotel_id while creating the contact record.
So, you need to replace
if row.to_hash["contact_name"]
hotel.contact = Contact.create(:hotel_id => row["id"], :name => row["contact_name"])
end
with
Contact.create(:hotel_id => row["id"], :name => row["contact_name"]) if row.to_hash["contact_name"]
Related
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
We are developing a mogration from a small issue tracker software to Redmine. We use the Ruby classes directly to migrate the data. The class for an issue is defined like this:
class BuggyIssue < ActiveRecord::Base
self.table_name = :issues
belongs_to :last_issue_change, :class_name => 'BuggyIssueChange', :foreign_key => 'last_issue_change_id'
has_many :issue_changes, :class_name => 'BuggyIssueChange', :foreign_key => 'issue_id', :order => 'issue_changes.date DESC'
set_inheritance_column :none
# Issue changes: only migrate status changes and comments
has_many :issue_changes, :class_name => "BuggyIssueChange", :foreign_key => :issue_id
def attachments
#BuggyMigrate::BuggyAttachment.all(:conditions => ["type = 'issue' AND id = ?", self.id.to_s])
end
def issue_type
read_attribute(:type)
end
def summary
read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
end
def description
read_attribute(:description).blank? ? summary : read_attribute(:description)
end
def time; Time.at(read_attribute(:time)) end
def changetime; Time.at(read_attribute(:changetime)) end
end
Creating an issue and defining custom fields for the issue works. However, populating the custom fields doesn't seem to work. There are 4 custom fields (Contact, Test status, Source and Resolution).
The custom fields are created like this:
repf = IssueCustomField.find_by_name("Contact")
repf ||= IssueCustomField.create(:name => "Contact", :field_format => 'string') if repf.nil?
repf.trackers = Tracker.find(:all)
repf.projects << product_map.values
repf.save!
The values for these fields are passed like this:
i = Issue.new :project => product_map[first_change.product_id],
...
:custom_field_values => {:Contact => issue.contact, 'Test status' => '', :Source => '', :Resolution => ''}
I've also tried a version with an index as hash key:
:custom_field_values => {'1' => issue.contact, 'Test status' => '', :Source => '', :Resolution => ''}
The issue can be saved without an issue, however, no value is ever passed over to Redmine. A
mysql> select count(*) from custom_values where value is not null;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.01 sec)
shows that all values for the custom fields are NULL after the migration. I don't seem to be able to find how this is done correctly, the documentation for the Redmine classes is very sparse.
I spent much time to solve near same issue. Take a look on my code written to transfer data from old system to new via Redmine REST API. Cause I used ActiveResource code will be usable for you.
def update_custom_fields(issue, fields)
f_id = Hash.new { |hash, key| hash[key] = nil }
issue.available_custom_fields.each_with_index.map { |f,indx| f_id[f.name] = f.id }
field_list = []
fields.each do |name, value|
field_id = f_id[name].to_s
field_list << Hash[field_id, value]
end
issue.custom_field_values = field_list.reduce({},:merge)
raise issue.errors.full_messages.join(', ') unless issue.save
end
Now you can just call update_custom_fields(Issue.last, "MyField" => "MyValue" .. and so on)
So I am trying to implement multiple autocomplete using this gem and simple_form and am getting an error.
I tried this:
<%= f.input_field :neighborhood_id, collection: Neighborhood.order(:name), :url => autocomplete_neighborhood_name_searches_path, :as => :autocomplete, 'data-delimiter' => ',', :multiple => true, :class => "span8" %>
This is the error I get:
undefined method `to_i' for ["Alley Park, Madison"]:Array
In my params, it is sending this in neighborhood_id:
"search"=>{"neighborhood_id"=>["Alley Park, Madison"],
So it isn't even using the IDs for those values.
Does anyone have any ideas?
Edit 1:
In response to #jvnill's question, I am not explicitly doing anything with params[:search] in the controller. A search creates a new record, and is searching listings.
In my Searches Controller, create action, I am simply doing this:
#search = Search.create!(params[:search])
Then my search.rb (i.e. search model) has this:
def listings
#listings ||= find_listings
end
private
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline)
listings = listings.includes(:neighborhood).where("listings.headline like ? or neighborhoods.name like ?", key, key) if keywords.present?
listings = listings.where(neighborhood_id: neighborhood_id) if neighborhood_id.present?
#truncated for brevity
listings
end
First of all, this would be easier if the form is returning the ids instead of the name of the neighborhood. I haven't used the gem yet so I'm not familiar how it works. Reading on the readme says that it will return ids but i don't know why you're only getting names. I'm sure once you figure out how to return the ids, you'll be able to change the code below to suit that.
You need to create a join table between a neighborhood and a search. Let's call that search_neighborhoods.
rails g model search_neighborhood neighborhood_id:integer search_id:integer
# dont forget to add indexes in the migration
After that, you'd want to setup your models.
# search.rb
has_many :search_neighborhoods
has_many :neighborhoods, through: :search_neighborhoods
# search_neighborhood.rb
belongs_to :search
belongs_to :neighborhood
# neighborhood.rb
has_many :search_neighborhoods
has_many :searches, through: :search_neighborhoods
Now that we've setup the associations, we need to setup the setters and the attributes
# search.rb
attr_accessible :neighborhood_names
# this will return a list of neighborhood names which is usefull with prepopulating
def neighborhood_names
neighborhoods.map(&:name).join(',')
end
# we will use this to find the ids of the neighborhoods given their names
# this will be called when you call create!
def neighborhood_names=(names)
names.split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
# view
# you need to change your autocomplete to use the getter method
<%= f.input :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, input_html: { data: { delimiter: ',', multiple: true, class: "span8" } %>
last but not the least is to update find_listings
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline).includes(:neighborhood)
if keywords.present?
listings = listings.where("listings.headline LIKE :key OR neighborhoods.name LIKE :key", { key: "#{keywords}")
end
if neighborhoods.exists?
listings = listings.where(neighborhood_id: neighborhood_ids)
end
listings
end
And that's it :)
UPDATE: using f.input_field
# view
<%= f.input_field :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, data: { delimiter: ',' }, multiple: true, class: "span8" %>
# model
# we need to put [0] because it returns an array with a single element containing
# the string of comma separated neighborhoods
def neighborhood_names=(names)
names[0].split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
Your problem is how you're collecting values from the neighborhood Model
Neighborhood.order(:name)
will return an array of names, you need to also collect the id, but just display the names
use collect and pass a block, I beleive this might owrk for you
Neighborhood.collect {|n| [n.name, n.id]}
Declare a scope on the Neighborhood class to order it by name if you like to get theat functionality back, as that behavior also belongs in the model anyhow.
edit>
To add a scope/class method to neighborhood model, you'd typically do soemthing like this
scope :desc, where("name DESC")
Than you can write something like:
Neighborhood.desc.all
which will return an array, thus allowing the .collect but there are other way to get those name and id attributes recognized by the select option.
I might be doing this wrong overall, but maybe someone will be able to chirp in and help out.
Problem:
I want to be able to build a relationship on an unsaved Object, such that this would work:
v = Video.new
v.title = "New Video"
v.actors.build(:name => "Jonny Depp")
v.save!
To add to this, these will be generated through a custom method, which I'm attempting to modify to work, that does the following:
v = Video.new
v.title = "Interesting cast..."
v.actors_list = "Jonny Depp, Clint Eastwood, Rick Moranis"
v.save
This method looks like this in video.rb
def actors_list=value
#Clear for existing videos
self.actors.clear
value.split(',').each do |actorname|
if existing = Actor.find_by_name(actorname.strip)
self.actors << existing
else
self.actors.build(:name => actorname.strip)
end
end
end
What I expect
v.actors.map(&:name)
=> ["Jonny Depp", "Clint Eastwood", "Rick Moranis"]
Unfortunatey, these tactics neither create an Actor nor the association. Oh yeah, you might ask me for that:
in video.rb
has_many :actor_on_videos
has_many :actors, :through => :actor_on_videos
accepts_nested_attributes_for :actors
I've also tried modifying the actors_list= method as such:
def actors_list=value
#Clear for existing videos
self.actors.clear
value.split(',').each do |actorname|
if existing = Actor.find_by_name(actorname.strip)
self.actors << existing
else
self.actors << Actor.create!(:name => actorname.strip)
end
end
end
And it creates the Actor, but I'd rather not create the Actor if the Video fails on saving.
So am I approaching this wrong? Or have I missed something obvious?
Try this:
class Video < ActiveRecord::Base
has_many :actor_on_videos
has_many :actors, :through => :actor_on_videos
attr_accessor :actors_list
after_save :save_actors
def actors_list=names
(names.presence || "").split(",").uniq.map(&:strip).tap do |new_list|
#actors_list = new_list if actors_list_changes?(new_list)
end
end
def actors_list_changes?(new_list)
new_record? or
(actors.count(:conditions => {:name => new_list}) != new_list.size)
end
# save the actors in after save.
def save_actors
return true if actors_list.blank?
# create the required names
self.actors = actors_list.map {|name| Actor.find_or_create_by_name(name)}
true
end
end
You cannot assign a child to an object with no id. You will need to store the actors in your new video object and save those records after the video has been saved and has an id.
I use Rails 3.0.6 with mongoID 2.0.2. Recently I encountered an issue with save! method when overriding setter (I am trying to create my own nested attributes).
So here is the model:
class FeedItem
include Mongoid::Document
has_many :audio_refs
def audio_refs=(attributes_array, binding)
attributes_array.each do |attributes|
if attributes[:audio_track][:id]
self.audio_refs.build(:audio_track => AudioTrack.find(attributes[:audio_track][:id]))
elsif attributes[:audio_track][:file]
self.audio_refs.build(:audio_track => AudioTrack.new(:user_id => attributes[:audio_track][:user_id], :file => attributes[:audio_track][:file]))
end
end
if !binding
self.save!
end
end
AudioRef model (which is just buffer between audio_tracks and feed_items) is:
class AudioRef
include Mongoid::Document
belongs_to :feed_item
belongs_to :audio_track
end
And AudioTrack:
class AudioTrack
include Mongoid::Document
has_many :audio_refs
mount_uploader :file, AudioUploader
end
So here is the spec for the FeedItem model which doesn`t work:
it "Should create audio_track and add audio_ref" do
#audio_track = Fabricate(:audio_track, :user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3"))
#feed_item= FeedItem.new(
:user => #author,
:message => {:body => Faker::Lorem.sentence(4)},
:audio_refs => [
{:audio_track => {:id => #audio_track.id}},
{:audio_track => {:user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3")}}
]
)
#feed_item.save!
#feed_item.reload
#feed_item.audio_refs.length.should be(2)
end
As you can see, the reason I am overriding audio_refs= method is that FeedItem can be created from existing AudioTracks (when there is params[:audio_track][:id]) or from uploaded file (params[:audio_track][:file]).
The problem is that #feed_item.audio_refs.length == 0 when I run this spec, i.e. audio_refs are not saved. Could you please help me with that?
Some investigation:
1) binding param is "true" by default (this means we are in building mode)
I found a solution to my problem but I didnt understand why save method doesnt work and didn`t make my code work. So first of all let me describe my investigations about the problem. After audio_refs= is called an array of audio_refs is created BUT in any audio_ref is no feed_item_id. Probably it is because the feed_item is not saved by the moment.
So the solution is quite simple - Virtual Attributes. To understand them watch corresponding railscasts
So my solution is to create audio_refs by means of callback "after_save"
I slightly changed my models:
In FeedItem.rb I added
attr_writer :audio_tracks #feed_item operates with audio_tracks array
after_save :assign_audio #method to be called on callback
def assign_audio
if #audio_tracks
#audio_tracks.each do |attributes|
if attributes[:id]
self.audio_refs << AudioRef.new(:audio_track => AudioTrack.find(attributes[:id]))
elsif attributes[:file]
self.audio_refs << AudioRef.new(:audio_track => AudioTrack.new(:user_id => attributes[:user_id], :file => attributes[:file]))
end
end
end
end
And the spec is now:
it "Should create audio_track and add audio_ref" do
#audio_track = Fabricate(:audio_track, :user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3"))
#feed_item= FeedItem.new(
:user => #author,
:message => {:body => Faker::Lorem.sentence(4)},
:audio_tracks => [
{:id => #audio_track.id},
{:user_id => #author.id, :file => File.open("#{Rails.root}/spec/stuff/test.mp3")}
]
)
#feed_item.save!
#feed_item.reload
#feed_item.audio_refs.length.should be(2)
end
And it works fine!!! Good luck with your coding)
Check that audio_refs=() is actually being called, by adding debug output of some kind. My feeling is that your FeedItem.new() call doesn't use the audio_refs=() setter.
Here's the source code of the ActiveRecord::Base#initialize method, taken from APIdock:
# File activerecord/lib/active_record/base.rb, line 1396
def initialize(attributes = nil)
#attributes = attributes_from_column_definition
#attributes_cache = {}
#new_record = true
#readonly = false
#destroyed = false
#marked_for_destruction = false
#previously_changed = {}
#changed_attributes = {}
ensure_proper_type
populate_with_current_scope_attributes
self.attributes = attributes unless attributes.nil?
result = yield self if block_given?
_run_initialize_callbacks
result
end
I don't currently have an environment to test this, but it looks like it's setting the attributes hash directly without going through each attribute's setter. If that's the case, you'll need to call your setter manually.
Actually, I think the fact you're not getting an exception for the number of arguments (binding not set) proves that your setter isn't being called.