Rails 3 Autocomplete Across Multiple Models - ruby-on-rails

So I found some great info on Autocomplete for Rails 3, it looks really easy to use. But I have a use case that doesnt fit and I need some advice.
I want to give the user the ability to add Products and Services to an Invoice through a simple form. I'd like them to be able to type into the Item field and have it autocomplete from both Product.name and Service.name as a single set.
I'm thinking of trying to write a parent model that overlays all three, but I still don't think that solves my problem since I can't use a function in the autocomplete definition from what I understand.
Any advice on how I might try to accomplish this? Even with the simple search examples that are out there they seem to be restricted to a single model.

If you're not opposed to introducing Redis into the mix, have a look at https://github.com/seatgeek/soulmate -- From the README:
Soulmate is a tool to help solve the common problem of developing a fast autocomplete feature. It uses Redis's sorted sets to build an index of partially completed words and the corresponding top matching items, and provides a simple sinatra app to query them. Soulmate finishes your sentences.
Soulmate was designed to be simple and fast, and offers the following:
Provide suggestions for multiple types of items in a single query (at SeatGeek we're autocompleting for performers, events, and venues)
Results are ordered by a user-specified score
Arbitrary metadata for each item (at SeatGeek we're storing both a url and a subtitle)
An item is a simple JSON object that looks like:
{
"id": 3,
"term": "Citi Field",
"score": 81,
"data": {
"url": "/citi-field-tickets/",
"subtitle": "Flushing, NY"
}
}
Where id is a unique identifier (within the specific type), term is the phrase you wish to provide completions for, score is a user-specified ranking metric (redis will order things lexicographically for items with the same score), and data is an optional container for metadata you'd like to return when this item is matched (at SeatGeek we're including a url for the item as well as a subtitle for when we present it in an autocomplete dropdown).
See Soulmate in action at SeatGeek.
If nothing else, maybe it'll give you some ideas on how to structure the data in a way that makes sense.
I did not write or have anything to do with soulmate. It's just a library I discovered when trying to solve a similar problem. Hope it helps!

If client-side autocomplete is an option (e.g. you have few products and services), you could go with JQuery autocomplete:
controller:
#keys = #categories.map { |x| x.name } + #entries.map { |x| x.description }
#autocomplete_categories = #keys.to_json.html_safe
view:
<script type="text/javascript">
$(document).ready(function() {
var data = <%= #autocomplete_categories %>;
$("#auto").autocomplete( { source: data } );
});
</script>
<input type="text" name="auto" id="auto" />

Related

Multi-level pagination in 11ty?

All the documentation I've found on Eleventy pagination has to do with a single level, and I've got that working pretty well.
Take a collection (ex. of tags) and create one page each
Take a collection of posts and put 10 on each page
and so on.
What I'd like to do now is combine them: loop over all the tags, and then paginate each tag's collection so if I use some tags a lot, they don't end up with 50 posts on the same page. Basically the way WordPress generates paginated views for each tag.
Something like this: (simplified, I know filters need to be in there)
pagination:
data: collections
size: 1
alias: tag
pagination:
data: tag
size: 10
alias: tagpost
Though that didn't seem to work.
Is there some way to do multi-level pagination, or would I need to take some other approach for the outer loop?
That has been the thorn in my side from the beginning. There is an issue post on 11ty's GitHub explaining how to flatten the data and then paginate using Javascript, but then you'll lose all the nice pagination features already built in 11ty.
Another big issue is how to get Tags dynamically from the API. If you need a single template file for each Tag, paginated or not, you have to do it manually for each tag. So if there's a new tag coming from CMS through API 11ty has no way to handle it automatically.
There are a zillion tutorials for 11ty and not a single one explaining how to do two things that literally every site needs to have.
Good luck with it.
BTW, that being said, I love 11ty, I really do.
You can build a custom collection
config.addCollection("tagpages", function(api) {
// Map over api.getAll() to build TagPage or TagGroup that contains
// an array of Pages
});
that has the format:
TagPage {
id: Number;
tag: string;
posts: post[];
}
Then the front matter:
---
Pagination:
Data: collections.tagpages
Size: 1
Alias:tagpage
Permalink: /{TagPage.tag}/{TagPage.id}
---
Display your tag and X posts.
You could have your config customize by tags and number of items per page.
config.addCollection("tagpages", buildTagPages(tag, numItems));

Cities Text Field with Auto Complete in Rails

I've searched a little about some ways to add a second field after States, to set a City based on it. I saw two options first : observe_field (From prototype) and jQuery, both with a select box. The fact is that I don't know JavaScript, and am in a hurry to set this... Well, I thought a text field with auto completion would be better, and maybe easiest to do.
Here we go : I have an model Uf and City, and both is populated with data. City has a "belongs_to Uf". The question is : How can I set something like this? Is there a way to do this with only ruby and rails?
To do this you'll want to have an AJAX action that responds to some search string query sent each time the user enters text. This action would respond to the view with data containing the result set of the search.
Here's a very simple example:
City Controller:
def search
term = params[:term]
cities = City.where('name LIKE ? OR code LIKE ?', "%#{term}%", "%#{term}%").order(:name)
render json: cities.map { |city|
{
id: city.id,
label: city.inspect,
value: city.full_name
}
}
end
Upon receiving a response, the dropdown would be populated with the results of the search. Selecting a value would set the field and store the matching id for the city in a hidden field for when the form is submitted.
I'd consider exploring the jQuery autocomplete Gem. If you're using Rails, there's a full-stack version here https://github.com/bigtunacan/rails-jquery-autocomplete which should be good to learn from.

How can we let our web designers code complex web apps without a server side language?

In my web development company, we have many designers who have the following dev knowledge and experience:
Client-side web languages (HTML, CSS, JavaScript)
Basic database design (how to create tables, relationships, etc), and
Basic SQL
The problem is that those guys don't know a server side language, such as C#. We want to give them an option to create web apps with their current knowledge.
Basically, they should be able to create data grids, add/edit forms, etc, without the use of a full server side language such as C# or PHP.
We don't want to create new huge new technologies here. We want a simple solution, that is easy to understand and maintain and possibly somewhat open source.
We thought about adding an extension to ASP.NET Razor, that looks something like this:
#DB(table="pages", where="id > 15")
<ul>
#while (row.Next()) {
<li>#row.Name</li>
}
</ul>
Is this a good solution, in terms of performance, design patterns, code style and security (SQL Injections)?
Is there anything open source for this?
Do you have any other ideas?
Here are some examples for a simple app that uses this style:
/apps/pages/index.html
#(DB.Select(table: "pages",
where: "id = ? and date > ?",
params: new[] { Request.QueryString["ID"], Request.QueryString["Date"] }))
<html>
<body>
<ul>
#foreach (var row in Model) {
<li>#row.Name</li>
}
</ul>
</body>
</html>
/apps/pages/create.html
#(DB.InsertOnPost(table: "pages",
fields: "name, description, date",
values: "?, ?, Now",
params: new[] { Request.Form["Name"], Request.Form["Description"] }))
#RedirectOnPost("/pages")
<form action="/pages/create" method=”post”>
<div>
<label for="name">Name</label>
<input type="text" name="name" />
</div>
<div>
<label for="description">Description</label>
<textarea name="description"></textarea>
</div>
</form>
/apps/pages/edit.html
#(DB.UpdateOnPost(table: "pages",
set: "name = ?, description = ?",
where: "id = ?",
params: new[] { Request.Form["Name"], Request.Form["Description"],
Request.QueryString["ID"] }))
#RedirectOnPost("/pages")
#(DB.Select(table: "pages",
where: "id = ?",
params: new[] { Request.QueryString["ID"] })
<form action="/pages/edit" method="post">
<div>
<label for="name">Name</label>
<input type="text" name="name" value="#Model.Name" />
</div>
<div>
<label for="description">Description</label>
<textarea name="description">#Model.Description</textarea>
</div>
</form>
IMHO
I have to say, UI guys are UI guys for a reason. This is where communication and understanding requirements comes in. Both parties should have a clear grasp on what needs to be done to get the end product complete. It should never be a concern to the guys on the front end how to get the information, just like you should never have to worry about which style sheet is being used when you're writing a service or making a view model.
If it's a matter of too much work load on the back end, it may be time to look at just cleaning things up or making things more concise, but not handing off DB access to someone who doesn't know what they're doing.
If you have this much concern over how they can and will access the data, shouldn't that be a red flag to start? It seems to me you'd rather invest weeks (and it will be weeks since you'll have to sandbox everything) in making it safely accessible instead of just plumbing it through for them.
Do requirements ever get you there 100%? No. You will have some back and forth (either UI needs something, or you do) but that's the name of the game. The good part is most of the time you can work in tandem (they are working on how it will look while you give them the pieces they need to wire it up).
I think you have several options here.
Get them to use node.js so they can use their JavaScript skills.
Get them to use Ruby on Rails, they will probably appreciate using a new technology, and you get a great framework to boot.
But probably more importantly, you should ask them what they think. You might be surprised.
Based on the data above, the fundamental conflicting datum is that you want them to be able to program something using a technology that they do not understand. In my experience, this is not a good idea. If no one on your team understands the ins and outs of server side development, you will end up with a low-quality product.
That said - the situation you are describing is quite common and this is thus a good question. My suggestion is that you should reorganize your team and workflow rather than trying to see how you can solve this by using a "simpler" technology (I put it in quotes because often attempts to make something simpler work for one set of problems, but as the requirements change those simplifications just start getting your way and it's then no longer simpler.)
Some specific options:
Hire one additional person with "backend coding" skillz and have them deal with all of the back end work. (Note that it's not all that important which backend language we're talking about - C#, PHP, Java, Python, etc. - the same principle applies - it's a different/more involved set of technical skills required as compared to doing UI scripting. And on the other hand, this person doesn't require any of the graphic design sense which a UI developer should have.)
Or, designate one of your existing team to bone up on these skills and while it will take them for them to learn - you at least have one specific person who will eventually be the guy who knows all about it.
Whoever this person is, should be in charge of selecting which framework to use. And they should do it with as much of an understanding of the particular problems specific to your project, etc.
Additionally, there are some technical approaches that could provide better separation. One idea is you could write API calls that provide JSON data which the UI people can then access using JQuery or similar tools. This way your backend code is all in one file and just returns data - making it more clear who is doing what. Example:
/api/some-data.php: (the "backend guy" edits this)
// do some work to get some data
$myData = ...;
// dump it out as JSON
print json_encode($myData);
/some-page-that-uses-data.html: (the "UI guy" edits this)
<!-- simple jQuery AJAX call -->
<script>
$(document).ready(function() {
$.ajax({ url:'/api/some-data.php' }).done(data) {
// do something to populate the page with the data...
};
});
</script>
(NOTE: You can use JSONP or CORS to get around cross domain problems if you run into that.)
Overall - instead of trying to have UI guys do work in an area they are not familiar with, separate the work so that people can specialize and each one does a good job on his/her part. You don't need a bunch of people to be server-side ninjas - but you need at least one...

Rails + Ajax - update view based on combo-box choice?

Just a heads-up, I'm a total beginner as far as ajax is concerned and am just trying to find my way around it, so please bear with me :)
I have a View with a combo box in it (generated through a collection_select) and I display some data on the side of that form that essentially gives more details about the user's choice.
What I'd like to achieve is to be able to change that description on the side as soon as the customer makes a different selection in the combo. Basically, figure out what the current choice is, request data from the model, display returned data on the screen.
What's the simplest / most elegant way of achieving that? I think understanding the process would be a good launching ramp into the rest of the async View world for me.
Thanks!
I don't think ajax is necessary in this situation. Simply preload all the description values and store them in hidden divs:
<div class="description" id="choice1">
description for choice1 ...
</div>
...
Then hide them all:
// somewhere in your stylesheet
div.description {
display: none;
}
And finally on client side, bind to wanted events of the combo box:
// checkout documentation for other events, like select
$('some_id').change(function(data){
$('div.description').css('display', 'none');
var id = // get information about selected choice and figure out the id of description
$('div.description#' + id).css('display', 'block');
});
Chekout documentation for exact details but the main idea should be clear. In this way, you'll save yourself some unnecessary requests to your server.

How do I let data in 1 column power the content of the 2nd column in Rails 3?

Say I have a list of users in the left column, generated by <%= current_user.clients %> and the second column is empty by default.
However, when the user clicks on one of the links, the second column becomes populated with the projects associated with that user - without the entire page being reloaded (i.e. using AJAX).
I would also like to continue the process, so when they click on a project from that user, the third column is populated with other things (e.g. the name of images, etc.).
How do I accomplish this in Rails?
I assume you are using Rails 3 and jQuery (I'm not well-versed in prototype). It's easy to switch jQuery for prototype in Rails 3: https://github.com/rails/jquery-ujs
For the link:
Something
Using JavaScript and jQuery, write a function that sucks in links of class first_column_link (please rename to something more reasonable, by the way):
$(function() {
$('.first_column_link').bind('click', function() {
$.getJSON('/clients/' + $(this).attr('data-client-id'), function(data) {
// Populate the second column using the response in data
});
});
});
This doesn't work on browsers that don't support or have otherwise disabled JavaScript. Gracefully degrading would likely be a good idea, but without more context, I can't advise you how to best do it.
<%= link_to_remote current_user.clients, go_to_controller_path %>
Proceed from there.
go_to_controller_path routes to an action which renders javascript to update the 2nd column (probably with a partial).

Resources