Something wrong in my sequence of factory creation - ruby-on-rails

I was hoping someone would spot why this wouldn't work.
I am getting an error thats being called because the attributes I specify with Factory_Girl are not being applied to the stub before validation.
The Error:
undefined method `downcase' for #<Category:0x1056f2f60>
RSpec2
it "should vote up" do
#mock_vote = Factory.create(:vote)
Vote.stub(:get_vote).and_return(#mock_vote)
get :vote_up, :id => "1"
end
Factories
Factory.define :vote, :class => Vote do |v|
v.user_id "1"
v.association :post
end
Factory.define :post, :class => Post do |p|
p.category "spirituality"
p.name "sleezy snail potluck"
p.association :category
end
Factory.define :category, :class => Category do |c|
c.name "spirituality"
c.id "37"
end
Post.rb - Model
before_save :prepare_posts
validate :category?
def prepare_posts
self.update_attribute("category", self.category.downcase)
if self.url?
self.url = "http://" + self.url unless self.url.match /^(https?|ftp):\/\//
end
end
def category?
unless Category.exists?(:name => self.category.downcase)
errors.add(:category, "There's no categories with that name.")
end
return true
end
Also, feel free to nitpick any blatantly gross looking code. :D
Thanks!!

You have a category attribute, which appears to be a string, but you also seem to have a category association which automatically creates, among other things, an attribute on Post called category, probably overwriting your category attribute. Hence, the Category class has no downcase method, because it's not a String.
Rename your category attribute to something like category_name, but really you shouldn't have that attribute at all.
Maybe where you're calling self.category.downcase you meant self.category.name.downcase?

Related

mutliple select dropdown company and save perk and respective company

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

Getting rails3-autocomplete-jquery gem to work nicely with Simple_Form with multiple inputs

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.

Is there a better way to write this named_scope? [Rails]

I am using this named_scope to search for products that have a description matching any word the user inputs.
E.g., Product.description_like_any("choc pret")
Will return products with names like
"Chocolate Bar"
"Chocolate Covered Pretzels"
"Miniature Chocolate Ponies"
Here's the named_scope I've written (which works)
named_scope :description_like_any, (lambda do |query|
return {} unless query
conditions = []
values = []
for q in query.split(/\s+/)
conditions << "(`products`.description LIKE ?)"
values << "%#{q}%"
end
{ :conditions => [conditions.join(' AND '), *values] }
end)
Is there a better way to write this? Perhaps I'm missing a Rubyism/Railism or two?
Solution
Using scope_procedure in conjunction with Searchlogic, this can be done in an even easier way. Note, the solution before even leverages Searchlogic's _or_ syntax for connecting two scopes together. The :keywords scope_procedure finds products matching product.description, or product.vendor.name; All with one text field!
Model
# app/models/product.rb
class Product < ActiveRecord::Base
scope_procedure :keywords, lambda |query|
description_like_any_or_vendor_name_like_any(query.split(/\s+/))
end
end
Controller
# app/controllers/products_controller.rb
class ProductsController < ApplicationController
def index
#search = Product.search(params[:search])
#products = #search.all
end
end
Views
# app/views/products/index.html.erb
<% form_for #search do |f| %>
<%= f.label :keywords, "Quick Search" %>
<%= f.input :keywords %>
<%= f.submit, "Go" %>
<% end %>
The most Railsy thing to do is to not write this yourself. :-) Use the excellent Searchlogic gem which will create the description_like_any scope for you.
Edit: If you want your user to be able to enter search terms in a free text field like this, you can define your own scope:
class Product < ActiveRecord::Base
# ...
scope_procedure :description_like_any_term, lambda { |terms|
name_like_any(terms.split(/\s+/))
}
# ...
end

How can I build/create a many-to-many association in factory_girl?

I have a Person model that has a many-to-many relationship with an Email model and I want to create a factory that lets me generate a first and last name for the person (this is already done) and create an email address that is based off of that person's name. Here is what I have for create a person's name:
Factory.sequence :first_name do |n|
first_name = %w[FirstName1 FirstName2] # ... etc (I'm using a real subset of first names)
first_name[(rand * first_name.length)]
end
Factory.sequence :last_name do |n|
last_name = %w[LastName1 LastName2] # ... etc (I'm using a real subset of last names)
last_name[(rand * last_name.length)]
end
Factory.define :person do |p|
#p.id ???
p.first_name { Factory.next(:first_name) }
p.last_name { Factory.next(:last_name) }
#ok here is where I'm stuck
#p.email_addresses {|p| Factory(:email_address_person_link) }
end
Factory.define :email_address_person_link do |eapl|
# how can I link this with :person and :email_address ?
# eapl.person_id ???
# eapl.email_address_id ???
end
Factory.define :email_address do |e|
#how can I pass p.first_name and p.last_name into here?
#e.id ???
e.email first_name + "." + last_name + "#test.com"
end
Ok, I think I understand what you're asking now. Something like this should work (untested, but I've done something similar in another project):
Factory.define :person do |f|
f.first_name 'John'
f.last_name 'Doe'
end
Factory.define :email do |f|
end
# This is optional for isolating association testing; if you want this
# everywhere, add the +after_build+ block to the :person factory definition
Factory.define :person_with_email, :parent => :person do |f|
f.after_build do |p|
p.emails << Factory(:email, :email => "#{p.first_name}.#{p.last_name}#gmail.com")
# OR
# Factory(:email, :person => p, :email => "#{p.first_name}.#{p.last_name}#gmail.com")
end
end
As noted, using a third, separate factory is optional. In my case I didn't always want to generate the association for every test, so I made a separate factory that I only used in a few specific tests.
Use a callback (see FG docs for more info). Callbacks get passed the current model being built.
Factory.define :person do |p|
p.first_name { Factory.next(:first_name) }
p.last_name { Factory.next(:last_name) }
p.after_build { |m| p.email_addresses << "#{m.first_name}.#{m.last_name}#test.com" }
end
I think that works.
You could also save yourself some work by looking into using the Faker gem which creates realistic first and last names and e-mail addresses for you.

ruby object array... or hash

I have an object now:
class Items
attr_accessor :item_id, :name, :description, :rating
def initialize(options = {})
options.each {
|k,v|
self.send( "#{k.to_s}=".intern, v)
}
end
end
I have it being assigned as individual objects into an array...
#result = []
some loop>>
#result << Items.new(options[:name] => 'name', options[:description] => 'blah')
end loop>>
But instead of assigning my singular object to an array... how could I make the object itself a collection?
Basically want to have the object in such a way so that I can define methods such as
def self.names
#items.each do |item|
item.name
end
end
I hope that makes sense, possibly I am overlooking some grand scheme that would make my life infinitely easier in 2 lines.
A few observations before I post an example of how to rework that.
Giving a class a plural name can lead to a lot of semantic issues when declaring new objects, as in this case you'd call Items.new, implying you're creating several items when in fact actually making one. Use the singular form for individual entities.
Be careful when calling arbitrary methods, as you'll throw an exception on any misses. Either check you can call them first, or rescue from the inevitable disaster where applicable.
One way to approach your problem is to make a custom collection class specifically for Item objects where it can give you the information you need on names and such. For example:
class Item
attr_accessor :item_id, :name, :description, :rating
def initialize(options = { })
options.each do |k,v|
method = :"#{k}="
# Check that the method call is valid before making it
if (respond_to?(method))
self.send(method, v)
else
# If not, produce a meaningful error
raise "Unknown attribute #{k}"
end
end
end
end
class ItemsCollection < Array
# This collection does everything an Array does, plus
# you can add utility methods like names.
def names
collect do |i|
i.name
end
end
end
# Example
# Create a custom collection
items = ItemsCollection.new
# Build a few basic examples
[
{
:item_id => 1,
:name => 'Fastball',
:description => 'Faster than a slowball',
:rating => 2
},
{
:item_id => 2,
:name => 'Jack of Nines',
:description => 'Hypothetical playing card',
:rating => 3
},
{
:item_id => 3,
:name => 'Ruby Book',
:description => 'A book made entirely of precious gems',
:rating => 1
}
].each do |example|
items << Item.new(example)
end
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
Do you know the Ruby key word yield?
I'm not quite sure what exactly you want to do. I have two interpretations of your intentions, so I give an example that makes two completely different things, one of them hopefully answering your question:
class Items
#items = []
class << self
attr_accessor :items
end
attr_accessor :name, :description
def self.each(&args)
#items.each(&args)
end
def initialize(name, description)
#name, #description = name, description
Items.items << self
end
def each(&block)
yield name
yield description
end
end
a = Items.new('mug', 'a big cup')
b = Items.new('cup', 'a small mug')
Items.each {|x| puts x.name}
puts
a.each {|x| puts x}
This outputs
mug
cup
mug
a big cup
Did you ask for something like Items.each or a.each or for something completely different?
Answering just the additional question you asked in your comment to tadman's solution: If you replace in tadman's code the definition of the method names in the class ItemsCollection by
def method_missing(symbol_s, *arguments)
symbol, s = symbol_s.to_s[0..-2], symbol_s.to_s[-1..-1]
if s == 's' and arguments.empty?
select do |i|
i.respond_to?(symbol) && i.instance_variables.include?("##{symbol}")
end.map {|i| i.send(symbol)}
else
super
end
end
For his example data you will get following outputs:
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
puts items.descriptions.join(', ')
# => Faster than a slowball, Hypothetical playing card, A book made entirely of precious gems
As I don't know about any way to check if a method name comes from an attribute or from another method (except you redefine attr_accessor, attr, etc in the class Module) I added some sanity checks: I test if the corresponding method and an instance variable of this name exist. As the class ItemsCollection does not enforce that only objects of class Item are added, I select only the elements fulfilling both checks. You can also remove the select and put the test into the map and return nil if the checks fail.
The key is the return value. If not 'return' statement is given, the result of the last statement is returned. You last statement returns a Hash.
Add 'return self' as the last line of initialize and you're golden.
Class Item
def initialize(options = {})
## Do all kinds of stuff.
return self
end
end

Resources