Rails: build for difference between relationships - ruby-on-rails

A doc has many articles and can have many edits.
I want to build an edit for each article up to the total number of #doc.articles. This code works with the first build (i.e., when no edits yet exist).
def editing
#doc = Doc.find(params[:id])
unbuilt = #doc.articles - #doc.edits
unbuilt.reverse.each do |article|
#doc.edits.build(:body => article.body, :article_id => article.id, :doc_id => #doc.id)
end
end
But when edits already exist it'll keep those edits and still build for the #doc.articles total, ending up with too many edits and some duplicates if only one article was changed.
I want to put some condition against :article_id which exists in both edits and articles in to say (in pseudocode):
unbuilt = #doc.articles - #doc.edits
unbuilt.where('article_id not in (?)', #doc.edits).reverse.each do |article|
#doc.edits.build(...)
end
Any help would be excellent! Thank-you so much.

You are doing something weird here:
unbuilt = #doc.articles - #doc.edits
You probably want this instead
unbuilt = #doc.articles - #doc.edits.map(&:article)
This works if #doc.articles and #doc.edits are small collections, otherwise a SQL solution would be preferred.
-- EDIT: added explanation --
this piece of Ruby
#doc.edits.map(&:article)
is equivalent to
#doc.edits.map do |edit| edit.article end
the previous one is much more compact and exploits a feature introduced in ruby 1.9
It basically takes a symbol (:article), calls on it the 'to_proc' method (it does this by using the '&' character). You can think of the 'to_proc' method as something very similar to this:
def to_proc
proc { |object| object.send(self) }
end
In ruby, blocks and procs are generally equivalent (kindof), so this works!

Related

rubocop string interpolation and size condition

Before I except these two methods I wanted to see if anyone in the community had a better idea to structure these and make the cops pass. The first one with to_s seems a bit crazy too. I was thinking of refactoring the other method but that would be a single line or two.
Thoughts?
Code Examples One:
def destroy(resource_name, id)
delete "#{resource_name.to_s.pluralize}/#{id}"
end
Code Examples Two:
def all_products
products_map = fetch(:products).map { |x| [x['id'], x] }.to_h
variants = fetch :variants
variants.group_by { |x| x['product']['resource']['id'] }.to_a.map do |product_id, product_variants|
product.merge 'variants' => product_variants if product == products_map[product_id]
end.compact
end
For Code example One, maybe this can be used:
delete [resource_name.to_s.pluralize, id].join('/')
For Code example Two, yes you definitely need to refactor it.
Maybe you need to create a separate method that does all the grouping and merging, etc. for the variants part.
I am not sure if this is a good practice, but you can create a private method for it.

How to stream large xml in Rails 3.2?

I'm migrating our app from 3.0 to 3.2.x. Earlier the streaming was done by the assigning the response_body a proc. Like so:
self.response_body = proc do |response, output|
target_obj = StreamingOutputWrapper.new(output)
lib_obj.xml_generator(target_obj)
end
As you can imagine, the StreamingOutputWrapper responds to <<.
This way is deprecated in Rails 3.2.x. The suggested way is to assign an object that responds to each.
The problem I'm facing now is in making the lib_obj.xml_generator each-aware.
The current version of it looks like this:
def xml_generator(target, conditions = [])
builder = Builder::XmlMarkup.new(:target => target)
builder.root do
builder.elementA do
Model1.find_each(:conditions => conditions) { |model1| target << model1.xml_chunk_string }
end
end
end
where target is a StreamingOutputWrapper object.
The question is, how do I modify the code - the xml_generator, and the controller code, to make the response xml stream properly.
Important stuff: Building the xml in memory is not an option as the model records are huge. The typical size of the xml response is around 150MB.
What you are looking for is SAX Parsing. SAX reads files "chunks" at a time instead of loading the whole file into DOM. This is super convenient and fortunately there are a lot of people before you who have wanted to do the same thing. Nokogiri offers XML::SAX methods, but it can get really confusing in the disastrous documentation and syntactically, it's a mess. I would suggest looking into something that sits on top of Nokogiri and makes getting your job done, a lot more simple.
Here are a few options -
SAX_stream:
Mapping out objects in sax_stream is super simple:
require 'sax_stream/mapper'
class Product
include SaxStream::Mapper
node 'product'
map :id, :to => '#id'
map :status, :to => '#status'
map :name_confirmed, :to => 'name/#confirmed'
map :name, :to => 'name'
end
and calling the parser in is also simple:
require 'sax_stream/parser'
require 'sax_stream/collectors/naive_collector'
collector = SaxStream::Collectors::NaiveCollector.new
parser = SaxStream::Parser.new(collector, [Product])
parser.parse_stream(File.open('products.xml'))
However, working with the collectors (or writing your own) and end up being slightly confusing, so I would actually go with:
Saxerator:
Saxerator gets the job doen and has some really handy methods for traversing into nodes that can be a little less complex than sax_stream. Saxerator also has a few really great configuration options that are well documented. Simple Saxerator example below:
parser = Saxerator.parser(File.new("rss.xml"))
parser.for_tag(:item).each do |item|
# where the xml contains <item><title>...</title><author>...</author></item>
# item will look like {'title' => '...', 'author' => '...'}
puts "#{item['title']}: #{item['author']}"
end
# a String is returned here since the given element contains only character data
puts "First title: #{parser.for_tag(:title).first}"
If you end up having to pull the XML from an external source (or it is getting updated frequently and do you don't want to have to update the version on your server manually, check out THIS QUESTION and the accepted answer, it works great.
You could always monkey-patch the response object:
response.stream.instance_eval do
alias :<< :write
end
builder = Builder::XmlMarkup.new(:target => response.stream)
...

Clean url with rails

I can't use any of the gems for creating clean Urls in rails. Instead I am rolling out my own implementation. I have created the following entry in routes.rb
match "/:slug" => "cleanurls#index"
Where cleanurl is a controller for handling all such requests. In the cleanurl controller:
class CleanurlsController < ApplicationController
def index
slug = params['slug']
url = Url.where(:slug => slug).first
case(url.url_type)
when 'profile'
user_id = url.id.to_i
#profile = Profile_info.getProfileDetails(user_id)
render '/profiles/index'
end
end
end
I have created the table urls which stores the slug,id (as relevant) and the type of page. Right now I have only the profile page to deal with but in the future I will have different types of pages with clean urls.
My first Question:
1) Is this implementation the right approach? And is this okay from a performance perspective given the tables have all the right indexes.
I am making the profile url like this:
def self.makeProfileUrl(id,name)
name = name.strip.titleize
extension = User.where(:name => name).count - 1
slug = name.split(" ").join("-")
if extension != 0
slug += "-#{extension}"
end
Url.create(:slug => slug, :id => id.to_i, :url_type => 'profile')
end
I am using extension to append a count in case their are users who share the same name.
Question:
Is this the right way to create the slug and ensure it being unique? Fetching the count of a name from the other table does not seem right.
Answering the question #1:
I don't know the details of what's your overall goal, but if you'd like
to have such URLs that are based on records from the database - then yes: it's
a good approach.
Answering question #2 (regarding slugs):
I'd rather use something much more elaborate and well tested like:
https://github.com/norman/friendly_id
My 50 cents about some other things:
Is this one of your first projects in Ruby/Rails? If so - congratulations! :)
I'm asking because I noticed that you're using camel case here and there...
Also:
user_id = url.id.to_i
Why do you call this #to_i method here? Did you set up this id as a string
or something?
Hope this helps

Fill array with nils for when nothing returned from query in ruby (RoR)

I have a model called foo with a date field.
On my index view, I am showing a typical "weekly view" for a specified week. To put the data in my view, I loop through each day of the specified week and query the data one day at time. I do this so that I can make sure to put a NIL on the correct day.
foos_controller.rb
for day in 0..6
foo = Foo.this_date(#date+day.days).first
#foos[day] = foo
end
index.html.haml
- for day in 0..6
%li
- if #foos[day].nil?
Create a new foo?
- else
Display a foo information here
Obviously, there's a lot of things wrong here.
I should find someone smart member to tell me how to write a good query so that I only have to do it once.
I should not have any if/else in my view
My goal here is to either show the content if the it is there for a particular day or show a "create new" link if not.
thanks for the help in advance!!
First, I have no idea what this_date actually does, but I'll assume it's retrieving a record with a specific date from your datastore. Instead of doing 7 queries, you can condense this into one using a date range:
Foo.where(date: (#date..(#date + 6.days)))
You can tack on a .group_by(&:date) to return something similar to the hash you are manually constructing, but using the actual dates as keys instead of the date offset.
To iterate over the dates in the view, I would recommend using Hash#fetch, which allows you to define a default return when a key is not present, e.g:
hash = { :a => 1, :b => 2 }
hash.fetch(:a){ Object.new } #=> 1
hash.fetch(:c){ Object.new } # #<Object:...>
The question now is what object to substitute for nil. If you want to avoid using conditionals here, I'd recommend going with the NullObject pattern (you could involve presenters as well but that might be a bit overkill for your situation). The idea here is that you would create a new class to substitute for a missing foo, and then simply define a method called to_partial_path on it that will tell Rails how to render it:
class NullFoo
def to_partial_path
"null_foos/null_foo"
end
end
You'll need to create partials at both app/views/foos/_foo.html.erb and app/views/null_foos/_null_foo.html.erb that define what to render in each case. Then, in your view, you can simply iterate thusly:
<% (#date..(#date + 6.days)).each do |date| %>
<%= render #foos.fetch(date){ NullDate.new } %>
<% end %>
Is this appropriate for your situation? Maybe it's also a bit overkill, but in general, I think it's a good idea to get in the habit of avoid nil checks whenever possible. Another benefit of the NullObject is that you can hang all sorts of behavior on it that handle these situations all throughout your app.

How to create a sortable interface with 'acts as nested set' in RubyOnRails

I've been implementing some nice interactive interfaces that can sort lists in my m rails app for models that use acts_as_list. I have a sort function that gets called and sets the position for each record afterr each drag and drop using the sortable_element script.aculo.us function.
This is an example of the controller action that handles the sort after the drag and drop completes:
def sort
params[:documents].each_with_index do |id, index|
Document.update_all(['position=?', index+1], ['id=?', id])
end
end
Now I am trying to do this same thing with a model that is a nested set (acts_as_nested_set). An example of the type of interface interaction: http://script.aculo.us/playground/test/functional/sortable_tree_test.html
I am stuck on how to write the controller action to handle the sort when the drag and drop completes.
I've added the :tree=>true parameter to the sortable _element function so far which appears to send a list of hashes but it seems that I am still missing information about the entire nested order....
I was certain this has been done before and didn't want to try to reinvent the wheel, but I can't seem to find any examples of the controller action <-> view with js function setup to handle a sortable acts_as_nested_set
Any help with creating an interactive sortable nested set in rubyonrails would be appreciated!
Thanks,
John
a good solution with ONE sql-query from http://henrik.nyh.se/2008/11/rails-jquery-sortables
# in your model:
def self.order(ids)
update_all(
['ordinal = FIND_IN_SET(id, ?)', ids.join(',')],
{ :id => ids }
)
end
see example app here - http://github.com/matenia/jQuery-Awesome-Nested-Set-Drag-and-Drop
It's a hacky way of doing it, but its basically, sort first, then save order.
Uses nestedsortables, serializelist, and 2 actions to traverse the tree
PS: I know this question is over a year old but hoping that the link above helps someone else coming here.
edit: added Rails3 example with some slightly cleaner code.
Just found this:
sortable_element_for_nested_set on github
Looks like it'll do the job, however I'm having some bugs while trying to implement it. It basically makes the javascript return the id of the element that was moved, then goes through the elements and returns its new parent, left and right values. Can't believe it's taken this long for something like this to be written! Lucky it was just when I needed it :)
Here's a snippet of code from my project that does the trick:
http://gist.github.com/659532
def reorder_children(ordered_ids)
ordered_ids = ordered_ids.map(&:to_i)
current_ids = children.map(&:id)
unless current_ids - ordered_ids == [] && ordered_ids - current_ids == []
raise ArgumentError, "Not ordering the same ids that I have as children. My children: #{current_ids.join(", ")}. Your list: #{ordered_ids.join(", ")}. Difference: #{(current_ids - ordered_ids).join(', ')} / #{(ordered_ids - current_ids).join(', ')}"
end
j = 0
transaction do
for new_id in ordered_ids
old_id = current_ids[j]
if new_id == old_id
j += 1
else
Category.find(new_id).move_to_left_of(old_id)
current_ids.delete(new_id)
end
end
end
end
You call it on the parent, and it'll sort the children.
You just pass in the value that you get from Sortable, like so:
def reorder
#category.reorder_children(params[:categories])
render :nothing => true
end
Hope this helps.
//Lars
the_sortable_tree
Sortable Nested Set for Rails 3.1+
Dreaming about Drag and Drop for Nested Sets? It’s should be with JQuery?
Here’s the solution!

Resources