my users have the option to add their website, facebook and twitter URL's to their profile.
I want to let them enter either the full URL (http://www.facebook.com/USERNAME) or part of the URL Eg. www.facebook.com/USERNAME or just USERNAME, and then have the https://facebook.com/ added automatically if needed. I want the http:// as then the entered URL will link directly to their website/facebook etc.
For the website URL I have:
before_validation :add_url_protocol
def add_url_protocol
if self.website && !url_protocol_present?
self.website = "http://#{self.website}"
end
end
def url_protocol_present?
self.website[/\Ahttp:\/\//] || self.website[/\Ahttps:\/\//]
end
There is then further regex validation.
This works fine.
The thing is I don't have much of an idea about regex and I am unsure on how to add the facebook.com/ part to this before_validation code.
Any help would be greatly appreciated, thanks.
UPDATE:
def add_url_protocol
if self.website && !url_protocol_present?
self.website = "http://#{self.website}"
end
if self.facebook && !url_facebook_present?
self.facebook = "http://facebook.com/#{self.facebook}"
end
end
This almost works. If a user inputs USERNAME then the output is good. If the user inputs www.facebook.com/USERNAME then the ouput becomes http://facebook.com/www.facebook.com/USERNAME
The best way to do this would be to collect user input in a form model, you can roll your own with by including ActiveModel::Model or use something like Reform.
The easiest thing you can do is simply treat their input as a string containing their facebook username separated by "/" so whatever the string they enter you can get the username by
"https://www.facebook.com/their.username".split('/')[-1] # 'their.username'
"www.facebook.com/their.username".split('/')[-1] # 'their.username'
"their.username".split('/')[-1] # 'their.username'
Simply declare a username attribute in your form model and overwrite the setter to extract the facebook username. Then only save the facebook username in your database, and write a method such as
def facebook_profile_url
"www.facebook.com/#{fb_username}"
end
No need to persist the redundant facebook url part.
I would try
match = /^(https?:\/\/)?((www\.)?facebook\.com)?(.*)/.match(self.website)
self.website = match[1] || 'http://'
self.website << match[2] || 'facebook.com'
self.website << match[3]
You can create capture groups by using parentheses. The ? means that group or char before it is optional (so I was able to condense your protocol search)
You may need to check my indexes into the match array...
Sorry this is untested as it is from a phone...
Related
I am trying to do a search with multiple attributes for Address at my Rails API.
I want to search by state, city and/or street. But user doesn't need to send all attributes, he can search only by city if he wants.
So I need something like this: if the condition exists search by condition or return all results of this condition.
Example:
search request: street = 'some street', city = '', state = ''
How can I use rails where method to return all if some condition is nil?
I was trying something like this, but I know that ||:all doesn't work, it's just to illustrate what I have in mind.:
def get_address
address = Adress.where(
state: params[:state] || :all,
city: params[:city] || :all,
street: params[:street] || :all)
end
It's possible to do something like that? Or maybe there is a better way to do it?
This is a more elegant solution using some simple hash manipulation:
def filter_addesses(scope = Adress.all)
# slice takes only the keys we want
# compact removes nil values
filters = params.permit(:state, :city, :street).to_h.compact
scope = scope.where(filters) if filters.any?
scope
end
Once you're passing a column to where, there isn't an option that means "on second thought don't filter by this". Instead, you can construct the relation progressively:
def get_address
addresses = Address.all
addresses = addresses.where(state: params[:state]) if params[:state]
addresses = addresses.where(city: params[:city]) if params[:city]
addresses = addresses.where(street: params[:street]) if params[:street]
addresses
end
I highly recommend using the Searchlight gem. It solves precisely the problem you're describing. Instead of cluttering up your controllers, pass your search params to a Searchlight class. This will DRY up your code and keep your controllers skinny too. You'll not only solve your problem, but you'll have more maintainable code too. Win-win!
So in your case, you'd make an AddressSearch class:
class AddressSearch < Searchlight::Search
# This is the starting point for any chaining we do, and it's what
# will be returned if no search options are passed.
# In this case, it's an ActiveRecord model.
def base_query
Address.all # or `.scoped` for ActiveRecord 3
end
# A search method.
def search_state
query.where(state: options[:state])
end
# Another search method.
def search_city
query.where(city: options[:city])
end
# Another search method.
def search_street
query.where(street: options[:street])
end
end
Then in your controller you just need to search by passing in your search params into the class above:
AddressSearch.new(params).results
One nice thing about this gem is that any extraneous parameters will be scrubbed automatically by Searchlight. Only the State, City, and Street params will be used.
What is the proper way to pass the parameters to a function?
For example:
def self.find_by_example(username, email)
user = User.find_by_username(username) || User.find_by_email(email)
end
I would like to find the user by his username or email but if a create a function passing the 2 parameters Rails shows
(wrong number of arguments (given 1, expected 2))
When I call User.find_by_example('example')
I still don't get it, the parameters passed in must not be the attribute?
and why does it say "given 1"?
You must be calling the function like `User.find_by_example("name to find") and the function expects two arguments (name and email). You could define the function as:
def self.find_by_example(term)
user = User.find_by_username(term) || User.find_by_email(term)
end
And call it User.find_by_example("Name to find") or User.find_by_example("email#to_find.com")
This does not work ok if you have users with a username like an email. And it is not much efficient if you wish to search by other fields. SO you could also:
def self.find_by_example(terms)
if terms[:email]
user = User.find_by_email(terms[:email])
elsif terms[:username]
user = User.find_by_username(terms[:username])
elsif terms[:id]
user = User.find_by_id(terms[:id])
elsif terms[:document]
user = User.find_by_document(terms[:document])
end
end
And call the method User.find_by_example(:email => "email#example.com"). This is similar to the find_by method that Active Record already provides (but allows many arguments), so no need to implement it.
The proposed and accepted answer is not really the equivalent of the code asked in the question. It is accepted, so one might assume that it guessed the OP intent correctly. But I think it can be useful for (especially junior) programmers to think about the problem more deeply.
Think of what method should do
(not just if it immediately gives you result you wish to see, there can be surprises in the edge cases)
The original code
def self.find_by_example(username, email)
user = User.find_by_username(username) || User.find_by_email(email)
end
Could be used this way x.find_by_example(nil, 'test#example.com').
If we assume there can't be users with NULL username (which IMO is a reasonable assumption), the call would result in finding an user strictly by email.
The proposed solution does not give you this possibility:
def self.find_by_example(term)
user = User.find_by_username(term) || User.find_by_email(term)
end
x.find_by_example('test#example.com') would return user with such username if exists, and (possibly other) user with such e-mail otherwise.
In other words - you have less control which field is used to find a user (which can be correct, if that's really what you need)
So it depends on the OP intent.
If one want to retain how the original method works, but improve the interface, it could be done like this:
def self.find_by_example2(username: nil, email: nil)
user = User.find_by_username(username) || User.find_by_email(email)
end
And calling x.find_by_example2(email: 'test#example.com') is equivalent to x.find_by_example(nil, 'test#example.com') but looks better.
Bonus: Performance implications
The proposed solution
def self.find_by_example(term)
user = User.find_by_username(term) || User.find_by_email(term)
end
makes second query when the user is not find by username. You can improve it as well if you wish to employ some sql magic:
def self.find_by_example(term)
user = User.where("username = ? OR (username IS NULL and email = ?)", term, term).first
end
There's another possibility (though not 100% equivalent to the accepted solution):
def self.find_by_example(term)
user = User.where("username = ? OR email = ?", term, term).first
end
(I'll leave as an exercise the answer how those are different, to keep this post short...ish)
Bonus 2: flexibility
This
def self.find_by_example(terms)
if terms[:email]
user = User.find_by_email(terms[:email])
elsif terms[:username]
user = User.find_by_username(terms[:username])
elsif terms[:id]
user = User.find_by_id(terms[:id])
elsif terms[:document]
user = User.find_by_document(terms[:document])
end
end
is a waste of your time, because rails gives you already better interface to do this.
Instead of calling
x.find_by_example(document: 'foo')
you could just do
User.find_by(document: 'foo')
There's really no need to implement it that way, it's basically crippled version of ActiveRecord interface, that you have to maintain as you add new fields to User model.
Thanks for your help!
I'm trying to save twitter like hashtags in my rails app. Users enter their tags prepended by the #hashtag symbol. However, it keeps saving empty strings. I added an additional unless statement to combat it, but now it doesn't save any tags.
Code:
def tag_list=(names)
self.tags = names.split(/\B#\w+/).map do |n|
unless n.strip == "" || n.strip == nil
Tag.where(name: n.strip).first_or_create!
end
end
end
I've also tried the following regex which also return the same:
/\B#\w+/
/(?:^|\s)(?:(?:#\d+?)|(#\w+?))\s/i
/(?:\s|^)(?:#(?!\d+(?:\s|$)))(\w+)(?=\s|$)/i
Your first regex works totally, but you must use scan instead of split, so your code to assign the tags would be:
def tag_list=(names)
self.tags = names.scan(/\B#\w+/).map do |tag|
Tag.find_or_initialize_by(name: tag.remove('#'))
end
save!
end
The changes are:
Use scan
Use find_or_initialize_by instead of where then first_or_create!
Use save! at the end to save once
You may not need tag.remove('#') if you want to save the hashtag with # prefix
I'm using omniauth exclusively to allow login to my website with facebook/google/twitter.
I store first name, last name, and email. However, when I raise the twitter auth hash from oauth I only get nickname, name, location, image, description and urls in the auth hash.
Is there a scope I can pass in my initializer to get the user's email and break name out into the first_name, last_name fields?
Twitter does not give out user emails so you will not be able to get that information from the Twitter API. Instead, you have to ask the user to type in their email address on your sign up form.
As far as splitting the name up, you'd do that once you have the hash returned using something like:
social_params ||= request.env["omniauth.auth"]
fullname = social_params["user_info"]["name"].split(' ')
first_name, last_name = fullname[0], fullname[1]
puts "first name is #{first_name} and last name is #{last_name}"
Just keep in mind that last_name could be nil if they don't have a space in their name or they didn't give a last name. This also doesn't consider the fact that many people have multiple last names in other cultures.
Using the current Twitter API is possible getting the email. You have to fill a form requesting that permission. The process is easy and quick, it is explained here.
Requesting a user’s email address requires your application to be whitelisted by Twitter. To request access, please use this form.
Once whitelisted, the “Request email addresses from users” checkbox will be available under your app permissions on apps.twitter.com. Privacy Policy URL and Terms of Service URL fields will also be available under settings which are required for email access. If enabled, users will be informed via the oauth/authorize dialog that your app can access their email address.
Well Twitter by Design will not pass you use email id.This is a deliberate design decision by the API team.
Here is same thread for your refrence
Is there a way to get an user's email ID after verifying her Twitter identity using OAuth?
If you have integration with FB , Google and Twitter than you will need to have a first and last in your DB (if your UI requires it > my case). This is what I came up with as some countries have people with more than 2 tokens for their names ex: (Marco De Franca Solis) or (Marco, De Franca Solis)
// + TEST CASES
var name1 = handleName("Marco De Franca Solis")
var name2 = handleName("first,last wer wer")
var name3 = handleName("aName")
var name4 = handleName("")
// - TEST CASES
handleName = function(input) {
var n=input.indexOf(" ");
n += input.indexOf(",");
var result = {};
if(n < 0){
result.first = input
result.last = ""
}
else{
arr = input.split(/[ ,]+/);
result.first = arr[0]
result.last = ""
if(arr.length > 1)
{
arr[0] = ""
result.last = arr.join(" ");
}
}
return result
}
OmniAuth is giving you all the names combined in one string. But, some people have more than two-word names, such as "John Clark Smith". You can choose to treat those in three different ways:
(1) first_name: "John", last_name: "Smith"
def first_name
if name.split.count > 1
name.split.first
else
name
end
end
def last_name
if name.split.count > 1
name.split.last
end
end
(2) first_name: "John Clark", last_name: "Smith"
def first_name
if name.split.count > 1
name.split[0..-2].join(' ')
else
name
end
end
def last_name
if name.split.count > 1
name.split.last
end
end
(3) first_name: "John", last_name: "Clark Smith"
def first_name
name.split.first
end
def last_name
if name.split.count > 1
name.split[1..-1].join(' ')
end
end
The above examples assume that if the name contains less than 2 words then it is a first name. This question is similar to this one
for php use:
$names = explode(' ',$socialData->firstName);
$socialData->firstName=array_shift($names);
$socialData->lastName=implode(' ',$names);
Be aware the name could have multiple surnames and possibly multiple firstnames, this deals with multiple surnames but not firstnames.
I have an algorithm that searches through all of my sites users, finding those which share a common property with the user using the algorithm (by going to a certain page). It can find multiple users, each can have multiple shared properties. The algorithm works fine, in terms of finding the matches, but I'm having trouble working out how to store the data so that later I'll be able to use each unit of information. I need to be able to access both the found users, and each of the respective shared properties, so I can't just build a string. This is an example of the output, being run from the perspective of user 1:
user 4
sharedproperty3
sharedproperty6
user 6
sharedproperty6
sharedproperty10
shareproperty11
What do I need to do to be able to store this data, and have access to any bit of it for further manipulation? I was thinking of a hash of a hash, but I can't really wrap my head around it. I'm pretty new to programming, and Ruby in particular. Thanks for reading!
EDIT - Here's the code. I'm fully expecting this to be the most incorrect way to do this, but it's my first try so be gentle :)
So if I'm understanding you guys correctly, instead of adding the interests to a string, I should be creating an array or a hash, adding each interest as I find it, then storing each of these in an array or hash? Thanks so much for the help.
def getMatchedUsers
matched_user_html = nil
combined_properties = nil
online_user_list = User.logged_in.all
shared_interest = false
online_user_list.each do |n| # for every online user
combined_properties = nil
if n.email != current_user.email # that is not the current user
current_user.properties.each do |o| # go through all of the current users properties
n.properties.each do |p| # go through the online users properties
if p.interestname.eql?(o.interestname) # if the online users property matches the current user
shared_interest = true
if combined_properties == nil
combined_properties = o.interestname
else
combined_properties = combined_properties + ", " + o.interestname
end
end
end
if shared_interest == true
matched_user_html = n.actualname + ": " + combined_properties
end
end
end
end
return matched_user_html
render :nothing => true
end
This returns an array of hashes with all users and their corresponding sharedproperties.
class User
def find_matching_users
returning Array.new do |matching_users|
self.logged_in.each do |other_user|
next if current_user == other_user # jump if current_user
# see http://ruby-doc.org/core/classes/Array.html#M002212 for more details on the & opreator
unless (common_properties = current_user.properties & other_user.properties).empty?
matching_users << { :user => other_user, :common_properties => common_properties }
end
end
end
end
end
In your view you can do something like this:
<%- current_user.find_matching_users.each do |matching_user| -%>
<%-# you can acccess the user with matching_user[:user] -%>
<%-# you can acccess the common properties with matching_user[:common_properties] -%>
<%- end -%>
You can use a hash table with the key being the user object and the value being an array of the shared properties . This is assuming that you first need to do a lookup based on the user .
Something like this :
#user_results = { user1 => [sharedproperty3,sharedproperty7] , user2 => [sharedproperty10,sharedproperty11,sharedproperty12]}
You can then acces the values like :
#user_results[user1]
or you can also iterate over all the keys using #user_results.keys