How to search in has_many association using Sunspot/Solr? - ruby-on-rails

Here is what my initial searchable block looks like in my User model:
searchable do
text :name
integer :sport_ids, multiple: true do
sports.map(&:id)
end
integer :position_ids, multiple: true do
positions.map(&:id)
end
integer :city_id
integer :school_id
string :state
end
How do I search by has_many associations? I need it to return each Athlete who has a specified ID in their sports or sport_positions. So if someone selects "Basketball" from a dropdown, ID of 2 is passed to my search method and it needs to return Athletes who have sport_id of 2 in their collection of sport_ids. Here is how sports and sport_positions are declared in the User model:
has_and_belongs_to_many :positions, :class_name => "SportPosition", :join_table => "user_sport_positions", :uniq => true
has_many :sports, :through => :user_sports, order: "user_sports.created_at", class_name: "Sport"
has_many :user_sports
:::EDIT:::
This worked for a minute after I reindexed, then all of a sudden I started getting this error:
Sunspot::UnrecognizedFieldError (No field configured for Athlete with name 'sport_ids'):
app/models/search.rb:12:in `block in execute'
here is my Search model:
class Search < ActiveRecord::Base
attr_accessible :coach_id, :sport_id, :gpa_min, :gpa_max, :sport_position_id,
:classification, :city_id, :state, :school_id, :athlete_name
belongs_to :coach
def self.execute(params, page = nil)
Sunspot.search(Athlete) do |query|
query.with :public, true
query.with :removed_from_listing, false
query.fulltext params[:athlete_name] unless params[:athlete_name].blank?
query.with :sport_ids, params[:sport_id] unless params[:sport_id].blank?
query.with :position_ids, params[:sport_position_id] unless params[:sport_position_id].blank?
query.with(:state).equal_to(params[:state]) unless params[:state].blank?
query.with(:classification).equal_to(params[:classification]) unless params[:classification].blank?
query.with :city_id, params[:city_id] unless params[:city_id].blank?
query.with :school_id, params[:school_id] unless params[:school_id].blank?
query.with(:current_gpa).between(params[:gpa_min]..params[:gpa_max]) unless params[:gpa_min].eql?("0.0") && params[:gpa_max].eql?("5.0")
query.paginate page: page unless page.blank?
end
end
end
NOTE: To make this even more strange, I have a field called "recruit_year" that is an integer attribute. I was getting the same error on this field saying "No field configured" blah blah. That error usually only happens on text fields if you try to do a comparison like equal_to or treat it like a string.
Help?

This works fine, the problem was STI. I had an Athlete block that was overriding the User block.

Related

Rails, Active admin, nested form, dynamic label value - issue with the form object

I have three models:
class Request < ActiveRecord::Base
belongs_to :scenario
belongs_to :location
has_many :actions, :foreign_key => 'request_id'
accepts_nested_attributes_for :actions, :allow_destroy => true
end
class Action < ActiveRecord::Base
belongs_to :request
belongs_to :scenario_step
end
class ScenarioStep < ActiveRecord::Base
belongs_to :scenario
has_many :actions
end
Using Active Admin I want to update information about action taken in response to a request. To do that I am using nested form:
ActiveAdmin.register Request do
permit_params :scenario_id, :location_id,
actions_attributes: [:scenario_step_id, :description]
form(:html => {:multipart => true}) do |f|
f.inputs "Request details" do
f.input :status
panel 'Steps' do
"Text ..."
end
f.has_many :actions, heading: 'Steps to follow', allow_destroy: false, new_record: true do |ff|
ff.input :description, label: ff.object.scenario_step_id, hint: 'Text'
ff.input :scenario_step_id
end
para "Press cancel to return to the list without saving."
f.actions
end
end
end
Everything seems to be fine except of label (or hint). As a value I want to put there related data from a table scenario_steps.
As you can see I currently try to at least print the value of scenario_step_id that should be available in the object form (ff.object.scenario_step_id) but it is not working (I have such column in actions table). On the other hand, next line: ff.input :scenario_step_id loads appropriate data into input field.
Can somebody give ma a hint what am I doing wrong?
Here is what I was missing (part of formtastic documentation):
Values for labels/hints/actions are can take values: String (explicit
value), Symbol (i18n-lookup-key relative to the current "type", e.g.
actions:), true (force I18n lookup), false (force no I18n lookup).
Titles (legends) can only take: String and Symbol - true/false have no
meaning.
So small change (to_s) in line below makes huge difference :)
ff.input :description, label: ff.object.scenario_step_id.to_s, hint: 'Text'

Ruby on Rails has_and_belongs_to_many uninitialized constant

I'm building a site on Refinery CMS, and have generated two extensions: one for Brands, and another for Bicycle Types (it's a site for a bike shop).
Now, what I want to do is have the Brands extension handle the creation of brand pages, which will be pulled into a brand index. On this page, I want to be able to filter by Bicycle Type, which is where the second extension comes in. Through the Bicycle Type extension, you can create a bicycle type, which I want to associate to a Brand. A Brand can have multiple Bicycle Types, and vice versa.
So, I edited the Brands model to add has_and_belongs_to_many :bicycle_types, and the Bicycle Types model to include has_and_belongs_to_many :brands and accepts_nested_attributes_for :brands. I wrote a migration to create a join table, and everything was going well so far.
I then went to modify the form for the Brands extension, and got my checkboxes displaying correctly and seemingly generating the right code. However, the problem occurs when I come to submit the form - I get NameError in Refinery::Brands::Admin::BrandsController#update and uninitialized constant Refinery::Brands::Brand::BicycleType.
The parameters I get look like the bicycle type IDs are being passed through correctly:
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"3193ZMPXkmHdgZThXwAurD6xF2eZ533Tb71pAi7Jxbs=",
"switch_locale"=>"en",
"brand"=>{"title"=>"Cannondale",
"teaser"=>"",
"splash"=>"",
"details"=>"",
"introduction"=>"",
"blockquote"=>"",
"bicycle_type_ids"=>["1",
"2"],
"logo_id"=>"",
"teaser_image_id"=>"",
"splash_image_id"=>""},
"id"=>"2",
"locale"=>:en}
I've been trying to figure this out and just keep hitting the same brick wall, so any help would be greatly appreciated!
Here's my code. Let me know if anything else would help.
Brands Controller
module Refinery
module Brands
module Admin
class BrandsController < ::Refinery::AdminController
crudify :'refinery/brands/brand',
:xhr_paging => true
end
end
end
end
Brands Model
module Refinery
module Brands
class Brand < Refinery::Core::BaseModel
self.table_name = 'refinery_brands'
attr_accessible :title, :teaser, :splash, :details, :introduction, :blockquote, :logo_id, :teaser_image_id, :splash_image_id, :position, :bicycle_type_ids
translates :title, :teaser, :splash, :details, :introduction, :blockquote
class Translation
attr_accessible :locale
end
validates :title, :presence => true, :uniqueness => true
belongs_to :logo, :class_name => '::Refinery::Image'
belongs_to :teaser_image, :class_name => '::Refinery::Image'
belongs_to :splash_image, :class_name => '::Refinery::Image'
has_and_belongs_to_many :bicycle_types
end
end
end
Bicycle Types Model
module Refinery
module BicycleTypes
class BicycleType < Refinery::Core::BaseModel
self.table_name = 'refinery_bicycle_types'
attr_accessible :title, :position
translates :title
class Translation
attr_accessible :locale
end
validates :title, :presence => true, :uniqueness => true
has_and_belongs_to_many :brands
accepts_nested_attributes_for :brands
end
end
end
Migration
class AddRefineryBicycleTypesBrands < ActiveRecord::Migration
def change
create_table :bicycle_types_brands, :id => false do |t|
t.references :bicycle_type
t.references :brand
end
add_index :bicycle_types_brands, [:bicycle_type_id, :brand_id], :unique => true
end
end
Form Partial (at least the part where I'm building my checkboxes)
<div class="field">
<%= f.label :bicycle_types %>
<% Refinery::BicycleTypes::BicycleType.order(:title).each do |bicycle_type| %>
<label class="checkbox">
<%= check_box_tag "#{f.object_name}[bicycle_type_ids][]", bicycle_type.id, f.object.bicycle_types %>
<%= bicycle_type.title %>
</label>
<% end %>
</div>
If the rest of the partial would be useful, or anything else for that matter, please let me know. Any help would be greatly appreciated!
you must specify the full class name:
has_and_belongs_to_many :bicycle_types, :class_name => "Refinery::BicycleTypes::BicycleType"
This is not your case but if you want to call the join table in a 'refinery style' (i.e refinery_bicycle_types_brands), you must also declare the join table:
has_and_belongs_to_many :bicycle_types, :join_table => :refinery_bicycle_types_brands, :class_name => "Refinery::BicycleTypes::BicycleType"
Bye

missing attribute error Rails

I cant seem to get the result of a scope to display in my view as i get this error message.I am trying to get all of the memberships amounts for the day added up and displayed as a total
missing attribute: membership_id
My Models and scope
class Member < ActiveRecord::Base
belongs_to :membership
accepts_nested_attributes_for :membership
attr_accessible :membership_id, :forename, :middlename, :surname, :house_no, :house_name, :street, :town, :postcode, :home_tel, :mobile_tel, :work_tel, :email, :start_date, :expiry_date
scope :new_memberships_cash_today, ->() {
joins(:membership).where(:start_date => Date.today).select('ROUND(SUM(memberships.cost), 2)')
}
end
class Membership < ActiveRecord::Base
has_many :members, :dependent => :destroy
attr_accessible :membership_type, :cost
end
And then my view
columns do
#Total amount in £ for New Memberships today
column do
panel "Cash Today", :class => 'NewAmountMemberships' do
table_for Member.new_memberships_cash_today do
column 'Total cash' do |c|
c.membership.map { |e| [e.cost, e.id] }
end
end
end
end
end
After some reading it would seem that there may be an issue with my select call in the scope, as i need to specify all of the models attributes to make a successful call with Active Record?
As i am performing a sum within the select i am unsure how to add more attributes, if this is even the case
Any help appreciated
i have run the scope in the the console and this is what is returned
Member Load (0.1ms) SELECT ROUND(SUM(memberships.cost), 2) FROM `members` INNER JOIN `memberships` ON `memberships`.`id` = `members`.`membership_id` WHERE `members`.`start_date` = '2013-12-13'
=> #<ActiveRecord::Relation [#<Member id: nil>]>
I wouldn't do this in a scope, but in a helper method. That way you can grab the associated records and just call your method to return the total.
Something along the lines of this:
def membership_sum(memberships = [])
sum = 0
memberships.each { |membership| sum += membership.cost }
sum.round
end
Now, store the associated records in a #memberships variable (from within your controller) and then, in your view, use <%= membership_sum(#memberships) %>

Ecommerce shipping and billing address into one table in rails

I'm trying to create an address form with shipping and billing address on same page.
When user gets ready for checkout , I want both shipping address form and billing address for to appear on same page. If billing address same as shipping address only record should be inserted into address table , if different two records has to be inserted and of course an update has to take place in orders table shipping_address_id,billing_address_id.
Having only one address model, how do I achieve two forms with one submit button.
Below is my model for address and orders
I need some help in putting in controller also I'm trying to get a hash value for each billing and shipping
Please help!!!
class Address < ActiveRecord::Base
attr_accessible :name,:first_name,:last_name,:address1,:address2,:city,:state,:zip,:phone,:billing_default,: user_id,:billing_address, :shipping_address
belongs_to :user
has_many :billing_addresses, :class_name => "Order", :foreign_key => "billing_address_id"
has_many :shipping_addresses, :class_name => "Order", :foreign_key => "shipping_address_id"
class Order < ActiveRecord::Base
attr_accessible :cart_id, :order_no, :sales_tax, :shipping_fee,:total,:order_state,:gateway_type,:transaction_id,:transaction_status,:ip_address,:card_verification,:card_number,:billing_address_id,:shippin g_address_id,:first_name,:last_name,:user_id,:card_expires_on,:authenticity_token
belongs_to :cart
belongs_to :user
belongs_to :billing_address, :class_name => "Address"
belongs_to :shipping_address, :class_name => "Address"
attr_accessor :card_number
has_many :transactions, :through => :order_id
has_many :invoices
has_many :order_details
This is a slightly complicated problem, you will find.
First, ask yourself: Do you really only want to insert one address if billing and shipping addresses are the same?
A customer wants to change the shipping address. You will need logic to create another address record and retain the original as billing.
Generally, avoid updates to billing and shipping addresses after an order is complete as they compromise data integrity. Once an order is closed, that's it; those addresses should be fixed. When an order requires a different shipping address, avoid having a dependency between it and the billing address.
Now, assuming you're going ahead.
Using Nested Forms
Hide billing fields, and add a check box to your form that maps to an order.bill_to_shipping_address. Default it to checked. Show billing address if it gets unchecked.
$('input[name="order[bill_to_shipping_address]"]').on 'click', ->
if $(this).is ':checked'
$('fieldset.billing_fields').hide()
else
$('fieldset.billing_fields').show()
In your order model:
accepts_nested_attributes_for :shipping_address
accepts_nested_attributes_for :billing_address, reject_if: :bill_to_shipping_address
The draw back with this approach is, if there is a validation error, and the user happens to change his mind and bill to a different address, the billing form will not appear since it gets rejected.
Use a Form Object
This might seem more complex, but it's a much cleaner solution.
See 7 Patterns for refactoring ActiveRecord Objects.
Build a form object as such. I've adopted this code from something I recently wrote for a Rails 4 app. Just reverse your relationships. In my case an order has one billing address and one shipping address; it does not belong to them.
class OrderForm
include ActiveModel::Model
def self.model_name
ActiveModel::Name.new(self, nil, "Order")
end
def persisted?
false
end
attr_accessor :params
delegate :email, :bill_to_shipping_address, to: :order
# Removed most fields for brevity
delegate :name, :street, :street_number, to: :shipping_address, prefix: :shipping
delegate :name, :street, :street_number, to: :billing_address, prefix: :billing
# Removed most fields for brevity
validates :email, length: { maximum: 60 }, email_format: true
validates :shipping_name, :shipping_street, presence: true
validates :billing_name, presence: true, unless: -> { bill_to_shipping_address }
def initialize(params = nil)
#params = params
end
def submit
populate
if valid?
order.save!
true
else
false
end
end
def order
#order ||= Order.new
end
private
def shipping_address
#shipping_address ||= order.build_shipping_address
end
def billing_address
#billing_address ||= order.build_billing_address
end
def populate
order.email = params[:email]
order.bill_to_shipping_address = params[:bill_to_shipping_address]
shipping_address.name = params[:shipping_name]
# etc...
unless order.bill_to_shipping_address?
billing_address.name = params[:billing_name]
# etc...
end
end
end
Then from the controller:
def new
#order_form = OrderForm.new
end
def create
#order_form = OrderForm.new(params[:order])
if #order_form.submit
# order saved, do whatever
else
render 'new'
end
end
Your form now does not care about nested attributes and properties. It's nice a clean.
= form_for #order do |f|
= f.text_field :email
= f.text_field :shipping_street
= f.text_field :billing_street
# etc...
I'd suggest using a checkbox so the user can specify whether use the same billing and shipping address or type different ones.
In the form file you need to handle nested forms in the following way:
= form_for #order do f
= f.fields_for :billing_address do |ba|
= ba.text_field :address1
= ba.text_field:address2
= ba.text_field :city
= ba.text_field :state
= ba.text_field :zip
= ba.text_field :phone
= f.fields_for :shipping_address do |sa|
= sa.text_field :address1
= sa.text_field:address2
= sa.text_field :city
= sa.text_field :state
= sa.text_field :zip
= sa.text_field :phone
In your model don't forget to add:
accepts_nested_attributes_for :shipping_address
accepts_nested_attributes_for :billing_address
And probably need to add the autobuild to your address relations
belongs_to :billing_address, :class_name => "Address", autobuild: true
belongs_to :shipping_address, :class_name => "Address", autobuild: true
In the controller create/update actions, you just need to check the checkbox value and assign them equal, here's one approach:
#order.shipping_address = #order.billing_address if params[:checkbox_use_same_address] == true

How to save embedded classes in mongoid?

I am using Rails 3 with mongoid 2. I have a mongoid class forum, which embeds_many topics.
Topics embeds_many forumposts
When I try to save a forumpost doing the following in my controller...
#forum = Forum.find(params[:forum_id])
#forum.topics.find(params[:topic_id]).forumposts.build(:topic_id => params[:forumpost][:topic_id], :content => params[:forumpost][:content], :user_id => current_user.id,:posted_at => Time.now, :created_at => Time.now, :updated_at => Time.now)
if #forum.save
On save I get...
undefined method `each' for 2012-11-14 23:15:39 UTC:Time
Why am I getting that error?
My forumpost class is as follows...
class Forumpost
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
field :content, type: String
field :topic_id, type: String
field :user_id, type: String
field :posted_at, type: DateTime
attr_accessible :content, :topic_id, :user_id, :posted_at, :created_at, :updated_at
validates :content, presence: true
validates :topic_id, presence: true
validates :user_id, presence: true
belongs_to :topic
belongs_to :user
end
There is alot wrong/wierd with your example code, so lets see if we can start at the start:
You say forum embeds many topics, which embeds many posts. But your model is using a belongs_to association. Belongs_to is used for references which are different than embedded documents. If your Topic model has this:
class Topic
...
embeds_many :forumposts
...
end
Then your Forumpost model should have this:
class Forumpost
...
embedded_in :topic
...
end
Read up on references vs embedded documents here: http://mongoid.org/en/mongoid/docs/relations.html
Next point, You don't need to put :topic_id into the forumpost since you are building it off the topic.
Next point, don't save the forum, save the forumpost. And instead of doing a build followed by a save, try just doing it as a create in one go.
Next point, instead of setting user_id => current_user.id, try setting user => current_user. This is the magic that the belongs_to association provides... its cleaner and avoids messing around with IDs.
Next point, why do you need both created_at (supplied by Mongoid::Timestamps) and posted_at ?
Last point, you shouldn't need to set the timestamps, they should be set automatically when created/updated (unless for some reason you actually need posted_at).
Try something more like this:
#forum = Forum.find(params[:forum_id])
#topic = #forum.topics.find(params[:topic_id])
if #topic.forumposts.create(:content => params[:forumpost][:content], :user => current_user)
#handle the success case
else
#handle the error case
end

Resources