I'm trying to subscribe users to Mailchimp with Gibbon 2.2.4 with a generic subscribe method I've been using, and then shortly after I want to add in some extra fields to track the results of a quiz they took.
I want to store this data on Mailchimp because I'd like to manage the emails I send off directly from Mailchimp's dashboard.
The service I created to handle my subscriptions:
class MailchimpService
def subscribe(list_id,email,first_name)
GIBBON.lists(list_id).members.create({
body: {
email_address: email,
status: 'subscribed',
merge_fields: {
FNAME: first_name,
},
double_optin: false,
update_existing: true
}
})
end
def subscribe_to_quiz(first_name, email, user_id, quiz_id)
list_id = ENV['QUIZ_MAILCHIMP_LIST_ID']
if subscribe(list_id,email,first_name)
attempt = QuizAttempt.where("user_id = ? AND quiz_id = ?", user_id, quiz_id).last
correct = attempt.correct_answer_count
total = attempt.questions_answered
successful = attempt.successful?
send_quiz_results(list_id, email, correct, total, successful)
end
end
def send_quiz_results(list_id, email, correct, total, successful)
GIBBON.lists(list_id).members(email).upsert(
body: {
email_address: email,
status: 'subscribed',
merge_fields: {
correct_answers: correct,
total_answers: total,
successful: successful
},
update_existing: true
})
end
end
In subscribe_to_quiz, I'm subscribing the user to my quiz_list in Mailchimp. The values of the fields I'm updating here are irrelevant, but I think they're quite explanatory. When I try to run my upsert statement in send_quiz_results, I get the following error:
the server responded with status 400
#title="Member Exists",
#detail="foo#bar.baz is already a list member. Use PUT to insert or update list members.",
#body={"type"=>"http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/", "title"=>"Member Exists", "status"=>400, "detail"=>"foo#bar.baz is already a list member. Use PUT to insert or update list members.", "instance"=>""},
#raw_body="{\"type\":\"http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/\",\"title\":\"Member Exists\",\"status\":400,\"detail\":\"foo#bar.baz is already a list member. Use PUT to insert or update list members.\",\"instance\":\"\"}",
#status_code=400
I have no clue why it won't let me do this... It seems like it's referencing a create statement, but the extracted source for the error references my upsert statement.
I know I'm using the corresponding PUT verb for Gibbon, since the following is taken straight from the documentation:
Of course, body is only supported on create, update, and upsert calls. Those map to HTTP POST, PATCH, and PUT verbs respectively.
I have no clue why this isn't working... I've tried taking out the other fields and just simply throwing in the ones I'm updating. I've also tried running it straight from the terminal to make sure nothing's overlapping.
The MailChimp API docs show that when updating a member you must provide the member's subscriber_hash, which the MD5 hash of the lowercase version of the members email address.
Use Digest::MD5.hexdigest to hash the email address with MD5:
GIBBON.lists(list_id).members(Digest::MD5.hexdigest(email.downcase)).upsert
Related
I am new to Postman. I have a Rails server running on the background. I am trying to mock a POST request, but it is not being accepted.
Let's say the model is called manufacturer_organization.rb. Inside, it requires 3 parameters: organization_id (uuid data type), manufacturer_id (integer data type), and account_number (string data type). manufacturer_organization belongs_to organization and it also belongs_to :manufacturer (vice versa; manufacturer and organization has_many manufacturer_organization)
Inside manufacturer_organizations_controller.rb, I have a create method:
def create
#manufacturer_organization = ManufacturerOrganization.new(manufacturer_organization_params)
if #manufacturer_organization.save
puts "success!"
render json: #manufacturer_organization
else
puts "Sorry, something went wrong"
end
end
I can confirm that I have sufficient authorization; when I perform a GET request I got the right JSON response. I am using rails serializer and I have setup serializer for this model as well. Route is also setup using resources :manufacturer_organizations. My gut feeling says the way I am using postman is wrong.
Here is the screenshot of Postman app. I have the right address on address bar, and I am performing a POST request. I have the three params under key-value.
After I Send it, under my Rails Server log I see:
Started POST "/manufacturer_organizations" for 127.0.0.1 at 2017-04-13 16:56:44 -0700
Processing by ManufacturerOrganizationsController#create as */*
Parameters: {"organization_id"=>"fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c", "manufacturer_id"=>"1", "account_number"=>"A rand
om account number test"}
...
(0.4ms) BEGIN
(0.3ms) ROLLBACK
Sorry, something went wrong
I can do ManufacturerOrganization.new(organization_id: Organization.last.id, manufacturer_id: Manufacturer.last.id, and account_number: "random test account number") just fine inside rails console.
How can I submit a POST request from postman to add a new manufacturer_organization?
Edit:
def manufacturer_organization_params
api_params.permit(:organization_id, :manufacturer_id, :account_number)
end
whereas inside application_controller.rb
def api_params
#api_params ||= ActionController::Parameters.new(ActiveModelSerializers::Deserialization.jsonapi_parse(params))
end
Edit2:
I added error.full_messages and this is what I got:
Manufacturer can't be blank
Organization can't be blank
Account number can't be blank
Why are they blank?
You can pass the data using params or within the body request.
The best way to do this is using the body, because you can send files and the request becomes more clean without the params.
To send data in the body, you must pass the model name and attribute in the "key" field, and the value in the "value" field, like this:
I don't understand what you do to your params. There is a reason the ActiveModelSerializers::Deserialization is namespaced in the "Model" namespace. It shouldn't be used to serialize or de-serialize internet params, but instead it's for serializing/de-serializing model instances.
If parameters arrive in the correct format ActionController::Base from which AplicationController and thus ManufacturerOrganizationsController inherit will de-serialize them for you. The Rails query parameter format looks as follows:
name=something #=> params[:name] = 'something'
names[]=something1&names[]=something2 #=> params[:names] = ['something1', 'something2']
instance[id]=1&instance[name]=foo #=> params[:instance] = {id: '1', name: 'foo'}
This can also be stacked and is used for nested resources by Rails. Example:
instance[title]=some&instance[nested][name]=thing&instance[nested][ids][]=1&instance[nested][ids][]=2
#=> params[:instance] = {title: 'some', nested: {name: 'thing', ids: ['1', '2']}}
Having said that let's get to your example. First of al let us throw away those manual building of params and stick to the convention:
class ManufacturerOrganizationsController
# ...
private
def manufacturer_organization_params
# arriving params should look like this:
#
#=> params = {
# manufacturer_organization: {
# organization_id: 'fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c',
# organization_id: '1',
# account_number: 'A random account number test'
# }
# }
#
# The method #require raises an exception if the provided key
# is not present or has a blank value (with exception of false).
# If the key is found and has a value present than that value is
# returned.
#
params.require(:manufacturer_organization)
.permit(:organization_id, :manufacturer_id, :account_number)
end
end
With that out of the way let's send the correct formatted params:
+--------------------------------------------+---------------------------------------+
| Key | Value |
|--------------------------------------------|---------------------------------------|
| manufacturer_organization[organization_id] | fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c |
| manufacturer_organization[manufacturer_id] | 1 |
| manufacturer_organization[account_number] | A random account number test |
+--------------------------------------------+---------------------------------------+
Those 2 things combined should let you create your resource successfully.
The key thing you should take from this is that params is not a string containing al the params that should be de-serialized. It already should be de-serialized, if it's not than you might have send your parameters wrong.
Ruby on Rails and Postman - Post request.
Hello, this is an example that I developed with Postman and Rails API.
Postman.
I can't add images but this what you have to add in postman Key = Value
Change to Post Request and send.
book[name] = 'Harry Potter'
book[author] = J.K. Rowling
Ruby on Rails 7.
Rails maintains the same code.
def create
#book = Book.new(book_params)
if #book.save
render json: #book, status: :created, location: api_v1_books_url(#book)
else
render json: #book.errors, status: :unprocessable_entity
end
end
def book_params
debugger
params.require(:book).permit(:name, :author, :price)
end
I hope this helps.
(If the next is too much to read for you, please read the last line). I'm using Ember and Rails backend from a while, but I'm still a bit stick with transactions or what other way is used to send actions to the backend. I have Users and Events. My Users can participate in one or several Events, and for each Event several Users can attend. I'm maintaining this relationship using the Mongoid macro has_and_belongs_to_many in this way:
Event
# ...
has_and_belongs_to_many :attendings, class_name: "User" , inverse_of: :attendances
# ...
User
# ...
has_and_belongs_to_many :attendances, class_name: "Event" , inverse_of: :attendings
# ...
In my template I'm showing a list of all the Events stored in my backend and a button "Attend" if the User is not already attending, and "Not attend" it the User is attending. For attending, I've tried to make the PUT request using transactions, making the next steps in my Ember Event controller:
Event controller (Ember), actionAttending method using transactions:
actionAttending: function() {
var userId = this.get('controllers.app.model.id');
this.transaction = this.get('store').transaction();
this.get('attendings').pushObject(App.User.find(userId));
this.transaction.add(this.get('model'));
this.transaction.commit();
}
When I try this, my Event JSON request is containing all the parameters and relations but attendings attribute. So I decided to try using a jQuery request:
Event controller (Ember), actionAttending method using jQuery ajax:
// ...
var eId = this.get('id');
this.get('attendings').pushObject(App.User.find(userId));
var eAttendings = this.get('attendings');
var url = "/events/" + eId + ".json";
var data = { event: { id: eId, attendings: eAttendings } };
$.ajax({
type: "PUT",
url: url,
data: data,
dataType: "JSON"
});
// ...
Well, this is pretty much working, except for the line where I declare eAttendings, where Ember complains about init function is not called, or somehing similar. After googling a bit I found a "solution", which is convert to array, so the line changed like this:
var eAttendings = this.get('attendings').toArray();
My nice error now is:
TypeError: fullName is undefined
var nameParts = fullName.split(":"),
This is raised in Firebug before my ajax request is launched. I don't know what's the exact meaning or how to fix it...
Anyway, I wanted to try if my ajax request is working, so I tried it in curl:
curl --request PUT localhost:3000/events/521b97ef5ef9095ba211bf70 --data "id=521b97ef5ef9095ba211bf70&attendings=521b7eda99027121d1533015"
The answer is:
{"errors":{"attendings":["is invalid"]}}
And the bakend is returning a 422 Unproccesable entity answer... I've got no validations on my model on this field, so I don't know what's going on here either... My update action on Rails Event controller is like this:
def update
e = Event.find(params[:id])
u = User.find(params[:attendings])
if params[:attendings]
e.attendings << u
e.save
respond_with e, api_template: :general_event, status: :no_content
end end
Last detail: I'm using Ember 1.0.0.rc6, jQuery 1.10.2 and gems acts_as_api and active_model_serializers in Rails.
So, to summarize, I want to get working just my PUT request in Ember to add Users attending to an Event through the has_many_and_belongs_to macro (using transactions, jQuery, or something else)
I am trying to create a Webhook through the API.
When the Customer installs the app (Controller):
def init_webhooks
topics = ["products/create", "products/update", "products/delete"]
topics.each do |topic|
webhook = ShopifyAPI::Webhook.create(format: "json", topic: topic, address: "http://#{#current_host}/webhooks/#{topic}")
raise "Webhook invalid: (#{topic}) #{webhook.errors}" unless webhook.valid?
end
end
Here is the error from the log:
RuntimeError (Webhook invalid: (products/create) #<ActiveResource::Errors:0x00000003bd7358>):
EDIT:
I have even tried just creating one webhook without the block code like so:
webhook = ShopifyAPI::Webhook.create topic: "products/create", address: "http://myapp.com/webhooks/products/create", format: "json"
But I get the same thing.
From my Routes file:
match 'webhooks/products/create' => 'webhook#product_new'
match 'webhooks/products/update' => 'webhook#product_updated'
match 'webhooks/products/delete' => 'webhook#product_deleted'
I know that the authorization and shop is in fact installing correctly because if I Comment out the 'Raise' error line, I then proceed to the index page which displays some test orders and test products that I created within the Shopify Admin.
I'm not sure where to go from here. Thanks
b
The params you use (topic,format,address) look good to me, but shouldn't it be ShopifyAPI::Webhook.new instead of create?
Did you now there is a shopify console where you can easily test your ruby code?
Has anyone had success in resubscribing an email address after being unsubscribed via the Campaign Monitor API.
I ask as i want to keep a list of Active User's email addresses in my CM Active List. When they are suspended they get removed, when they join or pay their fees before getting deleted they are (re)subscribed.
Looking at the Rails API docs:
# File lib/campaign_monitor.rb, line 241
def remove_subscriber(email)
response = #cm_client.Subscriber_Unsubscribe("ListID" => #id, "Email" => email)
Result.new(response["Message"], response["Code"].to_i)
end
# File lib/campaign_monitor.rb, line 445
def unsubscribe(list_id)
response = #cm_client.Subscriber_Unsubscribe("ListID" => list_id, "Email" => #email_address)
Result.new(response["Message"], response["Code"].to_i)
end
On the CM website to move an email in the subscriber list to the active list you need to confirm you have permission to resubscribe them, can anyone say for sure that this applies to the API too?
I've just found the Subscriber.AddAndResubscribe method, undocumented in http://campaignmonitor.rubyforge.org/
I'd like some advice on how I should synchronize a list of email addresses on 11k users against an external mailing list program, in this case Mailchimp.
Normally the way I'd do this is simply to have an :after_save callback, to send a single update to the external api.
But already each hour, a rake task is run to update a property on every user in the database. If I simply did that, every hour, the the poor mailchimp API would get be hit 11,000 times.
What's the most efficient, simple way to do this, to check only if a single attribute you're watching has changed from what it was before the save?
If there's a variable that persists across the transaction lifecycle I would simply do something like this, where I check if the value has changed, and if it's different execute come other code.
class User
:before_save :store_old_email
:after_save :sync_with_chimp
def store_old_email
$ugly_of_global_variable_to_store_email = user.email
end
:sync_with_chimp
if $ugly_of_global_variable_to_store_email != user.email
//update_mail_chimp_api
end
end
end
I've checked the rails api here, and I'm still slightly unclear on how I should be doing this.
Would you use the dirty? class here to do this?
This is the way I went with in the end.
It turns out Rails gives you loads of handy callbacks in the dirty to do this.
Any suggestions on how to make this code less repetitive wold be gratefully received.
def update_mailchimp(optin)
# Create a Hominid object (A wrapper to the mailchimp api), and pass in a hash from the yaml file
# telling which mailing list id to update with subscribe/unsubscribe notifications)
#hominid = Hominid.new
client_site_list_id = YAML.load(File.read(RAILS_ROOT + "/config/mailchimp.yml"))
case optin
when 'subscribe_newsletter'
logger.debug("subscribing to newsletter...")
"success!" if #hominid.subscribe(client_site_list_id['client_site_to_mailchimp_API_link'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'unsubscribe_newsletter'
logger.debug("unsubscribing from newsletter...")
"success!" if #hominid.subscribe(client_site_list_id['client_site_to_mailchimp_API_link'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'subscribe_monthly_update'
logger.debug("subscribing to monthly update...")
"success!" if #hominid.subscribe(client_site_list_id['monthly_update'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'unsubscribe_monthly_update'
logger.debug("unsubscribing from monthly update...")
"success!" if #hominid.unsubscribe(client_site_list_id['monthly_update'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
end
end
# Keep the users in sync with mailchimp's own records - by only firing requests to the API if details on a user have changed after saving.
def check_against_mailchimp
logger.info("Checking if changes need to be sent to mailchimp...")
if newsletter_changed?
logger.info("Newsletter changed...")
newsletter ? update_mailchimp('subscribe_newsletter') : update_mailchimp('unsubscribe_newsletter')
end
if monthly_update_changed?
logger.info("update preferences changed...")
monthly_update ? update_mailchimp('subscribe_monthly_update') : update_mailchimp('unsubscribe_monthly_update')
end
end
you could change your users model to an active resource instead of active record and just use mailchimps api as your db for users
this is an older post about active resource but might get you started down the right path
http://www.therailsway.com/2007/9/3/using-activeresource-to-consume-web-services