"Elixir + phoenix" filtering websocket messages - websocket

I try to preprocess websocket incoming messages. In lobby_channel.ex file I have:
defmodule Chatroom.LobbyChannel do
use Phoenix.Channel
def join("room:lobby", _payload, socket) do
{:ok, socket}
end
def join("room:" <> _private_room_id, _params, _socket) do
{:error, %{reason: "unauthorized"}}
end
def handle_in("new_move", payload, socket) do
changeset = Chatroom.Move.changeset(%Chatroom.Move{}, payload)
if changeset.valid? do
broadcast! socket, "new_move", payload
{:noreply, socket}
else
IO.puts "-----" <> changeset.errors[:message]
push socket, "error", %{msg: "Invalid move"}
{:noreply, socket}
end
end
#intercept ["new_move"]
#def handle_out("new_move", payload, socket) do
# IO.puts payload["name"] <> "=======" <> payload["message"]
# push socket, "new_move", payload
# {:noreply, socket}
#end
end
Model file contains validation functions:
...
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:name, :message])
|> validate_required([:name, :message])
|> validate_length(:name, min: 1)
|> validate_length(:message, is: 2)
|> is_move_allowed(params)
end
defp is_move_allowed(changeset, params) do
IO.puts "--zz---" <> params["message"]
if params["message"] == "00" do
IO.puts "--yy---" <> params["message"]
add_error(changeset, params["message"], "'message' cannot be '00'")
else
IO.puts "--xx---" <> params["message"]
[]
end
end
...
..the last one is my custom validation function and I can't make it working without errors. The output I get:
--zz---00
--yy---00
[error] GenServer #PID<0.445.0> terminating
** (ArgumentError) argument error
:erlang.byte_size(nil)
(chatroom) web/channels/lobby_channel.ex:18: Chatroom.LobbyChannel.handle_in/3
(phoenix) lib/phoenix/channel/server.ex:226: anonymous fn/4 in Phoenix.Channel.Server.handle_info/2
(stdlib) gen_server.erl:601: :gen_server.try_dispatch/4
(stdlib) gen_server.erl:667: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{event: "new_move", payload: %{"message" => "00", "name" => "a"}, ref: "2", to
pic: "room:lobby"}
State: %Phoenix.Socket{assigns: %{}, channel: Chatroom.LobbyChannel, channel_pid: #PID<0.445.0>, endpoint: Chatroom
.Endpoint, handler: Chatroom.UserSocket, id: nil, joined: true, pubsub_server: Chatroom.PubSub, ref: nil, serialize
r: Phoenix.Transports.WebSocketSerializer, topic: "room:lobby", transport: Phoenix.Transports.WebSocket, transport_
name: :websocket, transport_pid: #PID<0.439.0>}
As I understand, things are moving to the right way logically, message contains correct data, but how can I fix the error? What am I missing?

There are three errors in the code:
is_move_allowed must always return a Changeset. You're returning [] in else.
add_error's second argument should be an atom
changeset.errors[:message] is a tuple, so you cannot use <> to concatenate it with a String. IO.inspect is a better alternative as it'll print any value.
So, change:
if params["message"] == "00" do
IO.puts "--yy---" <> params["message"]
add_error(changeset, params["message"], "'message' cannot be '00'")
else
IO.puts "--xx---" <> params["message"]
[]
end
to:
if params["message"] == "00" do
IO.puts "--yy---" <> params["message"]
add_error(changeset, :message, "'message' cannot be '00'")
else
IO.puts "--xx---" <> params["message"]
changeset
end
and change:
IO.puts "-----" <> changeset.errors[:message]
to:
IO.inspect changeset.errors[:message]
# or IO.inspect changeset.errors

Related

Elixir Ecto Changeset OR Validation

I am attempting to perform a Changeset validation on either an email OR phone number, and I found a nifty OR changeset function from #Dogbert here here - but I cannot get my OR validation flow to work correctly.
Does anyone mind taking a quick look on why the email or phone validation is always returning a nil changeset?
#doc false
def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:email, :phone])
|> validate_required_inclusion([:email, :phone])
|> validate_required_inclusion_format([:email, :phone])
end
defp validate_required_inclusion(changeset, fields) do
if Enum.any?(fields, &present?(changeset, &1)) do
changeset
else
# Add the error to the first field only since Ecto requires a field name for each error.
add_error(changeset, hd(fields), "One of these fields must be present: #{inspect fields}")
end
end
defp present?(changeset, field) do
value = get_field(changeset, field)
value && value != ""
end
## TODO - this doesnt work
defp validate_required_inclusion_format(changeset, fields) do
if Enum.member?(fields, :email) do
value = get_field(changeset, :email)
if value && value != "" do
IO.inspect(value, label: "email found: ")
changeset
|> email_changeset()
end
end
if Enum.member?(fields, :phone) do
value = get_field(changeset, :phone)
if value && value != "" do
IO.inspect(value, label: "phone found: ")
changeset
|> phone_changeset()
end
end
changeset
end
defp email_changeset(changeset) do
changeset
|> validate_required([:email])
|> validate_format(:email, ~r/.+#.+/)
|> unique_constraint(:email)
end
defp phone_changeset(changeset) do
changeset
|> validate_required([:phone])
|> valid_phone(:phone)
|> unique_constraint(:phone)
end
defp valid_phone(changeset, field) do
phone = get_field(changeset, field)
IO.inspect(phone, label: "phone: ")
{:ok, phone_number} = ExPhoneNumber.parse(phone, "US")
IO.inspect(phone_number, label: "ExPhoneNumber: ")
if ExPhoneNumber.is_valid_number?(phone_number) do
changeset
else
changeset
|> add_error(field, "Invalid Phone Number")
end
end
Thanks in advance!
You're not returning the modified changesets properly in validate_required_inclusion_format. In Elixir, the last value in a block is its return value. In if statements, the last value of both its true and false branch is its return value. If you don't have an else branch and the condition is false, the return value is nil.
Here's the simplest way to fix the problem: join the two top level if and the fallback changeset return with a ||:
defp validate_required_inclusion_format(changeset, fields) do
if Enum.member?(fields, :email) do
value = get_field(changeset, :email)
if value && value != "" do
IO.inspect(value, label: "email found: ")
changeset
|> email_changeset()
end
end || # <- note this
if Enum.member?(fields, :phone) do
value = get_field(changeset, :phone)
if value && value != "" do
IO.inspect(value, label: "phone found: ")
changeset
|> phone_changeset()
end
end || # <- and this
changeset
end
Now if the first or second if conditions are not met, you'll get a nil and the third if will be evaluated. If the third or fourth also not met, the final fallback changeset will be returned.
Note: the naming of this function is misleading. Unlike the function you used from my previous answer, you're not using fields at all here. You're better off not passing fields to this function and calling it something like add_email_or_phone_changeset, e.g.
if value = get_field(changeset, :email) do
...
end ||
if value = get_field(changeset, :phone) do
...
end || ...

what is the purpose of ".push" in this line "#errorLogin.push("Username should not be blank")" of code

Here is the code below:
I wanted to know the purpose of .push in this line: #errorLogin.push("Username should not be blank")
def loginValidations(errorLogin)
puts"inside loginValidations"
loginUsername = #params["username"]
puts "loginUsername: #{loginUsername}"
loginPass = #params["password"]
puts "loginPass: #{loginPass}"
logFlag=true
#count=0
if loginUsername == nil || loginUsername == ""
#errorLogin.push("Username should not be blank")
logFlag=false
end
#count=count+1
if loginPass == nil || loginPass == ""
#errorLogin.push("Password should not be blank")
logFlag=false
end
end
Array#push inserts an element at the end of the array.
a = [1,2,3]
a.push 4
# a = [1,2,3,4]
In that case probably that line inserts a failed validation in a variable that keeps track of login errors.

How do I parse strings into their data types?

I want to parse strings as follows:
"1.0" # => 1.0 (Float)
"1" # => 1 (FixNum)
"hello" # => "hello" (String)
"true" # => true (TrueClass)
"false" # => false (FalseClass)
I have the following code:
def parse_value(val)
raise ArgumentError, "value must be a string" unless val.is_a? String
if val.to_i.to_s == val
val.to_i
elsif val.to_f.to_s == val
val.to_f
elsif val == 'true'
true
elsif val == 'false'
false
else
val
end
end
This does what is needed, but it seems horrible and inefficient. What would be the best way to do this?
Short of using eval, you can't get much more concise/elegant code, I'm afraid. Here's a variant using case/when, but it's a lipstick on the same pig.
def parse_value(val)
raise ArgumentError, "value must be a string" unless val.is_a? String
case val
when /\A\d+\z/
val.to_i
when /\A\d+(\.\d+)?\z/
val.to_f
when 'true'
true
when 'false'
false
else
val
end
end
def parse_value(val)
raise ArgumentError, "value must be a string" unless val.is_a? String
case val
when /\A\d+\z/ then val.to_i
when /\A\d+(\.\d+)?\z/ then val.to_f
when 'true' then true
when 'false' then false
else val
end
end
I wrote this as a more concise version of the Sergios answer.
I would like feedback on whether this would be against Ruby style guidelines.
You could use eval along with a regular expression:
def parse_string(val)
raise ArgumentError, "value must be a string" unless val.is_a? String
val =~ /\A(\d+(\.\d+)?|true|false)\z/ ? eval(val) : val
end
parse_string '1.0' #=> 1.0
parse_string '1' #=> 1
parse_string 'hello' #=> "hello"
parse_string 'true' #=> true
parse_string 'false' #=> false
def convert(s)
i = Integer(s) rescue nil
return i if i
f = Float(s) rescue nil
return f if f
return true if s == "true"
return false if s == "false"
s
end
arr = %w| 1 1.0 true false hello |
#=> ["1", "1.0", "true", "false", "hello"]
arr.each { |s| e = convert(s); puts "#{e.class}: #{e}" }
# Fixnum: 1
# Float: 1.0
# TrueClass: true
# FalseClass: false
# String: hello

How to download a delayed file via HTTP in Ruby?

I use the following Ruby function to download various files via HTTP:
def http_download(uri, filename)
bytes_total = nil
begin
uri.open(
read_timeout: 500,
content_length_proc: lambda { |content_length|
bytes_total = content_length
},
progress_proc: lambda { |bytes_transferred|
if bytes_total
print("\r#{bytes_transferred} of #{bytes_total} bytes")
else
print("\r#{bytes_transferred} bytes (total size unknown)")
end
}
) do |file|
open filename, 'w' do |io|
file.each_line do |line|
io.write line
end
end
end
rescue => e
puts e
end
end
I also want to download files (csv, kml, zip, geojson) from this website. However, there is some kind of delay set up. When I click the download link in the browser it takes a bit until the download window appears. I suppose the file needs to be processed on the server before it can be served.
How can I modify my script to take the delay into account?
I am running Ruby 2.2.2.
Here is the modification according to the post and comment:
require 'open-uri'
def http_download(uri, filename)
bytes_total = nil
index = 1
begin
open(
uri,
read_timeout: 500,
content_length_proc: lambda { |content_length|
bytes_total = content_length
},
progress_proc: lambda { |bytes_transferred|
if bytes_total
print("\r#{bytes_transferred} of #{bytes_total} bytes")
else
print("\r#{bytes_transferred} bytes (total size unknown)")
end
}
) do |io|
# if "application/json" == io.content_type
if io.is_a? StringIO
raise " --> Failed, server is processing. Retry the request ##{index}"
else # Tempfile
puts "\n--> Succeed, writing to #{filename}"
File.open(filename, 'w'){|wf| wf.write io.read}
end
end
rescue => e
puts e
return if e.is_a? OpenURI::HTTPError # Processing error
index += 1
return if index > 10
sleep index and retry
end
end

forward email with attachments

I would like to do this without downloading the attachments and then re/attaching to the new email.
This is what i have tried:
$emailslist.each do |e|
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
subject "[Events] #{subjectstr}"
if e.attachments.length>0
e.attachments.each do |a|
add_file a
end
end
end
end
#error in 'e.attachments.each'=>undefined method `attachments' for
#<TypeError: can't convert nil into String>
EDIT
I have been using this code for months and it worked fine.
The new stuff i have introduced now is the code above.
Anyways I'm pasting the whole code upon request.
require 'mail'
$subscribers=[]
File.new("C:/Users/j.de_miguel/Desktop/mailman.forma/subscribers2.txt",'r').each do |line|
line=line.sub("\n","")
$subscribers.push(line) if line =~ /#/
end
puts $subscribers
$errorfile=File.new("C:/Users/j.de_miguel/Desktop/mailman.forma/error_log2.txt",'a+')
$errorfile.write("#{Time.now}\n")
$errorfile.flush
def deleteSubjectRecursion(subjstr)
if subjstr =~ /(.\[FORMA 2013\])+/
subjstr.gsub!(/.\[FORMA 2013\]/,"")
end
if subjstr =~ /((?i)Re: ){2,}/
subjstr.gsub!(/((?i)Re: ){2,}/,"Re: ")
end
return subjstr
end
def UserIsRegistered(mailaddr)
registered = false
$subscribers.each{|s| registered = true if mailaddr==s}
if registered == false
$errorfile.write("#{Time.now} : user #{mailaddr} attempted to mailman\n")
$errorfile.flush
end
return registered
end
Mail.defaults do
retriever_method :imap, { :address => "imap.1and1.es",
:port => 143,
:user_name => "mailman#somedomain.com",
:password => "xxxxxxxx",
:enable_ssl => false }
delivery_method :smtp, { :address => "smtp.1and1.es",
:port => 587,
:domain => '1and1.es',
:user_name => 'mailman#somaedomain.com',
:password => 'xxxxxxxxxxxx',
:authentication => 'plain',
:enable_starttls_auto => true }
end
#$emailslist=Mail.find(keys: ['NOT','SEEN'])
$emailslist=[Mail.last]
$emailslist.each do |e|
eplain_part = e.text_part ? e.text_part.body.decoded : nil
ehtml_part = e.html_part ? e.html_part.body.decoded : nil
type=e.charset
type_plain=eplain_part ? e.text_part.charset.to_s : nil
type_html=ehtml_part ? e.html_part.charset.to_s : nil
bodystr= type ? e.body.decoded.to_s.force_encoding(type) : nil
type=type ? type.to_s : type_plain
puts type.inspect
subjectstr=e.subject.to_s.encode(type)
fromstr=e.from.first.to_s.encode(type)
puts fromstr
bodystr_plain=eplain_part ? eplain_part.force_encoding(type_plain) : nil
bodystr_html=ehtml_part ? ehtml_part.force_encoding(type_html) : nil
$subscribers.each do |tostr|
puts tostr.inspect
if (not subjectstr =~ /^\[FORMA 2013\]/ ) && (UserIsRegistered(fromstr) == true)
subjectstr=deleteSubjectRecursion(subjectstr)
begin
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
bcc tostr
subject "[FORMA 2013] #{subjectstr}"
if ehtml_part != nil
html_part do
content_type("text/html; charset=# {type_html}")
#content_transfer_encoding("7bit")
body "# {bodystr_html}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
end
end
if eplain_part != nil
text_part do
content_type("text/plain; charset=# {type_plain}")
#content_transfer_encoding("7bit")
body "#{bodystr_plain}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
end
end
if eplain_part == nil && ehtml_part == nil
body "#{bodystr}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
charset=type
end
#puts e.attachments.inspect
if e.attachments.length>0
e.attachments.each do |a|
add_file a.encoded
end
end
end
puts "1 email sent"
rescue => e
puts "error: #{e}"
$errorfile.write("#{Time.now}\nerror sending to #{tostr}: #{e},\nemail subject: #{subjectstr}\n\n")
$errorfile.flush()
end
end
end
end
$errorfile.close()
This is untested, and isn't really an attempt to find or fix the bug. It's to show how your code should look, written in more idiomatic Ruby code. And, as a result, it might fix the problem you're seeing. If not, at least you'll have a better idea how you should be writing your code:
require 'mail'
Define some constants for literal strings that get reused. Do this at the top so you don't have to search through the code to change things in multiple places, making it likely you'll miss one of them.
PATH_TO_FILES = "C:/Users/j.de_miguel/Desktop/mailman.forma"
BODY_BOILERPLATE_FORMAT = "%s\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
Group your methods toward the top of the file, after constants.
We open using 'a', not 'a+'. We don't need read/write, we only need write.
This opens and closes the file as necessary.
Closing the file automatically does a flush.
If you're calling the log method often then there are better ways to do this, but this isn't a heavyweight script.
I'm using File.join to build the filename based on the path. File.join is aware of the path separators and does the right thing automatically.
String.% makes it easy to create a standard output format.
def log(text)
File.open(File.join(PATH_TO_FILES, "error_log2.txt"), 'a') do |log_file|
log_file.puts "%s : %s" % [Time.now, text]
end
end
Method names in Ruby are snake_case, not CamelCase.
There's no reason to have multiple gsub! nor are the conditional tests necessary. If the sub-string you want to purge exists in the string gsub will do it, otherwise it moves on. Chaining the gsub methods reduces the code to one line.
gsub could/should probably be sub unless you know there could be multiple hits to be substituted in the string.
return is redundant so we don't use it unless we're explicitly returning a value to leave a block prematurely.
def delete_subject_recursion(subjstr)
subjstr.gsub(/.\[FORMA 2013\]/,"").gsub(/((?i)Re: ){2,}/, "Re: ")
end
Since registered is supposed to be a boolean, use any? to do the test. If any matches are found any? bails out and returns true.
def user_is_registered(mailaddr)
registered = subscribers.any?{ |s| mailaddr == s }
log("user #{ mailaddr } attempted to mailman") unless registered
registered
end
Use foreach to iterate over the lines of a file.
subscribers = []
File.foreach(File.join(PATH_TO_FILES, "subscribers2.txt")) do |line|
subscribers << line.chomp if line['#']
end
puts subscribers
log('')
Mail.defaults do
retriever_method(
:imap,
{
:address => "imap.1and1.es",
:port => 143,
:user_name => "mailman#somedomain.com",
:password => "xxxxxxxx",
:enable_ssl => false
}
)
delivery_method(
:smtp,
{
:address => "smtp.1and1.es",
:port => 587,
:domain => '1and1.es',
:user_name => 'mailman#somaedomain.com',
:password => 'xxxxxxxxxxxx',
:authentication => 'plain',
:enable_starttls_auto => true
}
)
end
#emailslist=Mail.find(keys: ['NOT','SEEN'])
emailslist = [Mail.last]
emailslist.each do |e|
This use of ternary statements here is probably not desirable but I left it.
Formatting into columns makes it easier to read.
Organize your assignments and uses so they're not strewn all through the file.
eplain_part = e.text_part ? e.text_part.body.decoded : nil
type_plain = eplain_part ? e.text_part.charset.to_s : nil
ehtml_part = e.html_part ? e.html_part.body.decoded : nil
type_html = ehtml_part ? e.html_part.charset.to_s : nil
e_charset = e.charset
body_str = e_charset ? e.body.decoded.to_s.force_encoding(e_charset) : nil
e_charset = e_charset ? e_charset.to_s : type_plain
puts e_charset.inspect
subjectstr = e.subject.to_s.encode(e_charset)
fromstr = e.from.first.to_s.encode(e_charset)
puts fromstr
bodystr_plain = eplain_part ? eplain_part.force_encoding(type_plain) : nil
bodystr_html = ehtml_part ? ehtml_part.force_encoding(type_html) : nil
subscribers.each do |subscriber|
puts subscriber.inspect
if !subjectstr[/^\[FORMA 2013\]/] && user_is_registered(fromstr)
subjectstr = delete_subject_recursion(subjectstr)
begin
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
bcc subscriber
subject "[FORMA 2013] #{ subjectstr }"
if ehtml_part
html_part do
content_type("text/html; charset=#{ type_html }")
#content_transfer_encoding("7bit")
body BODY_BOILERPLATE_FORMAT % bodystr_html
end
end
if eplain_part
text_part do
content_type("text/plain; charset=#{ type_plain }")
#content_transfer_encoding("7bit")
body BODY_BOILERPLATE_FORMAT % bodystr_plain
end
end
if !eplain_part && !ehtml_part
body BODY_BOILERPLATE_FORMAT % body_str
charset = e_charset
end
#puts e.attachments.inspect
e.attachments.each { |a| add_file a.encoded } if e.attachments.length > 0
end
puts "1 email sent"
rescue => e
puts "error: #{ e }"
log("error sending to #{ subscriber }: #{ e },\nemail subject: #{ subjectstr }")
end
end
end
end
if e.attachments.length>0
e.attachments.each do |a|
add_file a
end
end
That can be refactored into a simple, single-line using a trailing conditional if test:
e.attachments.each { |a| add_file a.encoded } if e.attachments.length > 0
Using a single line like this is OK when you're doing something simple. Don't use them for more complex code because you'll induce visual noise, which makes it hard to understand and read your code.
But let's look at what the code above is actually doing. e.attachments in this context appears to be returning an array, or some sort of enumerable collection, otherwise each wouldn't work. length will tell us how many elements exist in the "array" (or whatever it is) that is returned by attachments.
If length is zero, then we don't want to do anything, so we could say:
e.attachments.each { |a| add_file a.encoded } unless e.attachments.empty?
(Assuming attachments implements an empty? method.)
That's kind of redundant too though. If e.attachments is empty already, what will each do? It would check to see if attachments returned an array containing any elements and if it's empty it'd skip its block entirely, effectively acting just like the trailing if condition was triggered. SOOOooo, we can use this instead:
e.attachments.each { |a| add_file a.encoded }
Ruby Style guides:
https://github.com/bbatsov/ruby-style-guide
https://github.com/styleguide/ruby
The second is based on the first.
The Tin Mans answer mostly works. I change how attachments were added since his version was not working for me.
e.attachments.each { |a| attachments[a.filename] = a.decoded } if e.attachments.length > 0

Resources