Geocoder with One to Many realtionship Ruby on Rails - ruby-on-rails

I have two models in my Rails app which form a one_to_many relationship. The first model, store, represents a brick and mortar store and has latitude and longitude columns which are being geocoded correctly using the 'geocoder' gem and an address column. The store has_many products. The product has_one store. I would like to return the index of products based on proximity to an address which the user inputs in the search form. Here are the relevant parts of my code as well as my attempt at implementing the search:
in schema.rb:
create_table "products", force: true do |t|
t.string "title"
...
t.integer "store_id"
end
create_table "stores", force: true do |t|
...
t.string "business_address"
t.float "latitude"
t.float "longitude"
end
in store.rb
class Store < ActiveRecord::Base
has_many :products
geocoded_by :business_address
after_validation :geocode, :if => :business_address_changed?
end
in product.rb
class Offer < ActiveRecord::Base
belongs_to :store
end
in views/products/search.html.erb
...
<%= form_tag products_path, :method => 'get' do %>
<p>
Find products near<br />
<%= text_field_tag :custom_address, params[:custom_address] %><br />
</p>
<p>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
in products_controller.rb
def index
#products = Store.near(params[:custom_address], 100, order: :distance, :select => "products.*")
end
The above index method generates a
ActiveRecord::StatementInvalid in Products#index
error
I am not sure how to continue. Obviously there is a problem with the way I am using the near method and :select but I can't wrap my head around it. How can I return the products sorted by distance?
I am using MySQL as the database adapter; I have heard of issues due to a lack of trig functions with SQLite.

I got my code working properly by using the following:
I added an attr_accessor in the Product model:
class Product < ActiveRecord::Base
belongs_to :store
attr_accessor :distance_to_user
end
And I changed the index method:
def index
#products = []
if params[:custom_address]
geocoded_stores = (Stores.near(params[:custom_address], 100,))
geocoded_stores.each do |s|
s.products.each do |product|
product.distance_to_user = s.distance
#products << product
end
end
else
#products = Product.all
end
end

Related

Rails has_many :through association: Updating all 3 models at the same time

This question follows up on Rails has_many :through association: save instance into join table and I am restating things here for more clarity.
In our Rails app, there are 3 models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
And here are the corresponding migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
Here is what we are trying to accomplish:
When a logged in user (current_user) creates a calendar, we should:
Create a new #calendar and save it to the Calendar table
Assign the "Creator" role to the user (current_user) for this newly created calendar through the Role column in the Administration table
Increment the total_calendar_count and the owner_calendar_count columns of the User table
In order to do that, we think we need to work on calendars#create.
In the CalendarsController, we already have the following code:
def create
#calendar = current_user.calendars.create(calendar_params)
if #calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
And we collect data from users through the following _calendar_form.html.erb form:
<%= form_for(#calendar) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Your new calendar name" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
We are considering updating the controller as follows:
def create
#calendar = current_user.calendars.create(calendar_params)
#current_user.total_calendar_count += 1
#current_user.owned_calendar_count += 1
current_user.administrations << #calendar.id
#calendar.administration.role = 'Creator'
if #calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
ActiveRecord::AssociationTypeMismatch in CalendarsController#create
Administration(#70307724710480) expected, got Fixnum(#70307679752800)
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
app/controllers/calendars_controller.rb:7:in `create'
How can we make it work?
This line is actually causing the error: current_user.administrations << #calendar.id.
current.administrations expects an object of type Administration while you are passing a Fixnum into it.
You can accomplish the same functionality in the following way:
current_user.administrations.create(calendar_id: #calendar.id)
Edit:
As OP asked in comments that it is a good practice or not. See, there is rule that says that controllers should be skinny, and models should be fatty. Well, it means you should try to write minimum code, and all the logic and fetching of objects should be there in models. But that isn't the case in your code scenario. You should move your code into model, and then call that into your controller.
Here's how:
class User < ActiveRecord::Base
def add_calendar_and_role(calendar_id, role)
self.administrations.find_by(calendar_id: calendar_id).update(role: role)
end
end
This way, your code reduces to just:
current_user.add_calendar_and_role(#calendar.id, 'Creator')
And on the same way, you can further refactor your controller code.

Ruby on Rails - Associating a service with a shop. How to integrate this relationship in the 'create' method

I'm creating an app where I have a 'Shop' model, which is associated with a 'Service' model (For example, a shop might offer different services which can be added by the shop owner only - such as an oil change, new tires, window tinting etc.)
I believe I have the relationship sorted:
class Service < ActiveRecord::Base
belongs_to :shop
end
and
class Shop < ActiveRecord::Base
belongs_to :user
has_many :services
end
However I'm not sure how to add this association in the 'Service' create method so that 'service' belongs only to 'shop'
I'd also like to display all related services on the Shop 'show' page.
I'm in the 'Show' view of Shop, and I want to add a service, how can I pass the shop_id into the service and what files would I have to change?
The create method is just the bog standard scaffold one:
def create
#service = Service.new(service_params)
#service.save
respond_with(#service)
end
And the database looks like this:
Shops:
class CreateShops < ActiveRecord::Migration
def change
create_table :shops do |t|
t.string :name
t.text :description
t.string :phone
t.string :email
t.string :website
t.integer :user_id
t.timestamps
end
end
end
and Services:
class CreateServices < ActiveRecord::Migration
def change
create_table :services do |t|
t.string :category
t.integer :shop_id
t.timestamps
end
end
end
Any help would be very appreciated. I guess i'm just not getting how to pass the relationship information correctly.
Cheers!
First you will need a set of nested routes for shops & services:
# config/routes.rb
resources :shops do
resources :services
end
This creates routes for services nested under shop:
Prefix Verb URI Pattern Controller#Action
shop_services GET /shops/:shop_id/services(.:format) services#index
POST /shops/:shop_id/services(.:format) services#create
new_shop_service GET /shops/:shop_id/services/new(.:format) services#new
edit_shop_service GET /shops/:shop_id/services/:id/edit(.:format) services#edit
shop_service GET /shops/:shop_id/services/:id(.:format) services#show
PATCH /shops/:shop_id/services/:id(.:format) services#update
PUT /shops/:shop_id/services/:id(.:format) services#update
DELETE /shops/:shop_id/services/:id(.:format) services#destroy
shops GET /shops(.:format) shops#index
POST /shops(.:format) shops#create
new_shop GET /shops/new(.:format) shops#new
edit_shop GET /shops/:id/edit(.:format) shops#edit
shop GET /shops/:id(.:format) shops#show
PATCH /shops/:id(.:format) shops#update
PUT /shops/:id(.:format) shops#update
DELETE /shops/:id(.:format) shops#destroy
If look at the parameters for the services routes you can see that there is a :shop_id parameter.
We can use this in our ServicesController to set the shop before each action:
class ServicesController < ApplicationController
before_action :set_shop
# GET /shops/:shop_id/services/new
def new
#service = #shop.services.new
end
# POST /shops/:shop_id/services
def create
#service = #shop.services.new(service_params)
# ... #todo validate and save
end
private
def service_params
params.require(:service).permit(:category)
end
def set_shop
#shop = Shop.find(params[:shop_id])
end
end
In your views you need to change your forms somewhat so that form_for uses the correct path.
<!-- views/services/new.html.erb -->
<h1>Add a service to <%= #service.shop.name %></h1>
<%= form_for([#service.shop, #service]) do |f| %>
<div>
<%= f.label :category %>
<%= f.text_field :category %>
</div>
<%= f.submit %>
<% end %>

Sudden no method error in Active Admin?

I have activeadmin installed and working fine for a 'reviews' section of may app, allowing me to add individual reviews to various locations in which my business is based. I tried to add the identical set up but using a BusinessReviews model rather than Reviews (thus allowing me to add business reviews on the same basis)
Everything works fine until I go into active admin (log in and accessing the 'Business Reviews' panel is fine, until I try and actually add a business review. I then get the error:
NoMethodError in Admin::BusinessReviews#new
undefined method `business_profile_image' for #<BusinessReview:0x007f893fe853d0>
My model is as follows:
class BusinessReview < ActiveRecord::Base
belongs_to :location
has_many :images, :as => :referrer
accepts_nested_attributes_for :images, :allow_destroy => true
def thumbnail
if self.images.count > 0
self.images.last.avatar(:thumb)
else
nil
end
end
end
my_app/admin/business_review.rb is as follows:
ActiveAdmin.register BusinessReview do
index do
column :business_reviewer_name
column :business_review_content
column :business_reviewer_address
column :rating
column :location do |business_review|
business_review.location.name
end
column :business_profile_image do |business_review|
image_tag(business_review.business_profile_image) if business_review.business_profile_image.present?
end
actions
end
show do |business_review|
attributes_table do
row :business_reviewer_name
row :business_profile_image
row :business_review_content
row :business_reviewer_address
row :rating
row :location do
business_review.location.name
end
end
panel "Images" do
table_for business_review.images do
column {|img| img.currently_used }
column {|img| image_tag(img.avatar.url(:large)) }
end
end
active_admin_comments
end
permit_params [:id, :business_reviewer_name, :business_profile_image, :business_review_content, :business_reviewer_address, :rating, :location_id], images_attributes: [:id,:_destroy,:avatar,:usage_type, :currently_used]
form do |f|
f.inputs 'Details' do
f.input :business_reviewer_name
f.input :business_profile_image
f.input :business_review_content
f.input :business_reviewer_address
f.input :rating
f.input :location
end
f.inputs "images" do
f.has_many :images, :allow_destroy => true, :heading => 'Images', :new_record => true do |imgf|
imgf.input :currently_used
imgf.inputs "Attachment", :multipart => true do
imgf.input :avatar, :as => :file, :hint => imgf.object.avatar? \
? imgf.template.image_tag(imgf.object.avatar.url(:large))
: imgf.template.content_tag(:span, "no image yet")
end
end
end
f.actions
end
end
Relevant part of my schema:
create_table "business_reviews", force: true do |t|
t.text "business_reviewer_content"
t.string "business_reviewer_name"
t.string "business_reviewer_address"
t.float "rating"
t.string "profile_image"
t.datetime "created_at"
t.datetime "updated_at"
end
The routes appear to be there ok too?
batch_action_admin_business_reviews POST /admin/business_reviews/batch_action(.:format) admin/business_reviews#batch
_action
admin_business_reviews GET /admin/business_reviews(.:format) admin/business_reviews#index
POST /admin/business_reviews(.:format) admin/business_reviews#creat
e
new_admin_business_review GET /admin/business_reviews/new(.:format) admin/business_reviews#new
edit_admin_business_review GET /admin/business_reviews/:id/edit(.:format) admin/business_reviews#edit
admin_business_review GET /admin/business_reviews/:id(.:format) admin/business_reviews#show
PATCH /admin/business_reviews/:id(.:format) admin/business_reviews#updat
e
PUT /admin/business_reviews/:id(.:format) admin/business_reviews#updat
e
DELETE /admin/business_reviews/:id(.:format) admin/business_reviews#destr
oy
I just don't get it as the reviews one I set up works perfectly and is identical (apart from the not haveing business_ appended to it).
According to your schema there is no business_profile_image but just profile_image:
t.string "profile_image"
So either rename the column or use profile_image instead of business_profile_image.

NameError in Model - search form

I am working on a search with Rails 4 - learning from a Rails cast (which is about version prior 3, apparently).
However, I get the following error:
NameError in GamesController#index undefined local variable or method
`search' for #
This is my code
view:
<%= form_tag games_path, :method => 'get', :id => "games_search" do %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
<% end %>
controller:
def search
#games = Game.search(params[:search])
end
model:
self.search(search)
if search
where('title LIKE ?', "%#{search}")
else
scoped
end
end
migration:
def change
create_table :games do |t|
t.string :title
t.date :releaseDate
t.text :description
t.integer :rating
t.timestamps
end
end
routes:
resources :games
What am I doing wrong?
Class Method
I remember using that Railscast to help me create a search facility
What you're looking at is what's known as a class method - a method which initiates a class, and creates a response for you.
Here's how a class method should work in Rails (also consider each "model" is a class):
#app/models/game.rb
Class Game < ActiveRecord::Base
def self.search(query)
... logic here
end
end
I believe the problem you have is that you haven't declared def for your method, although I don't know for sure as you haven't posted your entire model code
The model missed 'def'
**def** self.search(search)
if search
where('title LIKE ?', "%#{search}")
else
scoped
end
end
I should be awarded the n00b badge. Sorry!

Accessing has_many model records

I have the following 2 tables defined in migrations
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.string :phone
t.string :email
t.string :address
t.string :resume
t.timestamps
end
end
end
Class CreateResumeSections < ActiveRecordMigration
def self.up
create_table :resume_sections do |t|
t.string :section_name
t.string :html
t.timestamps
end
end
end
I have following 2 models
class User
has_many :resume_sections, :dependent => :destroy
attr_accessor :section_layout
after_save :save_sections
private
def save_sections
self.section_layout = ###Someother logic here that sets this variable
end
end
class ResumeSection
belongs_to :user
end
In my users_controller, I have the following code
class UserController < ApplicationController
def create
#user = User.new(params[:user])
#user.save
#user.section_layout.each {|key,value|
rs = ResumeSection.new(:section_name => key, :html => value, :user => #user)
rs.save
}
end
end
In my view I have the following code
<% #user.resume_sections.each do |section| %>
<%= section.section_name %>
<%= section.html %>
<% end %>
I get Undefined method error for Nil:NilClass in the view. The expression #user.resume_sections is not returning to me the records that I just created and saved in the UsersController. Instead it returns nil to me. When I check the database the records are there.
Is the expression #user.resume_sections the correct expression to access these records?
Thanks
Paul
It seems to me that your you missed something in you migrations. ResumeSection needs to have and integer field called user_id. Just create a new migration that has something like this in it:
def change
add_column :resume_section, :user_id, :integer
end

Resources