Rails gem multiple NOSQL datastores - ruby-on-rails

I'm creating my own gem and I want to enable user to save data to multiple NOSQL data stores. How can I make this happen? Where should I place the necessary files?

I've done the same thing in my gem. I think you have created an App folder in your gem/engine. Create another folder called "backend" and create classes for each datastore. For my case I created a seperate for Mongo and Redis
module Memberfier
class RedisStore
def initialize(redis)
#redis = redis
end
def keys
#redis.keys
end
def []=(key, value)
value = nil if value.blank?
#redis[key] = ActiveSupport::JSON.encode(value)
end
def [](key)
#redis[key]
end
def clear_database
#redis.keys.clone.each {|key| #redis.del key }
end
end
end
module Memberfier
class MongoStore
def initialize(collection)
#collection = collection
end
def keys
#collection.distinct :_id
end
def []=(key, value)
value = nil if value.blank?
collection.update({:_id => key},
{'$set' => {:value => ActiveSupport::JSON.encode(value)}},
{:upsert => true, :safe => true})
end
def [](key)
if document = collection.find_one(:_id => key)
document["value"]
else
nil
end
end
def destroy_entry(key)
#collection.remove({:_id => key})
end
def searchable?
true
end
def clear_database
collection.drop
end
private
def collection; #collection; end
end
end

You may have already seen one of Uncle Bob's presentations on application architecture. If not, it's here. I'd recommend having a single boundary object that select models inherit from. That boundary object could have multiple CRUD methods such as find, create, delete. That boundary object could inherit from whatever NOSQL adapter you configure. Example/source: http://hawkins.io/2014/01/pesistence_with_repository_and_query_patterns/

Related

How to show the dependent records that will be deleted before destroying a record

Django admin shows you the dependent records that will be deleted when you delete a record as a confirmation.
Is there a way to do the same on Ruby on Rails?
I have been researching how to do it, but I am still looking for a way.
I couldn't find a gem, so I wrote this concern using association reflections:
module DependentDestroys
extend ActiveSupport::Concern
DEPENDENT_DESTROY_ACTIONS = %i[destroy delete destroy_async]
class_methods do
def dependent_destroy_reflections
#dependent_destroy_reflections ||= reflections.filter_map do |name, r|
r if DEPENDENT_DESTROY_ACTIONS.include?(r.options[:dependent])
end
end
end
def total_dependent_destroys
dependent_destroy_counts.sum { |r| r[1] }
end
def any_dependent_destroys?
dependent_destroy_counts.any?
end
# If you want all affected records...
def dependent_destroy_records
self.class.dependent_destroy_reflections.flat_map do |r|
relation = self.public_send(r.name)
if r.collection?
relation.find_each.to_a
else
relation
end
end
end
# If you only want the record type and ids...
def dependent_destroy_ids
self.class.dependent_destroy_reflections.flat_map do |r|
relation = self.public_send(r.name)
if r.collection?
relation.pluck(:id).map { |rid| [r.klass, rid] }
else
[[r.klass, relation.id]] if relation
end
end.compact
end
# If you only want counts...
def dependent_destroy_counts
self.class.dependent_destroy_reflections.filter_map do |r|
relation = self.public_send(r.name)
if r.collection?
c = relation.count
[r.klass, c] if c.positive?
else
[r.klass, 1] if relation
end
end
end
def dependent_destroy_total_message
"#{total_dependent_destroys} associated records will be destroyed"
end
def dependent_destroy_message
# Using #human means you can define model names in your translations.
"The following dependent records will be destroyed: #{dependent_destroy_ids.map { |r| "#{r[0].model_name.human}/#{r[1]}" }.join(', ')}"
end
def dependent_destroy_count_message
"The following dependent records will be destroyed: #{dependent_destroy_counts.map { |r| "#{r[0].model_name.human(count: r[1])} (#{r[1]})" }.join(', ')}"
end
end
Usage:
class User
include DependentDestroys
belongs_to :company
has_many :notes
has_one :profile
end
user = User.first
user.any_dependent_destroys?
# => true
user.total_dependent_destroys
# => 60
user.dependent_destroy_total_message
# => "60 associated records will be destroyed"
user.dependent_destroy_message
# => "The following dependent records will be destroyed: Note/1, Note/2, ..., Profile/1"
user.dependent_destroy_count_message
# => "The following dependent records will be destroyed: Notes (59), Profile (1)"
You can then use these methods in the controller to deal with the user flow.
With some improvements, options (like limiting it to the associations or modes (destroy, delete, destroy_async) you want) and tests, this could become a gem.

Ruby on Rails: Validate CSV file

Having followed the RailsCast on importing CSV (http://railscasts.com/episodes/396-importing-csv-and-excel), I am trying to validate that the file being uploaded is a CSV file.
I have used the gem csv_validator to do so, as documented here https://github.com/mattfordham/csv_validator
And so my model looks like this:
class Contact < ActiveRecord::Base
belongs_to :user
attr_accessor :my_csv_file
validates :my_csv_file, :csv => true
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |contact|
csv << contact.attributes.values_at(*column_names)
end
end
end
def self.import(file, user)
allowed_attributes = ["firstname","surname","email","user_id","created_at","updated_at", "title"]
CSV.foreach(file.path, headers: true) do |row|
contact = find_by_email_and_user_id(row["email"], user) || new
contact.user_id = user
contact.attributes = row.to_hash.select { |k,v| allowed_attributes.include? k }
contact.save!
end
end
end
But my system still allows me to select to import non-CSV files (such as .xls), and I receive the resulting error: invalid byte sequence in UTF-8.
Can someone please tell me why and how to resolve this?
Please note that I am using Rails 4.2.6
You can create a new class, let's say ContactCsvRowValidator:
class ContactCsvRowValidator
def initialize(row)
#row = row.with_indifferent_access # allows you to use either row[:a] and row['a']
#errors = []
end
def validate_fields
if #row['firstname'].blank?
#errors << 'Firstname cannot be empty'
end
# etc.
end
def errors
#errors.join('. ')
end
end
And then use it like this:
# contact.rb
def self.import(file, user)
allowed_attributes = ["firstname","surname","email","user_id","created_at","updated_at", "title"]
if file.path.split('.').last.to_s.downcase != 'csv'
some_method_which_handle_the_fact_the_file_is_not_csv!
end
CSV.foreach(file.path, headers: true) do |row|
row_validator = ContactCsvRowValidator.new(row)
errors = row_validator.errors
if errors.present?
some_method_to_handle_invaid_row!(row)
return
end
# other logic
end
end
This pattern can easily be modified to fit your needs. For example, if you need to have import on several different models, you could create a base CsvRowValidator to provide basic methods such as validate_fields, initialize and errors. Then, you could create a class inheriting from this CsvRowValidator for each model you want, having its own validations implemented.

Can't save record attributes to the database

class Contact < ActiveRecord::Base
attr_accessor :email
def initialize(data)
data.each { |k, v| send("#{k}=", v) }
end
end
In rails console
Contact.create!({"email"=>"foo#gmail.com"})
The record saved to the database has email as nil
Update:
The data is being passed in is JSON. I am getting all the data from the JSON and trying to save that into the database.
Did you try:
Contact.create!(email: "foo#gmail.com")
The email as a :symbol and no curly brackets?
Also, why are you initializing in your model?
With Mohamed El Mahallaway, I think your code setup could be improved (to negate initializing your model). I think you'll be better using the strong_params functionality of Rails:
#app/controllers/contacts_controller.rb
def new
#contact = Contact.new
end
def create
#contact = Contact.new(contact_params)
#contact.email = "foo#gmail.com"
#contact.save
end
private
def contact_params
params.require(:contact).permit(:email, :other, :params)
end
I may have miscalculated your competency with Rails, but this is the "Rails" way to save the correct data to your model :) You may to have a before_save method in your model to use the email attribute, considering it's a virtual attribute

How to test such class behavior in Rspec

I have a class which is responsible for dealing with some response from payments gateway.
Let's say:
class PaymentReceiver
def initialize(gateway_response)
#gateway_response = gateway_response
end
def handle_response
if #gateway_response['NC_STATUS'] != '0'
if order
order.fail_payment
else
raise 'LackOfProperOrder'
# Log lack of proper order
end
end
end
private
def order
#order ||= Order.where(id: #gateway_response['orderID']).unpaid.first
end
end
In payload from payment I've NC_STATUS
which is responsible for information if payment succeed and orderID which refers to Order ActiveRecord class byid`.
I would like to test behavior(in rspec):
If PaymentReceiver receives response where NC_STATUS != 0 sends fail_payment to specific Order object referred by orderID.
How you would approach to testing this ? I assume that also design could be bad ...
You have to make refactorization to remove SRP and DIR principles violations.
Something below I'd say:
class PaymentReceiver
def initialize(response)
#response = response
end
def handle_response
if #response.success?
#response.order.pay
else
#response.order.fail_payment
end
end
end
# it wraps output paramteres only !
class PaymentResponse
def initialize(response)
#response = response
end
def order
# maybe we can check if order exists
#order ||= Order.find(#response['orderID'].to_i)
end
def success?
#response['NCSTATUS'] == '0'
end
end
p = PaymentReceiver.new(PaymentResponse({'NCSTATUS' => '0' }))
p.handle_response
Then testing everything is easy.

Rail dirty and date changes

Have a column in orders called closed_date which is a DateTime field.
Using Dirty. trying to do if self.closed_date_changed? but it's not working. Do I have to do something special for tracking changes with Date Time fields?
EDIT
Using Rails 3.0.3, Ruby 1.9.2p136
Code in orders controller
def update
#order = Order.find(params[:id])
if #order.update_attributes(params[:order])
#order.close_order
end
end
end
In Model
include ActiveModel::Dirty
def close_order
if self.closed?
if self.closed_date_changed?
self.items.each do |item|
item.update_attribute(:created_at, self.closed_date)
end
end
else
self.update_attributes(:closed_date => Time.now, :closed => true)
self.items.each do |item|
item.update_attribute(:created_at => Time.now)
item.schedule_any_tasks
end
end
end
end
I think you mean something like:
def save_changes
if closed_date_changed?
# do something like save the modified data to a table
else
# do anything else
end
end
And the most important, don't forget to call this method on a before_save(update) callback.
Because the changes only remains while the actual record isn't saved.
Hope it helps!
def start_date_changed?
return true if self.start_date != self.start_date_was
return false
end
I have use was which checks for the value change..
Thanx

Resources