Identifying links in message body for view - ruby-on-rails

I have a rails app where users can talk to each other via chat. If they send over links in the chat (if you send anything at the moment in the chat it gets saved as message.body) to each other I would like to somehow distinguish it from plain text to be able to show the links on an index page later. How could I do that? At the moment 2 users have one common conversation. And messages belong to conversation.
Should I try to save them as different message attribute like message.link? If yes, then how can I distinguish message.link from message.body when creating the message. Or can I just simply use some helper method and showing only those message.body-s where the body itself is a link. And what if the half of the message is plain text and the other half is a link?
message schema
create_table "messages", force: :cascade do |t|
t.text "body"
t.integer "conversation_id"
t.integer "user_id"
t.string "message_attachment"
end

You can use Rinku.
1. Add it to your gemfile
gem 'rinku'
2. Create a method in your ApplicationHelper name it find_links
def find_links(message_body)
Rinku.auto_link(message_body, mode=:all, link_attr=nil, skip_tags=nil).html_safe
end
3. In your view pass the message body to the find_links method.
= find_links(message.body)

Related

Ruby on Rails: wrong number of arguments (0 for 1) even if provided

We inherited a rails project (the whole thing is based on elasticsearch). Thus, the application lists all the documents meeting the provided search criteria by a user. Imagine a facet or so, once you've selected i.e. a specific range of dates it gives you the documents that were created at that time.
We have been currently working on expanding its functionality accordingly:
Our priority is to utilize the elastic query such that all
matched documents by that query could be multiply altered (in
our case, we've been trying to hide all the documents). Then we want to send the query to appropriate rake task that would take care of it.
After the button responsible for mass hiding is clicked the controller's create method gets called, thereby creating a mass_hiding record in database (for convenience we keep a track of hidings which would allow us to revert the mass action afterwards).
def create
mh = current_user.mass_hidings.build(params[:mass_hiding])
mh.save!
mass_hide(mh.query_params)
redirect_to search_documents_path(mass_hide.search_parameters)
end
def mass_hide(query)
search = factic.create_restrictions_search(MultiJson.load(query.to_json))
Resque.enqueue(Document::Jobs::HideDocuments, nil, search.to_scrollable.build_query)
end
However, the line 3 of the create method mass_hide(mh.query_params) triggers the following exception once it gets executed:
ArgumentError in Admin::MassHidingsController#create
wrong number of arguments (0 for 1)
Full trace can be found here.
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"6mZvRcp4HJuoBWMRToA2gPec9Wv8T82hiTJQ/STf1j/sDhQ+16mBW3QkRmhqlJIHKR0kvX/kqwQh205hp6RuDg==",
"mass_hiding"=>{"serialized_query_params"=>"{}",
"description"=>"test"},
"commit"=>"Hide documents"}
Further, here is the schema representation of mass_hiding as well as the appropriate model:
schema.rb
create_table "mass_hidings", force: :cascade do |t|
t.integer "user_id"
t.text "serialized_query_params"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
mass_hiding.rb
class MassHiding < ActiveRecord::Base
attr_accessible :serialized_query_params, :description
belongs_to :user
def query_params=(params)
self.serialized_query_params = Oj.dump(params)
end
def query_params
Oj.load(self.serialized_query_params).with_indifferent_access
end
def self.find_or_initialize_by_user_and_query_params(user, query_params)
self.find_or_initialize_by_user_id_and_serialized_query_params(user.id, Oj.dump(query_params))
end
end
Thank you for your help in advance.
The problem is that you have defined this helper function:
def mass_hide(query)
But you are calling it with zero arguments.
First you call it with one argument, when you say mass_hide(mh.query_params). That's fine.
But then on the next line you also say mass_hide.search_parameters. To Ruby that means mass_hide().search_parameters. Perhaps you meant to write mh.search_parameters? I'm not sure. But mass_hide is the method that wants 1 param, and that's the callsite where you pass it 0.
I hope that helps!

Subscription based restrictions for rails queries

I have a rather complex issue that I could some assistance with, as all implementations I've been trying are flawed (Rails 4.2.1).
Users have access to devices:
create_table "devices", force: :cascade do |t|
t.string "device_guid"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_ids"
end
and devices have many data entries:
create_table "datas", force: :cascade do |t|
t.string "device_id"
t.datetime "start_time"
t.datetime "end_time"
end
We want to restrict all queries to the data based on Subscriptions.
create_table "subscriptions", force: :cascade do |t|
t.integer "device_id"
t.integer "user_id"
t.datetime "start_date"
t.datetime "end_date"
end
So effectively, data should not be accessed unless Subscriptions allows it.
I wanted to have this restriction done in one place (not by individual method to access it), so other people writing code still deal with the subscription restriction. I don't want to use a 3rd party gem as the data is very sensitive.
I thought the most obvious way to do this would be set a default_scope on the Data model, but I ran into several problems:
1) You can't access #user from the Data model. I got around this with an ugly hack to access User.current from the model.
2) I can't use current_user since we have admins that pose as other users, and need to see the data as they would. Used the same workaround as #1
3) There can be multiple subscriptions with different dates for the same user and device. There isn't any way to do an ActiveRecord Query that I know of that can account for two separate date ranges (I got around this by using arel_table).
4) This makes all of the methods written that query the data to fail when I'm in the rails console (and theoretically through an API interface). I got around this by ignoring the default scope if there is no User.current, but obviously that's a security concern.
This is my current implementation (in psuedocode):
query = []
Subscription.each do |s|
if (User.current.id == s.user_id)
temp = Data.all.where(data[:start_time] >= s.start_date).where(data[:end_time] <= s.end_date).where(data[:device_id] == Device.find_by(id: s.device_id).device_guid)
end
end
query += temp
end
default_scope { query }
I put this code just directly into the model. I didn't think it would work, but it does... sometimes. It acts oddly, sometimes completely restricting a user when it shouldn't, although it hasn't (yet) given somebody access to data they should not have.
I'm looking to redesign this from the ground up, and do it the best way. I'm open to any suggestions (except 3rd-party, sorry) on how best to approach it. I don't mind if it takes a lot of work to implement, I'd rather do it right this time. Any account for expandability (such as if we implemented an API for remote queries) would be a great feature as well, though we don't currently need it.
Just to reiterate, I insist that this restriction be "automatic" to all queries, so that if a front coder for instance writes:
<%= #user.device.find(1).data.all.count %>
in a view, it won't show any data the user doesn't have a subscription for.
Thanks for any suggestions. Sorry for the long wall of text, this has been a rather perplexing problem.

How does Backbone.js save data in my Rails model?

I'm using Rails together with Backbone and I've been learning a lot, but I'm having a problem I can't wrap my head around.
I have two models, a User and a Movie model.
ActiveRecord::Schema.define(version: 20141016152516) do
create_table "movies", force: true do |t|
t.string "title"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
create_table "users", force: true do |t|
t.string "name"
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
t.string "password_digest"
t.string "remember_digest"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
end
The User model has_many movies and the Movies model belongs_to users.
I've been using Backbone collections to add a movie to the view of the User like this,
class Movieseat.Collections.Movieseats extends Backbone.Collection
url: '/api/movies'
defaults:
title: ""
Through my Index view
class Movieseat.Views.MovieseatsIndex extends Backbone.View
template: JST['movieseats/index']
initialize: ->
#collection.on('update', #render, this)
#collection.on('add', #appendEntry, this)
render: ->
$(#el).html(#template())
#collection.each(#appendEntry)
this
events: ->
"click li": "addEntry"
addEntry: (e) ->
movie_title = $(e.target).text()
#collection.create title: movie_title
appendEntry: (entry) ->
view = new Movieseat.Views.Entry(model: entry)
$('#entries').append(view.render().el)
And this is my Entries view
class Movieseat.Views.Entry extends Backbone.View
template: JST['movieseats/entry']
className: 'movie-frame'
render: ->
$(#el).html(#template(entry: #model))
this
This is all working fine. I've got a few movie titles as static text on my page. When a user clicks on a movie title it gets added to the view. But the thing I don't understand is, where does the movie title get saved? And how can I find it using the Rails console?
I tried doing a Movie.all in the console which shows all the movies in the Movie model I made when I used a form to add movies to the model. It does not show the movie titles of the static titles. But if remove has_many :movies from the User model the app crashes.
So what's the connection between User, Movies and Rails and Backbone?
In modern Javascript in general all communication between the server (Rails) and the client (Backbone) happens via AJAX (although sometimes form submissions are also used in addition). In Backbone specifically, AJAX requests revolve mainly around two methods: fetch and save (other methods such as remove can also trigger AJAX). fetch takes data from Rails and brings it in to Backbone, while save does the reverse and brings data from Backbone in to Rails.
In order for that to work with your Movie model you need:
a Rails endpoint (eg. example.com/api/movie) on your server that expects to be given JSON for a Movie and saves it as a Rails record
a Backbone.Model with a url property that points to the Rails endpoint
If you have these two and you call save on your Movie Backbone.Model the model will POST its attributes, in JSON form, to the model's URL, at which point your Rails endpoint can receive that JSON and store it in your database.
Similarly when you call fetch on that Movie model, Backbone will make an AJAX request to that same URL, only instead of a POST it will be a GET request. The Rails endpoint in this case should return the JSON representation of the Movie, which Backbone will then convert to model attributes.
Hope that helps.
P.S. Since User is just another model like Movie, everything I said about Movie applies equally to User.

how do you integration OmniAuth (Twitter) to an existing user model?

I am using OmniAuth and I'm able to get the callback successfully working.
When I browse to /auth/twitter and log in using Twitter, it leads me back to /auth/twitter/callback with unformatted text that has user ID, profile image URL, screen name, etc.
I haven't done anything with this output yet and that's where I need help.
I already have an existing authentication system and I want to keep everything the way it is. I am looking to grab from Twitter just the screen name, their oauth_token, their oauth_token_secret and their email address (if possible) to create a new account on my site/login to my site. If I want to keep the exact same behavior of my current website and just want to allow people to login/register using Twitter, how can I do that?
Here's my already existing user table
create_table "users", :force => true do |t|
t.string "name"
t.string "email"
t.timestamp "created_at", :null => false
t.timestamp "updated_at", :null => false
t.string "password_digest"
t.string "remember_token"
Can I add the oauth_token and oauth_token_secret columns to this table and make it work that way?
You can. Check Ryans railscasts.com/episodes/241-simple-omniauth. Also be aware that twitter does not return email. You may also need to change user model to allow null for password etc, if you are planning to allow login either with twitter or with traditional user/pass (you may need to work on validation part if the user chose the later)

Separating multiple models in Rails form submission

Specifically, I have a form for creating a User. One of the fields is Group, which is a separate model. During the User#create action I call Group.find_or_create_by_name to check and see if the Group already exists by pulling out params[:user][:group], and create the Group if it doesn't exist.
But, when I create the User, I can't pass params[:user], because params[:user][:group] is not a group, it's a String. This would be a lot easier if I could supply params[:user] and params[:group] to my controller, instead of everything bundled under a single variable, but I don't know how to do that.
Relevant code:
User#create
#group = Group.find_or_create_by_name(params[:user][:group])
#group.save!
#user = #group.users.build(params[:user])
Partial User Schema
create_table "users", :force => true do |t|
t.string "name", :default => "", :null => false
t.string "email", :null => false
t.integer "group_id"
Partial Group Schema
create_table "groups", :force => true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
Params Dump from form submission
{"commit"=>"Register",
"authenticity_token"=>"x1KgPdpJop5H2NldsPtk0+mBDtrmpM/oNABOxjpabIU=",
"utf8"=>"✓",
"user"=>{"name"=>"aName",
"group"=>"aGroupName",
"password_confirmation"=>"[FILTERED]",
"password"=>"[FILTERED]",
"email"=>"anEmail"}}
The best explanation I've run across is this question.
I don't really want to start rearranging the params has inside of my controller as that really makes me queasy, and seems like terrible programming. Which really makes me question whether I've got a bigger problem.
If it goes against Rails conventions to do this, what then is the best way to do it, and why should I not?
I don't think the proper way to do this here is to build the user off the group. That'd be appropriate if you were in an action of the GroupsController that, for example, added new users to a group. I think the best approach here would be to do the following:
#group = Group.find_or_create_by_name(params[:user][:group])
#group.save!
#user = User.new(params[:user])
#user.group_id = #group.id
#user.save
Since you're in the new action of the UsersController, it seems more fitting to be creating a new user instead of building it off an association and then adding that user to the group that either already existed or was just created.
Does that make sense?
So, I'm going to spell out everything I've learned, in case other people are having trouble understanding this like I did. Also, if I get anything wrong here, please correct me.
If you're using a form that creates multiple model instances at once (preferably associated ones), you first need to use the helper accepts_nested_attributes_for in your model definition (probably right underneath your declared associations). The reason for this is it creates a setter method that knows how to write that type of associated model. (Note: you can also define this method yourself in your main model). Once you've done that you can nest a fields_for inside of a form_for, and Rails will know how to make the proper assignments.
I initially thought that accepts_nested_attributes_for was referring to nested resources, which is definitely not the case. If you're looking for more information, refer to section 11.8.3 (pp 343-347) of The Rails 3 Way.

Resources