I have a simple app running Rails3 & the gmaps4rails gem.
I have the map displaying successfully.
But, when I create a new User, the lat/long is not automatically generated like in the screencast. What am I missing?
Here is my model code:
class Merchant < ActiveRecord::Base
acts_as_gmappable
def gmaps4rails_address
"#{self.address}, #{self.zip} #{self.city}, # {self.country}"
end
end
I also can't enter in an address as simply "City, State". I have to put in a complete address or I get an error.
There will not be any issue not using the example I provide in the wiki:
def gmaps4rails_address
"#{self.address}, #{self.zip} #{self.city}, # {self.country}"
end
This is just supposed to tell that you can customize it as you need.
So yes, it could simply be:
def gmaps4rails_address
self.address
end
Related
I'm currently implementing liquid templates in my application. As part of that I have created a set of liquid drop (https://github.com/Shopify/liquid/wiki/Trying-to-Understand-Drops) classes to act as intermediates between my models and my templates. I'm currently using devise for authentication on rails 5.
In my product drop class I would like to be able to check if my current user owns the product:
class ProductDrop < Liquid::Drop
def initialize(model)
#model = model
end
def owned_by_user?
#somehow access the current_user provided by devise.
end
end
But haven't been able to figure out how to access the user.
I notice in this method on shopify: https://help.shopify.com/en/themes/liquid/objects/variant#variant-selected
They are able to access the current url to work out if the variant is selected. I thought perhaps it might be possible if they can access the url, to access the session and get the user identifier to look up the user.
So I can do something like:
def owned_by_user?
User.find_by_id(session[:user_id]).owns_product?(#model.id)
end
I'm not having any luck accessing the session. Does anyone have any suggestions or ideas? Or am I going about this completely the wrong way?
So after digging around in the liquid drop source code. I noticed that the context is accessible to the drop (https://github.com/Shopify/liquid/blob/master/lib/liquid/drop.rb). I totally missed it the first time I looked.
So the solution ended up being:
First add the user so it is available to the controller action the view is rendered for. this then gets added to the context by the liquid template handler (and therefore exists in the context)
class ApplicationController < ActionController::Base
before_action :set_common_variables
def set_common_variables
#user = current_user # Or how ever you access your currently logged in user
end
end
Add the method to the product to get the user from the liquid context
class ProductDrop < Liquid::Drop
def initialize(model)
#model = model
end
def name
#model.name
end
def user_owned?
return #context['user'].does_user_own_product?(#model.id)
end
end
Then add the method to the user to check if the user owns the product or not:
class UserDrop < Liquid::Drop
def initialize(model)
#model = model
end
def nick_name
#model.nick_name
end
def does_user_own_product?(id)
#model.products.exists?(id: id)
end
end
Obviously this needs error handling and so on. but hopefully that helps someone. Also if anyone knows of a better way, keen to hear it.
I am trying to learn how to write a service class in my rails 5 app.
When a user registers with devise, I'm trying to incorporate a service class that makes models associated with the user's account on creation of the user account.
In my devise registrations controller, I have:
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters, if: :devise_controller?
def create
super do |user|
if user.persisted?
User::CompleteRegistration.call(user: user)
end
end
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name, :email])
devise_parameter_sanitizer.permit(:account_update, keys: [:first_name, :last_name, :email, ])
end
private
end
In my app / services/ user folder, I have a file called: complete_registration.rb:
class User::CompleteRegistration #< ActiveRecord::Base
def self.call(user: u)
new(user: user).call
end
def initialize(user: u)
self.user = user
end
def call
Profile::SetupService.call(user: user)
Setting::SetupService.call(user: user)
User::OrganisationMapperService.new(user: user).call
end
# def join_an_organisation
# render "organisation_requests/new"
# end
private
attr_accessor :user
end
When I try to save this and then sign up as a user, I can see an error message that says:
NameError - uninitialized constant User::CompleteRegistrations:
I note that it has pluralised the word 'registration'. I don't know if this has something to do with the problem. I tried saving the file as a plural, and renaming the class as a plural, but that didn't work.
The actual error rendered says:
PG::UndefinedTable at /users
ERROR: relation "complete_registration_services" does not exist
LINE 8: WHERE a.attrelid = '"complete_registration_se...
I cant expand the message to get more detail, but there is no relation to be found, because it isnt an active record table that I'm trying to call.
I also tried adding a callback to my user model as an alternative solution:
class User < ApplicationRecord
after_update :after_confirmation_setup
def after_confirmation_setup
return unless !self.confirmed_at.blank?
User::CompleteRegistration.call(user: #user)
end
But that doesnt work either.
Can anyone see how I can setup my app to call a service class on user create (from the registration controller, or any other method)?
By the looks of things, this seems like a naming issue.
From first appearances (I can't say definatively without actually using your code base in console and debugging) but based on a quick scan and looking at the errors you are receiving this appears to be one of Ruby's quirks which I have bumped into a few times.
You have a User class in the global scope (In your models directory), and you have a service class User::CompleteRegistration, which when you call from somewhere is perfectly logical to assume will point to the defined User::CompleteRegistration class. However, Ruby doesn't see it like that.
User::CompleteRegistration can be split into two parts, first User:: Is evaluated, and Ruby searches for a User class, and grabs your model. It then evaluates the CompleteRegistration, and looks for a defined CompleteRegistration that can be used in your User class. This is may be why you are getting the error about a relation. It is searching for a relation within the User model scope.
So essentially, when you write User::CompleteRegistration Ruby doesnt say Right! lets grab a User::CompleteRegistration class! Ruby says Right! lets grab a CompleteRegistration class that can be used within the User scope!
To prevent this, I would perhaps change the naming of your service to something more simple, like: Registration::Complete and avoid any overlapping of class / module names within the global scope.
also on a side note, I too love using services in code, and I don't really want to write a shameless plug on SO but perhaps it your case it can help? I have written a gem to provide easy to use services that has an implementation not too dissimilar to what you are using, perhaps it could be of use, whether you use it as a gem or just scan the code in it, I hope somehow maybe it can help.
In my rails model Post.rb I have the following methods setup:
def category_names(seperator = ", ")
categories.map(&:name).flatten.join(seperator).titleize
end
def publish_date
read_attribute(:publish_date).strftime('%A, %b %d')
end
I would like to move these into PostsHelper module under helpers. But get a no-method error when I do so because I guess the reference to self is lost.
So how can I fix this? Is a helper module the wrong place for these methods?
Helper methods are designed mainly to be used in views, if I'm not mistaking.
What I'm sure about is your helper methods are outside of the scope of your model, meaning you need to pass to them the attribute to work with. Example:
def category_names(categories, seperator = ", ")
categories.map(&:name).flatten.join(seperator).titleize
end
And call in your view:
category_names #post.categories
If you find your self writing "helper" method that are not exclusively used in your view, you could create a service object and include them in your model.
Edit: Service object
You can create a "services" directory under "app" directory and create your classes there.
Let me give you an example. I got a User model class, and I want to group all password related methods in a UserPassword service object.
User class:
class User < ActiveRecord::Base
include ::UserPassword
...
end
UserPassword service object:
require 'bcrypt'
module UserPassword
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
module ClassMethods
def authenticate(email, password)
user = find_by_email email
if user and user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
end
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
So my User instanced object (i.e u = User.first) can call u.encrypt_password, and my User class can call User.authenticate.
There is maybe other way, but I find it flexible and easy to write and maintain :)
Helpers are always intended to help views, But if you want your Models to be clean and keep the unrelated methods as separate try using concerns in Rails 3. Concerns directory will be present by default in Rails 4.
There is a nice blogpost by DHH for same.
http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns
I'm installing a forum using the Forem gem. There's an option that allows avatar personalization, since it's possible to login with Facebook. You just specify your method in the User model and that's it.
# Forem initializer
Forem.avatar_user_method = 'forem_avatar'
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
end
end
But I want a fallback on Gravatar for normal, non-facebook accounts. I've found the method on Forem and in theory, I need to call the avatar_url method:
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
else
Forem::PostsHelper.avatar_url self.email
end
end
However, Forem isn't an instance, but a module and I can't call it nor create a new instance. The easy way is to copy the lines of that method, but that's not the point. Is there a way to do it?
Thanks
Update
Both answers are correct, but when I call the method either way, there's this undefined local variable or method 'request' error, which is the last line of the original avatar_url.
Is there a way to globalize that object like in PHP? Do I have to manually pass it that argument?
perhaps reopen the module like this:
module Forem
module PostsHelper
module_function :avatar_url
end
end
then call Forem::PostsHelper.avatar_url
if avatar_url call other module methods, you'll have to "open" them too via module_function
or just include Forem::PostsHelper in your class and use avatar_url directly, without Forem::PostsHelper namespace
If you want to be able to use those methods in the user class, include them and use
class User < ActiveRecord::Base
include Forem::PostsHelper
def forem_avatar
return user_pic if user_pic.present?
avatar_url email
end
end
Another way would be to set the Forem.avatar_user_method dynamically since the Forem code checks it it exists before using it and defaults to avatar_url if it does not.
class User < ActiveRecord::Base
# This is run after both User.find and User.new
after_initialize :set_avatar_user_method
# Only set avatar_user_method when pic is present
def set_avatar_user_method
unless self.user_pic.empty?
Forem.avatar_user_method = 'forem_avatar'
end
end
def forem_avatar
self.user_pic
end
end
This way you dont pollute your model with unnecessary methods from Forem and don't monkey patch Forem itself.
My project is about an online Mobile Shopping site.
I created search controller,and I use WHERE LIKE to do that:
def create
#result = Phone.where(['name LIKE ?', "%#{get}%" ])
render :index
end
private
def get
params[:keyword]
end
Now I want to exchange to Full-text-search.So have some gem to do that ? And how to code to searching by full-text-search ?
You can use elastic-search-rails gem for implementing full text search in your Rails application.
In your app/models/phone.rb:
require 'elasticsearch/model'
class Phone < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
end
Phone.import
Then, in your controller:
#result = Phone.search('foobar').records
Here is a good tutorial on Getting Started with Elasticsearch on Rails that would help you. Another good SitePoint article that will help you get going is Full-Text Search in Rails with ElasticSearch.